Agent 架构设计:工具调用、权限、记忆、压缩与 MCP 集成
🎯 学习目标
- 完整 Agent 的核心是功能数量还是边界一致?模型负责什么,Harness 必须拥有什么?
- P20 新增了业务模块吗?full_harness 是什么?
- 组合根如何检查对象身份?共享关系错误会怎样?
- 单一循环的真实执行顺序?
- AgentRunner.close() 如何关闭资源?什么顺序?
- 为什么 Prompt 提醒 ≠ 运行时硬边界?
🧠 核心概念
1 · 核心不是功能数量,而是边界一致 ▸
模型只负责提出下一步动作。Harness 必须拥有真正的控制权:
- ToolRegistry 决定当前能看到和调用哪些工具;
- PermissionPolicy 在 handler 前执行硬权限判断;
- HookRegistry 提供生命周期扩展,但不能放宽系统拒绝;
- MemorySession、CompactionManager、DynamicPromptProvider 控制上下文;
- RecoveryManager 处理 429/529/输出截断/输入过长;
- SQLite Task、Protocol、Mailbox、Worktree 保存可恢复状态;
- JobSupervisor、CronRuntime、TeammateRuntime、McpRuntime 持有长期资源;
- AgentRunner.close() 按逆序尝试关闭全部资源。
2 · P20 不新增业务模块,只接 full_harness ▸
full_harness 是整合验收标记,不对应新业务模块。它表示 P1-P19 全部 Capability 已在同一组合根中连接并通过交叉验证。
四处连接:(1)profiles.ts 末尾追加 full_harness 增量;(2)prompting.ts 末尾增加可选 runtime_status 段落,Provider 用 statusProvider 每轮重读;(3)bootstrap.ts 汇总 MCP 连接与后台 pending work,只给 P20 安装状态 Provider;(4)固定入口 ch20.ts 仍只 runProfile(P20, ...)。runtime_status 固定在提示词最后一段并参与缓存键——只改 status、其余不变时 cacheHits 加一,之前的正文不变;它是状态同步不是授权来源:模型看到 mcp_connections 不等于有权调用那个工具,权限事实源始终是本地 policy。
3 · 组合根检查对象身份 ▸
| 资源 | 必须共享的对象 | 原因 |
|---|---|---|
| Cron | JobSupervisor、EventInbox | 调度和后台完成事件进同一 typed inbox |
| Teammate | Supervisor、Cron、Inbox、Mailbox | 队友生命周期/消息/事件由同一 owner 管 |
| Protocol | Teammate、Mailbox | 请求状态与实际投递参与方必须一致 |
| Work stealing | SQLite Task store、Worktree claim service | 自动认领与手动认领用同一租约边界 |
| Worktree | SQLite Task store、Git runner | Task/claim/binding 独立但引用同一 Task |
| MCP | Lead 的 live ToolRegistry | 动态工具只进 Lead,下次请求可见 |
4 · 单一循环的真实执行顺序 ▸
所有能力都接到同一个 AgentRunner,执行顺序固定:
memory turn lifecycle (beginTurn 选择记忆)
-> render dynamic prompt (identity/tools/workspace/skills/memory/runtime_status)
-> recovery manager complete (429/529/截断/输入过长恢复)
-> prepare tool calls (JSON解析+Zod校验)
-> PreToolUse Hook
-> PermissionPolicy (硬边界+规则+审批)
-> dispatch (同步或后台)
-> PostToolUse Hook
-> 配对回填 tool result
-> tool result processor (大结果落盘)
-> history processor (snip/micro/摘要)
-> Stop Hook (最多续跑一次)
没有任何能力绕过这条链路。Cron 事件、Teammate 消息、后台 job 完成都通过 EventInbox 进入 event-only turn,走同一执行路径。事件去重两层:#seenEventIds 防进程内重投,store 里的 ack 防跨重启重放——至少一次投递 + eventId 去重,对模型表现成恰好一次。恢复矩阵(429 按 Retry-After、529×3 切 fallback、length 抬预算、过长反应式压缩)全部作用在冻结的 requestMessages 副本上,恢复过程一条都不进 canonical history。
5 · close() 逆序关闭资源 ▸
AgentRunner.close() 按逆序尝试关闭全部资源:MCP connections → stdio 子进程 → Teammate workers → Cron scheduler → JobSupervisor 后台 jobs → 持久化 stores。每个资源的 close 都是幂等且 try/catch 包裹的,一个资源关闭失败不影响其他资源关闭。
这保证进程退出时不留下孤儿子进程、悬挂连接或未刷盘的状态。CLI 入口在 run() 结束后统一调用 close(),把错误码映射到进程退出码。1 个资源关闭失败原样抛,多个失败组成 AggregateError 一次报全;#closed 只在全部成功时置位——失败后可重试 close(),但不能再 run()。
🏗 完整 Harness 架构总览
P1-P20 能力累进
🔑 一句话总结
19 章能力接到同一组合根,用跨功能场景证明边界同时成立。模型只提动作,Harness 拥有真正的控制权:工具/权限/Hook/上下文/恢复/状态/资源,close() 逆序回收。