> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/kai-fa/architecture/agent-harness.md).

# 智能体运行框架

> **状态（问题 #4249，tinyagents 迁移）：** 代理回合不再在树内的 `run_turn_engine` 循环中运行。 **所有三个入口点（`Agent::turn`、channel/CLI 总线路径，以及 `run_subagent`）现在都通过已发布的** [**`tinyagents`**](https://crates.io/crates/tinyagents) **2.1 代理循环 harness** 通过中的适配器缝隙驱动每一回合，位于 [`src/openhuman/agent/tinyagents/`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/tinyagents/README.md) (`run_turn_via_tinyagents_shared`）。旧版的 `run_turn_engine`、那三个手写循环、 `turn_engine_adapter`以及本页后面描述的自定义 `agent_graph/` 引擎已经被 **移除**；幸存的共享缝隙（`CheckpointStrategy`, `TurnProgress`）位于 `agent/harness/engine/`。废弃的 `token_budget.rs` （上下文裁剪现在由 `MessageTrimMiddleware`完成）以及名存实亡的 `interrupt.rs` 栅栏（取消现在是 tinyagents 的 steering channel）都已消失；策略 **停止钩子** （预算 / 线程目标 / 迭代上限）现在通过一个 `StopHookMiddleware` ([`tinyagents/stop_hooks.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/tinyagents/stop_hooks.rs)）触发，它会在第一次停止投票时暂停运行，而 channel 路由会像 chat 路由一样转发实时的 `AgentProgress` 。
>
> 多代理 **编排** 通过 tinyagents 的 **图层** 以 `graph::parallel::map_reduce`、 `spawn_parallel_graph` 脚手架，以及共享的 `graph::orchestration` `TaskStore` 生命周期原语来表达，这些原语从 [`tinyagents/orchestration.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/tinyagents/orchestration.rs):
>
> * [`tinyagents/delegation.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/tinyagents/delegation.rs) 重新导出；一个 `plan → execute ⇄ review → finalize` `CompiledGraph` （条件路由、 `RecursionPolicy`、持久化的 `FileCheckpointer`, `CancellationToken`, `GraphTracingSink`);
> * 的 **工作流阶段引擎** 会将每个阶段的代理在图上展开（`with_max_concurrency`），并将持久化的 `WorkflowRun` 账本作为恢复的事实来源；
> * `spawn_parallel_agents` 通过 `spawn_parallel_graph` + `graph::parallel::map_reduce`;
> * 的 **agent-teams** 运行其展开逻辑；成员运行时是一个条件路由图（`execute → complete | fail → done`, [`agent_teams/graph.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/orchestration/agent_teams/graph.rs));
> * 的 **分离子代理** 注册表由一个类型化的 `TaskStore` 生命周期账本（Pending → Running → Completed/Failed/Cancelled）支持。
>
> 下面介绍自定义的 `agent_graph/` 模块 + 每个代理的 `GraphBlueprint`的各节 **是历史性的** （迁移前的设计），仅保留作上下文。

## TinyAgents crate：特性与兼容性

OpenHuman 需要 `tinyagents = { version = "2.1", features = ["sqlite", "repl"] }`，并补丁到 vendored git 子模块 `vendor/tinyagents` ，这样 SDK 变更就能先在树内测试，再向上游提 PR（见 [`Cargo.toml`](https://github.com/tinyhumansai/openhuman/tree/main/Cargo.toml) ——请让子模块与版本要求保持同步）。其原因如下，以免未来升级时悄悄回退：

* **原生 TinyAgents 模型接口，OpenHuman 拥有的产品策略。** 每条在线路由都是一个 `Arc<dyn ChatModel<()>>`：TinyAgents 兼容 OpenAI 的客户端覆盖了等价的托管、本地和 BYOK 路由，而宿主 `ChatModel` 实现则覆盖 Claude SDK/Code 和 Codex 特定的传输。OpenHuman 仍然负责凭据解析、OAuth、访问门控、端点选择、出站披露、计费元数据和错误分类。
* **`sqlite` 特性已启用，使用一条原生 sqlite 链。** OpenHuman 的根 Cargo 世界和 Tauri Cargo 世界固定为 `rusqlite = "=0.40.0"` 并在本地补丁 `rusqlite` / `libsqlite3-sys` ，以避免上游在当前工具链上出现 `cfg_select!` 构建中断。两个世界最终都解析为单一的 `libsqlite3-sys v0.38.0` 链。持久化图检查点仍然通过 [`SqlRunLedgerCheckpointer`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/tinyagents/checkpoint.rs) 运行，直到迁移将这些行重定向到 crate checkpointer。
* **WhatsApp Web 存储桥接。** `whatsapp-rust`的基于 Diesel 的 `sqlite-storage` 特性将 sqlite 与 rusqlite 0.40 分开链接，因此可选的 `whatsapp-web` 特性当前是针对 `wacore::store::InMemoryBackend` 构建的，并记录会话并非持久化。在再次将 Web 会话视为持久化之前，需要一个基于 rusqlite 的持久化 WhatsApp 存储。
* **`repl` 特性已为语言工作流启用； `.rag` 富表达语言未使用。** OpenHuman 仍然从 Rust 驱动 *图* （`GraphBuilder`），而不是使用声明式 `.rag` 语言。但 `repl` 特性（命令式 Rhai `.ragsh` 会话运行时）已启用，用来支撑 `rhai_workflows` 语言工作流工具（[`openhuman::flows::rhai`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/flows/rhai/README.md)，见下文“语言工作流（Rhai）”）。
* **所有权映射：** 模型构建 → `inference::provider::create_chat_model*`；crate SQLite checkpointer 行尚未采用 → `SqlRunLedgerCheckpointer`；通用分离执行器状态 → `DetachedTaskRegistry`；面向控制器的持久性 → OpenHuman SQL/JSON 运行账本（`running_subagents`, `workflow_runs`, `agent_teams`, `command_center`）。通用的 harness/graph/middleware/event 原语按原样使用。

代理 harness 是运行时，它把用户消息（或 webhook 触发，或 cron 时钟滴答）转换成一次完整的、使用工具的 LLM 交互。它负责工具调用循环、子代理调度、触发器分流流水线，以及围绕它们的钩子表面。它 **不** 负责提供方的 HTTP 传输、工具实现、提示词区段组装或内存存储——这些都是 harness 组合的独立领域。

本页先讲单次回合中发生了什么，然后逐步放大每个移动部件。

## 回合的形状

每个回合——无论用户刚输入了一条消息、Telegram webhook 刚触发，还是早上 9 点的 cron 刚响——都通过同一生命周期流转：

```
┌─ 输入 ─────────────────────────────────────────────────────────┐
│ 用户消息 · channel 输入 · webhook · cron · composio 事件 │
└──────────────────────────┬────────────────────────────────────────┘
                           │
                           ▼  （仅外部触发器）
                ┌──────────────────────┐
                │   触发器分流         │  分类 → 丢弃 / 通知 /
                │   （本地小型 LLM）   │  生成反应器 / 生成编排器
                └──────────┬───────────┘
                           │
                           ▼
            ┌──────────────────────────────┐
            │      Agent::turn()           │
            │  1. 恢复对话记录        │
            │  2. 构建系统提示词*     │
            │  3. 注入记忆上下文      │
            │  4. 进入工具调用循环 ────┼──► 提供方调用
            │  5. 调度工具调用  ────┼──► 工具执行 / 子代理生成
            │  6. 上下文保护 / 压缩  │
            │  7. 停止钩子检查        │
            │  8. 最终助手文本        │
            └──────────┬───────────────────┘
                       │ 异步执行，在用户看到回复之后
                       ▼
              ┌─────────────────┐
              │  回合后        │  归档员 · 学习 · 成本日志 ·
              │  钩子          │  情节性记忆索引
              └─────────────────┘

* 系统提示词只在第一回合构建——后续
  回合原样复用已渲染的提示词，因此推理
  后端的 KV-cache 前缀仍然有效。
```

本页其余部分是同一图示的展开版。

## 会话与 `Agent::turn`

一个 **会话** 就是一个 `Agent` 实例正在运行时的实时对话。这个 `Agent` 结构体拥有：

* 对话历史（系统 + 用户 + 助手 + 工具消息）。
* 要调用的提供方客户端（由 [模型路由器](/openhuman/zh/gong-neng/model-routing.md)).
* 解析出的模型）。
* 一个记忆加载器，在每条用户消息之前加载相关记忆。
* 每回合预算——最大工具迭代数、最大载荷大小、最大 USD 成本。
* 本地动作预算——一个用于会产生副作用的工具动作的滚动小时上限，读取自 `config.autonomy.max_actions_per_hour`.

`Agent::turn(user_message)` 是热路径。一次回合中它：

1. **恢复会话对话记录** 如果这是一个新进程——从磁盘重新加载精确的提供方消息，因此推理后端的 KV-cache 前缀仍能命中。
2. **构建系统提示词** （仅在第一回合）。这会拉入身份、soul、profile、memory、已连接的集成、可用工具、安全前置说明——由提示词区段构建器组装。
3. **注入记忆上下文** 用于新的用户消息，方法是通过记忆加载器：来自 [Memory Tree](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md)的相关块，并附带引用，以便 UI 展示来源。
4. **进入工具调用循环** （下一节）。
5. **在后台生成回合后钩子** ——在归档员 / 学习 / 成本记录完成之前，用户就会先拿到回复。

系统提示词 **不** 会在后续回合中重建。即使是纯粹的字节级变化也会使 KV-cache 前缀失效并强制完整重新预填充，因此动态的逐回合上下文（记忆回忆、新近学习的片段）会作为用户可见的消息内容附加，而不是拼接进系统提示词。

### AGENTS.md 项目指令

除了身份/soul/profile/memory 之外，系统提示词还会拉入 **AGENTS.md** 指令文件——OpenHuman 对 Claude Code 的 `CLAUDE.md` / Codex 的 `AGENTS.md`对应物。两层内容在 **一次**系统提示词构建时加载（每回合不会重新读取，因此冻结前缀 / KV-cache 合约成立）：

* **全局** — `<workspace_dir>/AGENTS.md`，用户的 OpenHuman 工作区（ `SOUL.md` / `USER.md` 位于此处）。适用于每次运行。
* **项目** — `<action_dir>/AGENTS.md`，代理正在操作的文件夹。对于带有 git-worktree 覆盖的子代理运行（`SubagentRunOptions.worktree_action_dir`），该覆盖目录会作为项目层。

全局层先渲染，项目层后渲染（项目指令叠加在后，冲突时以其为准），其标题为 `## 项目指令（AGENTS.md）` 。当两个目录解析为同一路径时，该文件只会被加载 **一次** （去重）。缺失 / 无法读取 / 为空的文件会被静默跳过，每层上限为 `BOOTSTRAP_MAX_CHARS` （约 20,000 字符），并带有 `[... 已截断]` 标记，这样大文件就不会挤占提示词。

加载器是 `agent::prompts::agents_md` （返回预加载字符串的纯函数，读取时有上限，因此畸形的多 MB 文件不会在渲染时上限之前耗尽内存）；这些字符串会被串联到 `PromptContext` (`agents_md_global` / `agents_md_local`）并由 `AgentsInstructionsSection`渲染；它位于默认和子代理构建器中的用户文件区段之后、工具目录之前。 **主 / 编排** 代理以及其他内建动态代理（`PromptSource::Dynamic`）通过 `render_*` 帮助函数组装自己的正文，因此同样的 `AgentsInstructionsSection` 会由 `SystemPromptBuilder::from_dynamic` 集中注入——附加在代理自己的正文之后、中心 grounding 合约之前——而不是由每个 `agents/<id>/prompt.rs` 构建器分别注入。该特性由 `agent.agents_md_enabled` （默认 **开启**）控制；关闭时，不会加载或注入任何 AGENTS.md 内容。

## 工具调用循环

在 `Agent::turn`内部，工具调用循环就是内核引擎。自问题 #4249 起，它就是已发布的 **tinyagents** crate 的 `AgentHarness` 循环，由 `run_turn_via_tinyagents_shared` ([`src/openhuman/agent/tinyagents/mod.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/tinyagents/mod.rs)按每回合组装。它最多运行 `max_tool_iterations` 轮（默认 10）：

```
loop {
    1. 上下文保护      - 如果历史太大，则微压缩 / 自动压缩
    2. 停止钩子检查    - 预算上限、最大迭代数、自定义杀开关
    3. 提供方调用      - 发送消息 + 工具规格，流式接收响应
    4. 解析响应       - 将助手文本与工具调用分离
    5. 如果没有工具调用 - 返回最终文本
    6. 执行工具调用   - 分发每一个调用（下一节）
    7. 超大内容摘要   - 将巨大的工具输出路由给摘要代理
    8. 追加结果       - 将工具结果推入历史，再次循环
}
```

每次迭代都会发出一个实时 `AgentProgress` 事件，因此 UI 可以渲染逐 token 流式输出、“正在调用工具 X”状态，以及每次迭代的成本更新。

**一个引擎，三个入口点。** 循环只存在于一个地方（tinyagents `AgentHarness`，通过 `run_turn_via_tinyagents_shared` 进入 `src/openhuman/agent/tinyagents/mod.rs`），并且每个调用方都会驱动它：聊天回合（`harness/session/turn/core.rs` → `session/turn/graph.rs`），通道/CLI 总线轮次（`harness/graph.rs`），以及生成的子代理（`harness/subagent_runner/ops/graph.rs`）。每个调用方不同的部分通过适配器接缝提供：将 OpenHuman 的 provider 封装为一个 `ChatModel` (`tinyagents/model.rs`），将工具封装为 tinyagents `Tool`s（`tinyagents/tools.rs`），一个事件桥，将 harness 的 `AgentEvent`s 投射为 `AgentProgress` + 成本遥测（`tinyagents/observability.rs`), `RunPolicy::unknown_tool` 用于幻觉工具恢复，以及一个具名中间件栈（`tinyagents/middleware.rs`）承载 OpenHuman 的横切关注点：审批/安全门控（`ApprovalSecurityMiddleware`），工具策略以及仅限 CLI/RPC 的拒绝（`ToolPolicyMiddleware`, `CliRpcOnlyMiddleware`），错误参数恢复（`ArgRecoveryMiddleware`），成本预算预检（`CostBudgetMiddleware`），重复工具失败断路器（`RepeatedToolFailureMiddleware`），以及上下文裁剪/压缩。策略停止钩子通过 `StopHookMiddleware` (`tinyagents/stop_hooks.rs`）。保留下来的由 OpenHuman 拥有的接缝， `CheckpointStrategy` （在模型调用上限处是报错还是总结）以及 `TurnProgress`，位于 `harness/engine/`。由于这三个入口点组装的是同一个 harness，它们不会产生偏移。

### 工具分发与工具调用方言

实时轮次使用 **原生工具调用**：tinyagents harness 通过 `ChatModel` 适配器发送结构化工具规格，并为每个 provider（Claude、GPT、Gemini、本地 Ollama 皆如此）接收结构化工具调用返回。

较旧的 `ToolDispatcher` trait（`src/openhuman/agent/dispatcher.rs`）及其三种方言仍然存在，但作为一个 **转录兼容层**，而不是实时路由选择：

* **原生** - 结构化工具调用字段，实时轮次如今生成的形状。
* **XML** - `<tool_call>{...}</tool_call>` 标签，出现在助手文本中，由旧会话生成。
* **P-格式** - 某些早期本地模型使用的一种紧凑文本格式。

持久化的会话历史可以包含这三种形状中的任意一种后缀，因此会话 shell 会保留 dispatcher，以便在恢复转录时准确解析并回放它们。

### 循环中的上下文管理

长工具调用链可能会突破上下文窗口。对此有两层处理：

* **工具结果预算** - 每个工具结果都会按每次调用的字节预算检查，由 tinyagents 的工具中间件强制执行。超出的部分会被硬截断，并附上说明标记，让模型知道它没有看到完整输出。
* **微压缩 / 自动压缩** - 当总历史接近上下文窗口时，tinyagents 中间件（消息裁剪 + 中的压缩钩子 `tinyagents/summarize.rs`）会在下一次 provider 调用前将较早的轮次压缩为摘要。压缩后的历史会保留系统提示和最近的轮次不变（KV-cache 稳定性），并重写中间部分。

### 过大的工具结果 - 摘要器绕行

有些工具调用会返回巨大的载荷——比如某个 Composio 动作输出 200 KB 的 JSON、一次网页抓取返回 50 KB 的 markdown，或一个 `file_read` 跨越数千行日志。若在载荷中间硬截断，就会丢掉截断点之后的任何内容。

当工具结果超过摘要器阈值时，它会被路由到专用的 `summarizer` 子代理，然后再进入父级历史。摘要器会按照一个保留标识符和关键信息的抽取契约压缩载荷，而父代理只会看到压缩后的摘要。当摘要失败，或者载荷大到为其支付一次 LLM 调用都不划算时，硬截断仍然是下游的最后保险。

### 文件系统卸载 - `outputs/` 和 `workspace/`

仅靠压缩无法支撑长周期运行。摘要仍会一步步累积，而无论压缩多少都无法恢复已丢失的保真度。因此，对于持续数分钟到数小时的任务，harness 会将大结果 **移出上下文并写入磁盘**，并向下一步提供一个 **路径**.

位于代理现有的两个目录下 `action_dir` 在运行时（实现位于 `src/openhuman/agent/harness/artifact_offload/`):

| 目录                      | 包含                          |
| ----------------------- | --------------------------- |
| `action_dir/outputs/`   | 交付物。旨在比生成它们的步骤存活更久，并通过路径传递。 |
| `action_dir/workspace/` | 临时区。工作线程需要但不会回传的中间文件。       |

注意 `action_dir/workspace/` 是代理 action 根目录中的一个临时文件夹。它是 **不** 核心内部的 `workspace_dir`，代理写入的内容可能永远不会到达这里。

两个部分共同强制这一约定：

* **提示。** 真正持有 `file_write` 的子代理会在其系统提示中获得一份长周期工件卸载契约：超过大约 2 000 个 token 的结果会写入位于以下位置的文件： `outputs/`，回复则是该相对路径加上一段简短摘要。这个门槛是刻意设置的——提示里只能写代理真正能调用的工具，否则模型会生成失败的调用。 `研究员` （仅搜索 + 获取）以及按技能过滤的专家都不会获得契约文本，而专门的守卫会断言其提示中绝不提及文件系统工具。它们仍受下面的 harness 部分覆盖，该部分不需要模型配合。相关的 archetype 提示（`研究员`, `规划者`）会说明该约定对其自身工作的含义；规划者会被要求在 DAG 节点之间引用工件路径，而不是将载荷直接向前粘贴。
* **Harness。** `offload_oversized_result` 会在每个子代理结果上运行，因此即使工作线程已经内联返回，过大的结果也会被卸载。它会在 **之前** 定义的 `max_result_chars` 上限之前触发，因此完整正文会落盘而不是被截断。

父级收到的是一个指针，而不是载荷：

```
[artifact] kind=output path=outputs/researcher/sub-1234-result.md bytes=52318
read_with: file_read {"path":"outputs/researcher/sub-1234-result.md"}
注：完整结果已写入 action workspace，而不是以内联方式返回。……

[abstract]
核心发现……
```

`SubagentRunOutcome.artifact_paths` 会以结构化方式携带相同的路径，这些路径解析自 `[artifact]` 输出中的指针，因此无论 harness 卸载了结果还是工作线程自己写入了文件，交接都会携带路径。父级接收到的任何路径都可以通过普通的 `file_read` 在子级上下文消失很久之后恢复。

**摘要器现在是后备方案，而不是首选。** 卸载处理常见情况；摘要器绕行以及 `tool_result_budget_bytes` 上面的截断仍会处理它无法处理的一切，包括这里的每一种失败模式。被拒绝或失败的卸载是刻意设计成软失败：调用方保留其内联载荷，并继续落到这些后备机制上。

**路径加固采用 fail-closed 策略。** `resolve_artifact_path` 会拒绝绝对路径， `..` 目录穿越，以及在词法规范化后逃出其约定根目录的任何内容。当 `SecurityPolicy` 可用时，它还会拒绝位于以下位置下的任何内容： `workspace_dir`，既通过整体包含规则，也通过 `is_workspace_internal_path`。卸载目标解析到 `action_dir`下，绝不会到 `workspace_dir`  - 包括有人把 `action_dir` 配置在 workspace 根目录内的情况，在那里每一次卸载都会被拒绝，而不是悄悄写入内部状态。

写入日志 `[artifact] wrote worker artifact under action_dir`；交接携带的每个路径都会记录 `[artifact] handoff carried an artifact path to the parent`，在生产端和消费端都会记录，因此运行日志会显示每个指针的两端。

### TokenJuice - 感知内容的工具输出压缩（阶段 1a）

在新的工具结果进入历史之前（并且在字节预算后备机制之前），它会先经过 **TokenJuice 内容路由器** ，位于 vendored 的 TinyJuice crate（`vendor/tinyjuice`），OpenHuman 适配器位于 `src/openhuman/inference/tokenjuice/`。受 Headroom 启发，该路由器 *检测内容类型* （JSON、代码、日志、搜索、diff、HTML、纯文本），依据字节本身和/或从工具名及参数派生出的提示，然后分发到专用压缩器：

* **JSON** → SmartCrusher：对象数组 → 表格（每个键只出现一次），保留包含错误或数值离群值的行。
* **代码** → tree-sitter（Rust/TS/JS/Python）签名保留器，会折叠函数体；并有基于括号深度的启发式后备。
* **日志** → 用于 *命令* 输出（git/cargo/npm/…）的 100 规则引擎；否则基于信号保留失败。
* **搜索** → 按相关性排序的每文件 top-K 匹配，带有一个 `+N 个更多` 计数。
* **差异** → 保留变更的代码块，折叠未变更上下文，总结 lockfile 代码块。
* **HTML** → 去除标记，转为可读文本。
* **纯文本** → 可选启用的 Python/ML“Kompress”压缩器（ModernBERT），或直接透传。

每一次有损压缩都会将原始内容卸载到 **CCR（压缩-缓存-检索）** 存储中，并附带一个 `⟦tj:<hash>⟧` 标记，因此压缩在效果上是无损的：代理会调用 `tokenjuice_retrieve` （token + 可选的字节/行范围）按需获取完整原始内容。相同的引擎也作为通用的 `compress_content(content, hint, opts)` 接口提供给任何大型载荷（文件读取、网页抓取），并作为只读的 `tokenjuice.*` 调试 RPC。通过以下方式配置： `[tokenjuice]` 块 / `OPENHUMAN_TOKENJUICE_*` 环境变量。代理定义可以通过以下方式覆盖工具结果压缩： `tokenjuice_compression = "auto" | "full" | "light" | "off"`; `自动` 会将编码模型代理（`[model] hint = "coding"`）解析为 `轻量`，这会禁用基于 CCR 的有损压缩，因此编码代理会保留原始的构建/测试/diff/搜索文本，除非某种缩减确实是无损的。其他代理默认为 `完全`。ML（Kompress）路径作为一个 `kompress` 共享的 [`runtime_python_server`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/runtime/python_server/README.md) （torch + ModernBERT 在运行时通过 pip 安装），受以下标志控制： `ml_compression_enabled` 标志控制；当 Python 运行时不可用时，会优雅降级为原生压缩器。

### 这个 `tool_maker` archetype

这个 `tool_maker` archetype 的用途是在缺少某项能力时编写 polyfill 脚本和小型辅助工具。它像其他任何子代理一样会被显式启动（由 orchestrator 或其他代理）。随着内部循环的引入，旧的自动“command not found → spawn ToolMaker → retry”拦截器已被移除；如今 shell 失败时不会隐式自愈重试。

## 子代理 - orchestrator 模式

OpenHuman 是 **多代理**。用户正在与之聊天的代理是 **主代理** （稳定内部 id： `orchestrator`）——一个能直接回答并完成普通工作的强大默认代理，包括 inspect → edit → verify 的编码循环。当并行处理、更深层推理或某项专门能力能显著提升效果时，它会启动专门子代理。

### 为何采用多代理

一个什么都知道的单一代理，其系统提示也会大到像一本小书。将工作拆分给专门代理意味着：

* 每个子代理都会获得一个 **狭窄的系统提示** ，只包含它需要的部分（身份 / 记忆 / 安全前言都可以移除）。
* 每个子代理都会获得一个 **过滤后的工具注册表**  - integrations 代理不需要文件系统工具，coder 不需要 Composio 目录。
* 子代理历史绝不会泄漏回父级——父级看到的只是一个紧凑的工具结果，而不是内部对话。
* 更便宜的模型可以完成叶子工作。orchestrator 使用更强的推理模型；research 子代理可能使用更快、更便宜的模型。

### 内置的 archetype

每个 archetype 都位于 `agents/<name>/` ，并带有一个 `agent.toml` （元数据、工具范围、模型提示）以及一个提示：

| Archetype            | 当 orchestrator 选择它时                                      |
| -------------------- | -------------------------------------------------------- |
| `orchestrator`       | 主代理：顶层、可直接处理的默认代理。绝不会由另一个 orchestrator 生成。               |
| `规划者`                | 多步分解 - 将复杂请求拆分为有序子任务。                                    |
| `研究员`                | 网页/文档查找，引用搜集。                                            |
| `code_executor`      | 在工作区中编写、运行和调试代码。                                         |
| `critic`             | 代码审查，对另一个代理的输出进行质量检查。                                    |
| `summarizer`         | 压缩过大的工具结果（由 harness 调用，通常不是模型调用）。                        |
| `archivist`          | 记忆提炼 - 保留什么，遗忘什么。                                        |
| `tool_maker`         | 自愈 - 为缺失的 shell 命令编写 polyfill。                           |
| `tools_agent`        | 用于任意工具绑定任务的通用专家。                                         |
| `integrations_agent` | 绑定到特定的 Composio 工具包（Gmail、GitHub、Slack…），用于该工具包的操作。      |
| `trigger_triage`     | 将传入的外部事件分类为 drop / notify / spawn-reactor / spawn-agent。 |
| `trigger_reactor`    | 对已分诊触发器的轻量响应，不需要完整的 orchestrator 回合。                     |
| `morning_briefing`   | 由 cron 运行的每日精选摘要。                                        |
| `welcome` / `help`   | 入门流程。                                                    |

自定义 archetype 以 TOML 文件形式放在 `$OPENHUMAN_WORKSPACE/agents/*.toml` （或 `~/.openhuman/agents/*.toml` 供用户全局专家使用）。当 id 冲突时，自定义定义会覆盖内置定义。

### 运行可复用子代理

当 orchestrator 调用 `spawn_subagent`时，默认契约是持久且异步的。该工具会根据父会话/线程、agent id、工具包范围、模型覆盖、sandbox 模式、action 根目录以及规范化后的任务 key/title 构建一个确定性的兼容性选择器。然后它会检查 `agent_orchestration::subagent_sessions` ，再生成之前：

* 如果已有兼容的 worker 在运行，则该指令会通过其 `RunQueue` 注入，父级则会快速获得一个 `subagent_session_id` / `task_id` 引用。
* 如果兼容的 worker 处于空闲或暂停状态且拥有可复用历史，harness 会为相同的持久 `subagent_session_id` 启动一个新的临时运行，并通过 `SubagentRunOptions.initial_history`传入已保存的子级历史，同时将新指令追加为用户可见的后续内容。
* 如果形态不兼容、worker 已关闭， `fresh: true` 传入了，或者不存在会话，则 harness 会创建一个新的持久会话和 worker 线程。

子运行本身仍使用相同的 runner：

1. 从 task-local 读取父级的执行上下文——父级的 provider、sandbox 模式、取消栅栏、transcript 根目录。
2. 解析子代理的模型 - 内联 `模型` 覆盖优先，然后是配置级固定项（`[orchestrator].model`, `[teams.*].lead_model`, `[teams.*].agent_model`），然后是 archetype 提示或继承的父模型。
3. 根据定义的 `tools`, `disallowed_tools`，以及 `skill_filter`。在 `fork` 模式下，父级的完整注册表会原样继承。
4. 构建一个狭窄的系统提示，省略定义要求移除的部分。
5. 使用与父级相同的机制运行内部工具调用循环。
6. 将子级历史和 worker 线程指针持久化到该持久 `subagent_session_id` ，以便后续轮次可以恢复或检查它。

`wait_subagent` 和 `steer_subagent` 接受持久的 `subagent_session_id` 或临时的 `task_id`；跨轮次更偏好持久 ID。 `list_subagents` 显示当前父线程可复用的子对象，以及 `close_subagent` 将某个工作器标记为不可复用，并在其仍在运行时取消它。内联阻塞通过 `blocking: true`显式启用；它不再是默认值。

合成的原型委派（`delegate_*`, `build_workflow`，以及其他 `delegate_name` 工具）遵循相同的契约：它们默认通过持久异步路径路由，返回一个 `[async_subagent_ref]` （带有 `subagent_session_id` + `task_id`）立即返回，完成结果随后通过 `background_completions`/`background_delivery`作为一条新的系统回合插入到父聊天中。当没有父代理回合或没有可投递的聊天线程（cron/CLI）时，或者当 `blocking: true` 被传入时，它们会自动回退到内联阻塞。跨轮次连续性来自三部分：每轮的 `[active_subagents]` 名册会将内存中的活动注册表与持久化的 `subagent_sessions` 存储合并（因此冷启动的编排器仍能看到之前的工作器）； `continue_subagent` 会在暂停检查点与持久化存储之间回退，用其持久化历史恢复一个空闲工作器；以及一条 `workflow_proposal` 在已完成子对象历史中找到的负载会作为父线程消息持久化（`extraMetadata.scope = "workflow_proposal"`），前端在加载线程时会将其重新水合为提案卡片。

### 生成层级与层级划分

并非每个代理都被允许生成任意其他代理。该测试框架建模了一个三层层级，以反映模型在成本 / 延迟 / 思考深度上的划分：

```
Primary     （可直接能力 — 带 `coding` 提示的 Master Agent）
  │
  ├─► Worker      ◄─── 快速路径：一次委派，由叶子执行工作
  │
  └─► Reasoning   （慢、深思考 — 例如带 `reasoning` 提示的规划器）
        │
        └─► Worker  ◄─── 深路径：推理拆解，工作器执行
```

每个 `AgentDefinition` 都带有一个 `agent_tier` 字段（`chat` / `reasoning` / `worker`，默认 `worker`）。契约如下：

| 层级          | 可生成                   | 不得生成                      | 典型成员                                                                               |
| ----------- | --------------------- | ------------------------- | ---------------------------------------------------------------------------------- |
| `chat`      | `reasoning`, `worker` | 另一个 `chat`                | `orchestrator`                                                                     |
| `reasoning` | `worker`              | 另一个 `reasoning`，任何 `chat` | `规划者` （当前是规范的那个）                                                                   |
| `worker`    | 无[^1]                 | 任何                        | researcher, code\_executor, critic, archivist, tool\_maker, integrations\_agent, … |

**规则的原因。**

* *聊天 → 聊天毫无意义。* 聊天层的存在是为了快速 UX。一个聊天代理再生成另一个聊天代理，只会把首字节时间翻倍并消耗 token，却没有带来任何新能力。
* *推理 → 推理会把深度撑爆。* 推理层的成本很高。推理代理链往往会重复拆解同一个问题，并产生失控的层级。
* *工作器 → 任何东西都会混合执行与编排。* 工作器是叶子节点，因此父级总是看到一个紧凑的结果，而不是嵌套委派的完整转录。

**强制执行。** 两个层面：

1. **加载时（静态）。** [`agents::loader::validate_tier_hierarchy`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent/agents/loader.rs) 会在合并后的注册表（内置项 + 工作区 TOML）上运行，并拒绝启动一个列出同层级或带子代理的工作器条目的注册表。内置原型在编译测试时检查；用户提供的 TOML 在工作区加载时检查。
2. **运行时深度门控（动态）。** 独立于层级，子代理运行器会将总生成链深度上限设为 `MAX_SPAWN_DEPTH = 3` ，通过一个在 `run_subagent`之间递增的任务局部计数器实现，并以 `SpawnDepthExceeded` 代理错误的形式暴露。这使得即使用户提供的 TOML 去掉了层级注解，也仍然无法递归超过三跳。

> **状态：** 加载时层级检查、 `agent_tier` 字段以及运行时深度计数器任务局部都已启用。深度同时受静态加载契约和运行时 `MAX_SPAWN_DEPTH = 3` 守卫限制。

### 工具包特定专家

对于拥有数百个动作的 Composio 工具包（仅 GitHub 就有 500+），把每个动作都加载进子代理的工具集会让提示规模膨胀。测试框架使用一个廉价的仅 CPU 过滤器（动词检测、token 重叠、动词对齐加分）将工具包动作与父级细化后的任务提示进行排序，并且只将排名靠前的子集加载到子代理中。不调用模型，纯启发式——快速且可解释。

## 语言工作流（Rhai）

固定的委派原语（`spawn_subagent`, `spawn_parallel_agents`, `run_workflow`）无法表达 *即席控制流* ——“生成 N 个读取器，去重它们的发现，用 3 个反驳者验证每个幸存者，循环直到干净” 。 **`rhai_workflows` 工具** 弥补了这一缺口：它暴露了 TinyAgents 受 Rhai 支持的 `.ragsh` REPL（ `repl` cargo 功能）因此编排器可以编写并运行自己的工作流脚本。

**一次工具调用 = 一次 `eval_cell`.** 编排器的常规工具调用循环 *是* CodeAct 驱动循环：模型写入一个 Rhai 单元格，该单元格在持久化的每会话命名空间中运行（顶层 `let` 绑定会通过可选的 `session_id`保留到下一个单元格中），结构化结果则作为工具结果返回。脚本只能通过能力函数访问宿主—— `tool_call`, `agent_query`, `model_query`，它们的 `*_batched` 扇出变体， `emit`，以及 `answer`.

该领域位于 [`src/openhuman/flows/rhai/`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/flows/rhai/README.md):

* **`policy.rs`** 将自主层级 + `tool_timeout` 约束到一个 `tinyagents::ReplPolicy` （始终受限，绝不无界； `readonly` 会被拒绝； `完全` 可将调用次数限制提高到硬性 2× 上限）。
* **`bridge.rs`** 构建 `CapabilityRegistry`：父级可见的工具（每个都重新包装，以便 **审批门在 bridge 中运行** ——它 *不* 位于 repl 路径上，而该路径绕过测试框架 `wrap_tool` 中间件），本轮的提供方模型，以及每个 `allowed_subagent_ids`的子代理能力。递归/重复风险（`rhai`、旧版 `rlm`, `spawn_*`、工作流工具、 `CliRpcOnly`作用域工具）被排除。因为 `eval_cell` 运行在 `spawn_blocking` + `block_on`、 `agent_query` 适配器重新安装了 `PARENT_CONTEXT` 任务局部， `run_subagent` 从而解析。
* **`sessions.rs`** 是一个有界的（LRU + 空闲 TTL）持久会话管理器，一次处理一个单元格（对忙碌会话的并发调用会返回一个类型化的“忙碌”错误）。
* **`ops.rs`** 在 `spawn_blocking` 下运行该单元格，采用分层时间边界（rhai `on_progress` deadline → `bridge_block_on` timer race → outer `tokio::timeout` 兜底 → 测试框架 `ToolTimeout`），将运行取消令牌连接到一个新的每单元格 `ReplCancelFlag`，并将每一种失败模式映射为模型可消费的结果。

该工具仅在 `受监督的`/`完全` 层级上为编排器注册，位于 `OPENHUMAN_RHAI_WORKFLOWS=0` 熔断开关之后； `OPENHUMAN_RHAI=0` 和 `OPENHUMAN_RLM=0` 仍然是旧别名。TinyAgents 侧的宿主嵌入支持（外部取消、实时能力事件）已登陆该 crate 的 `repl` 功能。

## 分流 - 处理外部触发

当 webhook 触发、cron 计时或 Composio 事件到达时，系统不能直接把它交给编排器。大多数触发只是噪声；有些值得通知；只有少数应当获得完整的代理回合。 **触发分流管线** 就是这个门槛。

```
TriggerEnvelope ──► run_triage ──► TriageDecision ──► apply_decision
                       │                                     │
                       │                                     ├─► 丢弃（噪声）
                       │                                     ├─► 仅通知
                       │                                     ├─► 生成 trigger_reactor
                       │                                     └─► 生成编排器
                       │
                       └── 小型本地 LLM（带云端 LLM 重试回退）
```

评估器有意设计得很便宜——优先使用可用的小型本地模型，在重试时回退到远程模型。决策会被缓存，因此相同的触发不会重新分类。只有升级到“生成编排器”的触发才会进入完整的 `Agent::turn` 机制。

## 钩子 - 可观测性与策略控制杠杆

两个钩子面分别包裹循环，位于相反两端：

### 停止钩子（回合中）

停止钩子会在 **之间** 工具调用循环的各次迭代之间触发。它们是预算上限、速率限制和自定义熔断开关的策略杠杆。内置钩子：

* **预算停止钩子** - 使用每次迭代的成本累加器限制回合的累计美元成本。
* **最大迭代次数停止钩子** - 从代理持久配置之外限制迭代次数。
* **动作预算策略** - `SecurityPolicy` 强制执行 `config.autonomy.max_actions_per_hour` 针对产生副作用的工具操作。用户可以在 Settings -> Advanced -> Agent autonomy 中调整，或者操作员可以通过 `OPENHUMAN_MAX_ACTIONS_PER_HOUR`.

返回 `Stop` 的钩子会以一个清晰的原因中止循环，调用方可以把该原因展示给用户。停止钩子与中断不同（下一节）：它们由策略驱动，而不是由用户驱动。

### 回合后钩子

回合后钩子会在 **之后** 回合完成后于后台触发。它们会拿到一个 `TurnContext` 快照——用户消息、助手回复、每次带参数与结果的工具调用、总墙钟时间、迭代次数、会话 ID。内置消费者：

* **Archivist** - 提炼本回合中哪些事实值得持久化到长期记忆。
* **Learning** - 为反思、工具跟踪器和用户画像更新提供数据。
* **成本日志** - 最终的每回合成本行。
* **情景记忆索引** - 将该回合写入 [Memory Tree](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md) 作为未来召回的一个块。

钩子通过 `tokio::spawn`运行，因此用户会在它们完成之前先拿到答案。

## 中断 - 优雅取消

取消是 tinyagents 的 **steering channel**。旧的内部 `InterruptFence` (`harness/interrupt.rs`）已经移除；当用户按下 Ctrl+C 或发送 `/stop`时，运行器会把请求转发到测试框架的 steering/cancellation 接缝处，并在与旧 fence 保护相同的安全点停止循环——每次工具执行前、每次子代理生成前、每次提供方调用前：

* 每个正在运行的子代理都会共享取消作用域，并在下一个检查点退出。
* 进行中的提供方流会被丢弃。
* Archivist 仍会带着现有的部分上下文触发，因此对话不会丢失。

中断由用户驱动；停止钩子由策略驱动。二者都会进入同一套测试框架的暂停/停止管线，但来自不同侧。

## 成本核算

每个提供方响应都带有一个 `UsageInfo` 块——输入 token、输出 token、缓存的输入 token，以及由 `charged_amount_usd` 填充的权威金额，来自 OpenHuman 后端。 `TurnCost` 会将一次回合中的所有提供方调用加总，以便测试框架可以：

* 通过进度通道发出每次迭代的成本遥测。
* 为预算停止钩子提供数据，以便失控的回合在循环中途自行终止。
* 记录准确的回合结束成本行。

当后端没有暴露 charged amount（旧版本、未通过它计费的提供方）时，一个小的按层级费率表会提供 token 费率的下限估计。只要可用，后端的直接成本始终优先。

## 分叉上下文 - 测试框架中的 KV 缓存复用

测试框架使用一个任务局部 `ParentExecutionContext` ，将父状态传递给子代理，而无需让每个函数签名都膨胀。相同的模式还携带当前沙箱模式、中断 fence 和停止钩子列表。继承父级提供方、模型和提示前缀的子代理可以 **共享父级的 KV 缓存前缀** 在推理后端上——比从头重新预填充便宜得多。

## 自愈式回顾

在主循环之上叠加了几个小型自适应系统：

* **负载摘要器断路器** - 一次会话中连续三次子代理失败会禁用摘要，回退到截断。
* **分流本地 vs 远程重试** - 先用本地 LLM；解析失败时回退到远程模型。
* **未知工具与错误参数恢复** - 中间件会把无效的模型工具调用改写为可恢复结果，而不是中止运行。

这些都不会改变循环的形状——它们只是让常见故障模式无需用户介入也能恢复。

## 在代码中查看的位置

测试框架外壳位于 `src/openhuman/agent/`，tinyagents 适配器接缝在 `src/openhuman/agent/tinyagents/` 中，原型定义在 `src/openhuman/agent/registry/`。其中的 README 列出了公开表面；最关键的文件（相对路径， `src/openhuman/agent/` 若未加前缀）如下： `src/openhuman/agent/` 文件 / 目录

| 其中内容                                     | - 上面描述的生命周期；通过                                                    |
| ---------------------------------------- | ----------------------------------------------------------------- |
| `harness/session/turn/core.rs`           | `Agent::turn` ../tinyagents/mod.rs `session/turn/graph.rs`.       |
| `路由到 tinyagents 运行器。`                    | `run_turn_via_tinyagents_shared` - 共享的 tinyagents 测试框架组装（活动循环）。   |
| `../tinyagents/middleware.rs`            | 命名的 OpenHuman 中间件栈（审批/安全、工具策略、恢复、预算、断路器）。                         |
| `harness/graph.rs`                       | 通道/CLI 总线转入 tinyagents 运行器的回合路径。                                  |
| `harness/subagent_runner/`               | `run_subagent`、历史重放、分叉模式、超大结果交接； `ops/graph.rs` 是其 tinyagents 路由。 |
| `agent_orchestration/subagent_sessions/` | 持久化的可复用子代理身份、兼容性匹配、已持久化的状态/历史。                                    |
| `harness/definition.rs`                  | `AgentDefinition` - 原型声明了什么。                                      |
| `harness/tool_filter.rs`                 | 用于 integrations 子代理的工具包动作排名。                                      |
| `../tinyagents/payload_summarizer.rs`    | 超大工具结果分流。                                                         |
| `harness/engine/`                        | 保留的 OpenHuman 接缝： `CheckpointStrategy`, `TurnProgress`.           |
| `dispatcher.rs`                          | 工具调用方言抽象（持久化转录兼容性）。                                               |
| `triage/`                                | 外部触发分类 + 升级。                                                      |
| `../agent_registry/agents/`              | 内置原型——每个代理一个子目录。                                                  |
| `hooks.rs` / `stop_hooks.rs`             | 后轮次和轮中钩子接口。                                                       |
| `cost.rs`                                | 按轮次计算的 USD/令牌记账。                                                  |
| `progress.rs`                            | 面向 UI 的实时进度事件。                                                    |
| `memory_loader.rs`                       | 为每条用户消息注入 Memory-Tree 上下文。                                        |

## 代理状态图（`agent_graph`）：历史设计（已移除）

> **⚠️ 本节描述的是一个从未发布且已被移除的设计。** 定制的 `src/openhuman/agent_graph/` 引擎， `GraphBlueprint`以及 `SqliteCheckpointer` 如下所述 **并不存在**。当前线上系统运行在已发布的 **tinyagents** crate 上；请参见本页顶部的状态横幅以及下方的“TinyAgents 上的代理引擎 + 编排（线上）”。图由 `tinyagents::graph::GraphBuilder` (`agent_orchestration/*/graph.rs`, `tinyagents/delegation.rs`构建），持久化检查点使用 `SqlRunLedgerCheckpointer`，每个代理的图选择由 `AgentGraph` (`agent/harness/agent_graph.rs`）与每个代理的 `agent_registry/agents/<id>/graph.rs`。下文内容仅作为迁移前的设计历史保留。

除了线性的工具调用循环之外，harness 还提供了一个 **类 LangGraph 的状态机引擎** ，位于 [`src/openhuman/agent_graph/`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/agent_graph/README.md) （issue #4249）。在循环是一个隐式的“prompt → tool → result → next prompt”往复时，图将代理执行建模为一个显式的有向图，由 **节点** （状态）和 **边** （迁移）组成，并带有可在迁移之间保留、支持并行分支与检查点的类型化工作状态。

```
StateGraph::new(name)
  .add_node(id, node)            // 一个工作单元：async fn(State) -> (State, Command)
  .add_edge(from, to)            // 静态迁移
  .add_conditional_edges(...)    // 通过检查状态进行路由
  .add_fork(from, [a, b])        // 并行分流；通过 State::merge 合并
  .set_entry_point(id) / .set_finish_point(id)
  .compile()? -> CompiledGraph   // 验证通过；.invoke(state) / .resume_with(...)
```

| 子目录              | 作用                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `graph/`         | 引擎： `GraphState` （合并归约器）， `Node` trait、builder 以及 `compile()` 验证、Pregel 超步 `执行器` ，带循环/取消/步数上限守卫， `invoke`/`resume`.                                                                                                                                                                                                                                                             |
| `checkpoint/`    | `Checkpointer` trait（类型擦除的 JSON 状态）→ `InMemoryCheckpointer` （测试）+ `SqliteCheckpointer` 位于 `{workspace}/.openhuman/agent_graph/checkpoints.db`。支持持久化暂停/恢复。                                                                                                                                                                                                                       |
| `hitl/`          | 人在回路中： `审批`/`澄清` 中断构建器 + `ApplyResume` （在恢复时将人类的回答折叠进状态中）。节点返回 `Command::Interrupt` 以暂停。                                                                                                                                                                                                                                                                                        |
| `observability/` | `EventBusSink` （一个 `ProgressSink`）发出 `跟踪` 跨度并发布 `GraphRun*`/`GraphNode*` `DomainEvent` 家族（新的 `agent_graph` 事件域）。                                                                                                                                                                                                                                                                |
| `summarization/` | 对 `context::summarize_chat_history`.                                                                                                                                                                                                                                                                                                                                            |
| `memory/`        | 节点前包装器，基于 `DefaultMemoryLoader::load_context`.                                                                                                                                                                                                                                                                                                                                  |
| `definitions/`   | 围绕共享的 `ProductState`: `canonical_turn` （把代理轮次作为一个 `dispatch → parse → stop_check → tools → compact → loop / finalize` 图）以及 `plan_execute_review` （将 `规划者` + `code_executor` 诸多原型围绕一个 HITL 审查门进行组合），另有一个用于测试的确定性 `demo_review` 孪生体。一个注册表（`list_definitions`/`build_definition`) + `runner` (`run_graph`/`resume_graph`）将运行持久化到检查点器并发出总线事件。                                       |
| `blueprint/`     | 每个代理的链类型。每个内置代理都在一个 `graph.rs` 旁边声明其与 LangGraph 兼容的链，位于 `prompt.rs` (`pub fn graph() -> GraphBlueprint`中），并接入 `BuiltinAgent.graph_fn`. `GraphBlueprint` 可序列化（类型化 `NodeKind`/`EdgeSpec`），结构经过验证，并 `compile()`映射到真实的 `CompiledGraph`。可复用的形态： `canonical_turn` （大多数代理）， `single_shot`, `orchestrator`, `plan_execute_review`。可通过 `openhuman.agent_graph_{agent_list,agent_graph}`. |

### 每个代理的图（`graph.rs`)

位于 `src/openhuman/agent/registry/agents/<name>/` （以及那四个生活在各自域中的代理）现在都包含，连同 `agent.toml` + `prompt.rs`:

* **`graph.rs`**: `pub fn graph() -> GraphBlueprint`. `prompt.rs` 定义了代理 *说*; `graph.rs` 定义了它如何 *运行*，也就是其节点/边链。加载器测试会断言 **每一个** 内置代理的链都能验证并编译，因此畸形的链会让 CI 失败。

大多数代理复用 `blueprint::canonical_turn(id)` （标准工具调用循环）；一次性代理使用 `single_shot`，编排器使用委托链，而规划器使用 `plan_execute_review`.

**RPC 接口** (`schemas.rs` + `ops.rs`，注册在 `src/core/all.rs`): `openhuman.agent_graph_definition_list`, `_run`, `_run_list`, `_run_get`, `_checkpoint_list`, `_resume`.

> **状态（issue #4249，已被已发布的 `tinyagents` crate 取代）：** 内部的 `agent_graph` 本节所述引擎 **已不复存在**。openhuman 的代理引擎 + 编排现在运行在已发布的 [`tinyagents`](https://crates.io/crates/tinyagents) **2.1** crate 上（同一个类 LangGraph 的 harness + 持久化图运行时），通过 `src/openhuman/agent/tinyagents/`中的适配器缝隙接入。上面的章节作为设计历史保留；下面的小节描述的是当前线上架构。

## TinyAgents 上的代理引擎 + 编排（线上）

每次代理轮次（通过 `harness/session/turn/core.rs`进行聊天，通过 `harness/graph.rs`进行频道/CLI，通过 `harness/subagent_runner/ops/graph.rs`进行子代理）都会经由 `crate::openhuman::agent::tinyagents::run_turn_via_tinyagents_shared`，它运行 crate 的 `AgentHarness`。不再保留内部的轮次引擎、工具循环或路由门；调度是无条件的。接口：

| 文件（`src/openhuman/agent/tinyagents/`)             | 作用                                                                                                                                                                                    |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mod.rs`                                          | 运行器（`run_turn_via_tinyagents_shared`模型 `ChatModel`、宿主工具适配器和中间件安装到一个 `AgentHarness`运行框架中；执行一轮；通过 `MaxTokenModel`；镜像进度；转发 steering；并在模型调用上限处优雅暂停。                                       |
| `mod.rs` / `model.rs` / `tools.rs` / `convert.rs` | `RunPolicy` / `ChatModel` / `Tool` / 消息适配器（包括未知工具策略和带外推理转发）。                                                                                                                          |
| `observability.rs`                                | Harness `AgentEvent` → `AgentProgress`  + cost； `GraphTracingSink` 用于图事件。                                                                                                             |
| `orchestration.rs`                                | 重新导出的 `graph::orchestration` 任务存储类型；map-reduce 分流现在直接使用 TinyAgents SDK 接口。                                                                                                            |
| `checkpoint.rs`                                   | `SqlRunLedgerCheckpointer`：一个 `Checkpointer` 基于 openhuman 的 SQLite（`graph_checkpoints` 表）的适配器。TinyAgents 2.1 已提供 `SqliteCheckpointer`；OpenHuman 会保留此适配器，直到现有检查点行迁移或过期且 schema 归属问题解决。 |
| `delegation.rs`                                   | 持久化的 `plan → execute ⇄ review → finalize` 委托图（生产 worker 接入于 `agent_orchestration::delegation`).                                                                                       |

**图上的编排** (`src/openhuman/agent/orchestration/`):

* **工作流阶段 DAG** (`workflow_runs/engine.rs`）运行于一个 `dispatch ⇄ run_phase → done` 条件路由图；每个阶段通过 `graph::parallel::map_reduce`对其代理进行分流。持久化的 `workflow_runs` 行仍是事实来源（控制器 + 恢复会读取它）。
* **团队成员运行时** (`agent_teams/graph.rs`）是一个条件路由图（`execute → complete|fail → done`).
* **多阶段委托** (`agent_orchestration::delegation` + `delegate` 工具）运行 `delegation.rs`，并持久化到会话数据库。
* **分离的子代理** (`running_subagents.rs`）使用 TinyAgents `DetachedTaskRegistry` 来实现具备所有权感知的快照、等待/超时、steering 查找、协作式取消、强制中止和终态清理。OpenHuman 保留持久化的任务存储投影、产品/会话元数据、RPC 和交付语义，以及 `RunQueue` 兼容回退。

**刻意不依赖 crate 的原语** （记录在案的工程决策，而非缺口）：

* **子代理构建流水线** (`subagent_runner/`）仍由 openhuman 负责：定义解析、原型工具过滤、提供者解析、窄化 prompt 构建、内存上下文、工作线程镜像、交接缓存、检查点/恢复。子代理已经在 *执行* 在 harness 上；crate 的通用 `SubAgentTool` 会为了细微的 crate 原生深度跟踪而丢弃这套流水线（openhuman 的 `spawn_depth_context` 已经限定了递归）。
* **持久化运行账本** (`workflow_runs`, `agent_teams`, `command_center`, `subagent_sessions`）在 openhuman SQLite/JSON 上保留，直到它们的控制器投影与重启语义映射到 TinyAgents 的 task/status/journal 记录。 `agent_teams` 用于规避竞态的 SQL compare-and-swap 任务认领仍由 OpenHuman 负责。

> **注：** TinyAgents 2.1 提供了 harness store/cache/session 原语（`harness::store` ，带 JSONL 追加存储， `harness::cache`, `harness::subagent`，具 lineage 感知的状态）、图任务存储、分离运行时注册表以及一致性契约。会话外壳和产品特定的子代理构建/交付流水线仍由 OpenHuman 负责。

## 可靠性：断路器、回传和分类失败

三种协同机制可防止运行走偏或悄无声息地死亡：

**无进展断路器** (`RepeatedToolFailureMiddleware`, `src/openhuman/agent/tinyagents/middleware.rs`）是 crate 的 `NoProgressTracker`的薄封装驱动。它会为每次工具调用的参数打指纹，并将结果送入升级阶梯： `继续` → `轻推` （通过 `SteeringCommand::InjectMessage`注入的一种结构化“自步骤 X 以来无进展”纠正，在交互式轮次中是安全的）→ `停止` （将根因摘要记录到 `HaltSummarySlot`中，并通过 steering 句柄暂停）。相同参数的重试会计入触发条件（连续 3 次相同失败）； *可恢复的* 失败（超时、连接重置、速率限制、5xx）会获得扩展的余量阶梯，而不是固定的 crate 阈值。

**子代理回传** (`subagent_runner/ops/runner.rs`）：子代理运行会归于三种状态之一：

* `已完成`：干净的最终响应。
* `AwaitingUser { question, options }`：子级调用了 `ask_user_clarification`；完整检查点（历史、问题、选项、覆盖项）会写入 `{workspace}/.openhuman/subagent_checkpoints/{task_id}.json`，并在用户回答后从该处恢复运行。
* `Incomplete { reason }`：子级被断路器停止或达到了模型调用上限。委托父级 **会转发阻塞原因** ，而不是把已停止的子级当作已完成答案，或重新发起相同的委托。

顶层的断路器停止同样绝不会静默结束：该轮次的最终文本会被断路器的根因摘要覆盖，并且 `hit_cap` / `breaker_halt` 会在轮次结果中暴露。

**分类工具失败** (`src/openhuman/tools/status/`）：每次失败的工具调用都会被分类为一种与传输无关的 `ClassifiedFailure { class, category, cause_plain, next_action, recoverable }`。类别包括 `MissingPermission`, `MissingApp`, `ServiceUnavailable`, `BadCredentials`, `BlockedByPolicy`, `ModelConnection`, `Timeout`, `Denied`, `ApprovalExpired`；类别一一映射到 UI 状态： *可恢复的* （安全自动重试）， *受策略阻止* （更改设置）， *需要用户确认* （登录 / 安装 / 授权）， *用户拒绝* （从未自动重试）。分类依附于 `AgentProgress::ToolCallCompleted.failure` （包括子代理调用）到聊天时间线中。

## 日志、回放和迁移影子

每次运行都会追加到一个持久的 **事件日志** (`tinyagents/journal.rs`）：一个 `StoreEventJournal` 基于位于以下位置的 JSONL 追加存储： `{workspace}/tinyagents_store/journal`，由以下组成 `FanOutSink` （实时桥接 + 日志）→ `RedactingSink` （持久化前进行凭据掩码），并使用重启稳定的事件 ID（`{run_id}-evt-{offset}`）。即使是未被观察到的后台轮次，事后也可重建。三个只读 RPC 将其暴露出来： `agent_run_events` （分页式、延迟接入回放，按 `run_id`/`offset`/`limit`), `agent_run_status` （最新的 harness 状态），以及 `agent_runs_active` （活动运行，可按线程或根运行筛选）。

其余存储切换运行在 **影子脚手架** （产品行为不变；差异会被记录）：

* **会话双写 / 影子读取** (`session/turn/session_io.rs`）：会话消息双写到 TinyAgents 存储中（默认开启标志 `config.session_dual_write`）；为保持一致性会加载影子读取，而旧文件存储仍是权威来源。
* **任务板影子** (`todos/graph_shadow.rs`）：将看板镜像到 crate `graph.todos` `TaskBoard` 并对其进行影子运行 `claim_card` CAS。
* **目标影子** (`thread_goals/crate_adapter.rs`）：忠实复制到 crate 中 `graph.goals` KV 存储，以线程 ID 为键。

## 工作负载路由与 burst 层级

`tinyagents/routes.rs` 是声明式的 TinyAgents `ModelRouter` 用于 OpenHuman 各层级 `chat`, `reasoning`, `代理式`, `编码`, `突发`, `摘要`，以及 `视觉`。它负责管理回退链和能力门控； `inference::provider::factory` 将每个所选层级解析为其配置的原生 `ChatModel`。 **`burst-v1`** 该层级为低上下文、高扇出的工作者提供快速/低成本模型。

## 另请参见

* [架构概览](/openhuman/zh/kai-fa/architecture.md) —— harness 在整体架构中的位置。
* [Memory Tree](/openhuman/zh/gong-neng/obsidian-wiki/memory-tree.md) —— memory loader 从哪里读取，以及轮次后钩子写入到哪里。
* [自动模型路由](/openhuman/zh/gong-neng/model-routing.md) —— 如何 `model: "hint:reasoning"` 解析为具体的 provider+model。
* [原生工具 - 代理协调](/openhuman/zh/gong-neng/native-tools/agent-coordination.md) —— 面向用户的接口，用于 `spawn_subagent`, `delegate_*`, `todo_write`.

[^1]: 技能通配条目（`{ skills = "*" }`）被豁免，因为它们会收敛为单个 `delegate_to_integrations_agent` 目标是工作器的工具；它们是扇出式委派表面，而不是递归生成。
