tiny-agent 把 “调用模型 → 执行工具 → 回灌结果” 的循环建模成一个显式状态机,并将模型、沙箱、存储、可观测与拦截全部抽象为 trait。框架本身不绑定任何后端:用哪家模型、状态存到哪里、工具跑在何种隔离环境,皆由调用方决定。
它面向需要把 agent 嵌进真实服务的场景——进程会重启、用户会取消、工具有副作用、运行过程要审计——这些工程负担都被收敛进清晰的边界里,而不是散落在业务代码中。
[]
= "0.3"
工作原理
一次运行就是状态之间的转移。每个状态都携带可序列化的 AgentState,这是 checkpoint 与恢复能成立的基础。
submit ─▶ Reasoning ─┬─▶ ToolExecuting ─▶ Reasoning 执行工具、回灌结果,继续推理
├─▶ WaitingForUser 工具请求用户输入,停车等待
└─▶ Success 模型给出最终回复(终态)
cancel ─▶ Interrupted ─▶ resume 任意安全点取消并落 checkpoint,可续跑
error ─▶ Fail 超过重试 / 迭代上限(终态)
特性
- 状态机式的 agent loop —
Ready → Reasoning → ToolExecuting → …全程显式建模,每个状态都可序列化。 - Checkpoint 与恢复 — 中断点写快照,
resume从断点续跑,终态自动清理。 - 协作式取消 — 传入
CancellationToken即可在安全点停车;未应答的工具调用会被补齐占位结果,transcript 不留孤儿tool_use。 - 后端无关 — Provider、Sandbox、两类存储、实时消息与 Middleware 边界清晰,可任意替换。
- 内置 Provider — Anthropic 与 OpenAI 兼容端点(DeepSeek、OpenAI 及各类兼容服务),均为流式。
- 沙箱抽象 — 触达外部世界的工具统一经
Sandbox::execute,自带HostSandbox,换隔离方案不改工具代码。 - 类型安全的工具 — 参数 struct 经
schemars自动生成 JSON Schema;只读工具之间自动并发调度。 - Skills — 按
SKILL.md约定从目录发现技能,模型按需加载,不必把说明一次性塞满上下文。 - 中间件 — 洋葱模型的生命周期钩子,可改写请求/消息/工具结果,或在工具执行前短路。
- 可观测 — 按 OpenTelemetry GenAI 语义约定产出嵌套 span(
agent.run/user_input/gen_ai.chat/tool.call),含完整输入输出、工具入参结果与 token 用量;由宿主程序决定是否接入 OTLP、日志或监控平台。 - 子 Agent —
spawn_agent把子任务委托给独立配置的子 agent。 - 健壮性 — 区分可重试与永久错误,指数退避 + jitter,并限制最大连续失败与迭代次数。
快速开始
AgentRunTime 需要五样必填依赖:一个 Provider、一个 model 名、两类存储(checkpoint 与 transcript)、一个 sandbox。Sandbox 框架自带 HostSandbox;存储 trait 由你按后端实现(内存、文件、SQLite、Redis……)。
下面用内存存储跑通一轮完整的 agent loop,完整版见 src/main.rs:
use ;
use async_trait;
use ;
use CancellationToken;
// 最小内存存储:同时实现 checkpoint 与 transcript 两个 trait。
// Agent 没有 Clone 但可序列化,所以 checkpoint 按 JSON 字符串存。
async
# 默认指向 DeepSeek,可用 LLM_BASE_URL / LLM_MODEL 覆盖
LLM_API_KEY=sk-xxx
HostSandbox会在本机直接执行 bash 命令,register_default_tools也会注册文件读写与 shell 工具;这套组合适合本地试跑和受信任环境。对外提供服务时,请实现隔离型Sandbox,并按业务白名单手动注册工具。
两种构造方式
上面的 AgentRunTimeBuilder 是链式写法。等价地,你也可以先组一个 AgentRuntimeConfig,再用 AgentRunTime::from_config 构造——当配置来自外部(反序列化、依赖注入、需要在多个 runtime / 子 agent 间复用同一份配置)时更顺手。
use Arc;
use ;
let config = new;
let runtime = from_config?;
AgentRunTimeBuilder 内部也只是收集这些字段后调 from_config,两者完全等价,按喜好选用。
可选 feature
tiny-agent 默认不带文件存储和 exporter,应用可按需打开:
| Feature | 提供内容 |
|---|---|
file-storage |
storage::FileStorage,把 checkpoint / transcript 以 JSON 落盘 |
otel-tempo |
telemetry::init_tempo_from_env / init_tempo,通过 OTLP/gRPC 导出 tracing span |
= { = "0.3", = ["file-storage", "otel-tempo"] }
use Arc;
use ;
let store = new;
let storage = shared;
核心概念
Agent 状态机
| 状态 | 含义 |
|---|---|
Ready |
准备进入下一轮推理 |
Reasoning |
正在调用模型 |
ToolExecuting |
正在执行模型请求的工具 |
WaitingForUser |
工具要求用户交互(如追问),已停车 |
Success / Fail |
终态,运行结束清理 checkpoint |
Interrupted |
被取消,已写快照,可 resume |
驱动入口:
| 方法 | 作用 |
|---|---|
create_session() |
新建会话 |
submit(session_id, text) |
追加一条用户消息,返回 Ready |
run(agent, token) |
把状态机跑到停车点 |
run_turn(session_id, text, token) |
可选会话 id + 用户输入,一站式追加并跑完一轮 |
resume(session_id, token) |
从中断的 checkpoint 续跑 |
run_session(session_id, token) |
一站式:有断点就恢复,否则从头跑 |
// 传 None 自动新建会话;传 Some(id) 复用已有会话(缺失则按该 id 初始化)。
let turn = runtime
.run_turn
.await?;
println!;
可插拔的 trait
| Trait | 职责 | 自带实现 |
|---|---|---|
LlmProvider |
流式 chat completion | AnthropicProvider、OpenAiCompatibleProvider |
Sandbox |
工具执行环境(按会话 open / execute / save / fork) | HostSandbox |
CheckpointStorage |
可恢复状态快照的存取 | FileStorage(需 file-storage)或自行实现 |
TranscriptStorage |
喂给模型的消息历史 | FileStorage(需 file-storage)或自行实现 |
Middleware |
生命周期钩子(拦截 / 改写 / 短路) | 自行实现 |
工具
用参数 struct(实现 Deserialize + JsonSchema)注册,Schema 自动生成:
tools.register;
内置工具:read_file、write_file(write / replace / insert / append)、bash、check、kill、askFollowupQuestion。
此外 register_spawn_agent 提供子 agent,register_read_skill 提供 skill 加载(配置 skill 目录时自动注册)。
register_default_tools 是便捷入口,会一次性注册文件读写、shell、后台任务查询与追问工具。它不会替你做权限控制;生产环境通常应从 ToolRegistry::new() 开始,只注册当前场景允许的工具,或用 Middleware::before_tool 做二次拦截。也可以先注册一套全集,再用 registry.subset(&["read_file", "bash"]) 按能力边界裁剪出某个(子)agent 可见的子集(保留各工具的 callable / schema / 异步策略);contains / names 可用于检视已注册的工具。
长耗时工具可以用结构化注册声明为 asynchronous:工具在 yield_after 窗口内完成时直接返回完整结果;超过窗口则登记为后台工具任务,立即返回 {id, status:"running", output},之后由 check 轮询、kill 终止。工具执行期间可通过 ctx.progress 发增量状态,这些状态会实时推给 UI,也会被 check(id) 按增量读回。
ToolCtx 对外只暴露只读的 session_id() 与 progress;工具不要直接操作 runtime 的消息通道或后台任务表。
use Duration;
use ;
tools.register_with_ctx_tool;
bash 只是内置的一个 asynchronous 工具,默认让步窗口是 2 秒。后台工具任务表存在于当前 runtime 进程内:进程重启后 checkpoint 和 transcript 可以恢复,但已经在后台运行的宿主机进程不会被 tiny-agent 重新挂接,旧的任务 id 也会失效。
Skills
Skill 让模型按需加载领域知识或操作手册,约定沿用 Agent Skills:一个目录配一个 SKILL.md,YAML frontmatter 写 name 与 description,正文是给模型看的说明,同目录其余文件作为可选资源。
构建时用 skill_dir / skill_dirs 指定发现目录,发现规则:
dir/SKILL.md存在 →dir本身是一个 skill;root/*/SKILL.md存在 → 每个子目录各是一个 skill;- skill id 取目录名,
name/description取自 frontmatter。
let runtime = new
// ...必填项
.skill_dir
.build?;
配置了 skill 目录后,框架自动注册 readSkill 工具:模型先看到所有 skill 的 name + description 索引,需要时再调用 readSkill 拉取某个 skill 的正文或某个资源文件——按需加载,避免把全部说明一次性塞进上下文。
底层的 SkillManager 也可独立使用(discover_dirs / list / read),用于在运行时之外检视或预加载技能。
子 Agent(spawn_agent)
spawn_agent 是一个工具:让父 agent 把一段独立子任务委托给一个单独配置的子 agent,子 agent 跑完后把结果回传给父 agent。
子 agent 的形态由你传入的一份 AgentRuntimeConfig 决定——它可以用不同的 model、system prompt、工具集、中间件,与父 agent 互不影响。注册时把这份子配置交给 register_spawn_agent:
use ;
// 1. 子 agent 的配置:可与父 agent 完全不同的一套
let mut child_tools = new;
register_default_tools;
let child_config = new;
// 2. 父 agent 的工具集:默认工具 + spawn_agent
let mut parent_tools = new;
register_default_tools;
register_spawn_agent;
// 3. 父 runtime 使用 parent_tools,模型即可调用 spawn_agent { task: "..." } 派生子 agent
let parent_runtime = new
.provider
.model
.checkpoint_storage
.transcript_storage
.sandbox
.tools
.system_prompt
.build?;
几个关键点:
- 子 agent 拥有独立的会话与 transcript(互不污染上下文),但共享父 agent 当前的 sandbox——子任务里
write_file、bash的副作用落在同一个工作环境,父 agent 后续可见。注册时配置里的sandbox会在调用时被父 sandbox 覆盖,所以那一项填什么都行。 - 子会话会登记为父会话的 subsession(父 transcript 的
subsessions里记下子会话 id),便于按一次 turn 下钻到各子 agent 会话;级联删除父会话时子会话树会被一并清掉。 - 取消会向下传播:子 agent 用父 run 取消令牌派生的 child token 跑,父任务被取消时子 agent 也会干净地一并取消(走 Interrupted / 存档),而非被硬掐断。
- 工具入参只有一个
task字段;返回child_session_id与子 agent 的最终结果(含失败 / 等待用户 / 中断等状态)。 - 子配置同样可挂自己的 skills、中间件、工具集,实现「专用子 agent」。
中间件
钩子按洋葱模型组合:before_* 正序、after_* 逆序。可改请求、改消息、给工具结果脱敏,或在工具执行前直接短路。
可观测:tracing / OpenTelemetry
tiny-agent 不内置观测存储,也不替宿主程序初始化全局 exporter。运行时把一轮对话建模成一棵嵌套 span 树,按 OpenTelemetry GenAI 语义约定产出,可直接被 Tempo / Jaeger / Langfuse 等 OTLP 后端渲染:
agent.run 一轮运行(root)
├─ user_input 用户提交本轮输入
├─ gen_ai.chat 一次 LLM 调用
│ gen_ai.request.system / .model / .temperature / .max_tokens
│ gen_ai.input.messages 发给模型的完整输入(system + 历史 + 本轮,全量 JSON)
│ gen_ai.output.messages 模型输出(thinking / 回答 / 工具调用,全量)
│ gen_ai.response.finish_reasons
│ gen_ai.usage.input_tokens / .output_tokens / .total_tokens
├─ tool.call 一次工具调用
│ gen_ai.tool.name / .call.id
│ gen_ai.tool.call.arguments 工具入参(全量 JSON)
│ gen_ai.tool.call.result 工具结果(全量 JSON)/ error
└─ gen_ai.chat …
tracing event 只作为轻量阶段标记挂在对应 span 上(user_message、reasoning_started、assistant_message、tool_started、tool_completed、succeeded 等),只携带 id、模型、工具名、数量、字节数、错误标记等元数据;完整 prompt / 消息 / 工具入参 / 工具结果只记录在 span attribute 上。框架自身的补充统计放在 tiny_agent.* 属性下。是否记录、采样、脱敏、落盘或导出,全部由你的应用侧 tracing_subscriber / OpenTelemetry exporter 决定——库不会和宿主已有的 tracing 配置争抢全局 subscriber。
打开 otel-tempo feature 后,可用便捷函数按环境变量接入本地 Collector / Tempo:
let _telemetry = init_tempo_from_env?;
设置 TINY_AGENT_OTEL=1 或 OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317 后会启用导出;未设置时 init_tempo_from_env 返回 Ok(None)。如果宿主已有 subscriber,可用 build_provider / tempo_layer 自行组合。
⚠️ 隐私(自 0.2 起的行为变化):span attribute 现在会记录完整原文——系统提示、对话历史、模型输出、工具入参与结果都会进入 tracing 属性。这便于审计与排查,但意味着 trace 里会含敏感数据。如不需要全量内容,请在应用侧 subscriber 过滤
tiny_agenttarget 或相应字段、调低采样、或在 Collector 侧脱敏;只要不接 exporter,则什么都不会离开进程。
配置
AgentRunTimeBuilder 上的可调参数:
| 方法 | 默认 | 说明 |
|---|---|---|
max_iter |
10 | 单次运行的最大推理轮数,超限以 MaxIter 失败 |
max_consecutive_fail_count |
3 | 最大连续失败次数 |
retry_backoff |
200ms | 可重试失败的退避基数(指数 + jitter),ZERO 关闭 |
tool_timeout |
120s | 单个工具调用超时,ZERO 关闭 |
temperature / max_tokens |
无 | 透传给 provider |
system_prompt |
无 | 系统提示 |
skill_dir / skill_dirs |
无 | skill 发现目录 |
middleware |
空链 | 可多次调用追加 |
兼容性
需要 Rust 1.85 及以上(edition 2024)。运行时基于 Tokio。
许可证
采用 MIT 或 Apache-2.0 双许可,任选其一。
除非你明确声明,你有意提交并被本仓库收录的贡献,均按上述双许可授权,不附加额外条款。