call() 究竟经历了什么,以及状态如何在系统中流动。
1. 设计理念
理解 harness 架构需要先理解三个核心决策。决策一:薄包装,不替换推理循环
HarnessAgent 不是一个新的推理引擎——它只是 ReActAgent 的薄包装,自身只做两件额外的事:
bindRuntimeContext(ctx):每次call()开头,把当次身份(sessionId、userId)分发给关心它的 hook,并按需从 Session 恢复 Memory 状态;forceCompactAndRetry:当模型真的返回 ContextOverflow 错误时,强制压缩并重试一次。
决策二:Hook 驱动,能力正交
每个 hook 只做一件事,通过priority 在同一个事件上排好执行顺序:
CompactionHook(10)在推理前检查是否需要压缩历史;SubagentsHook(80)在推理前注入子 agent 列表;WorkspaceContextHook(900)最后叠加工作区文件——因为它是最终拼进 system prompt 的一层,需要在所有前置处理完成后才运行。
compaction 需显式配置,session persistence 默认开启,toolResultEviction 按需启用。
决策三:共享对象是唯一耦合点
所有 hook 都通过同一组”通用语言”协作:2. 顶层架构图
三层职责一眼看清:- 薄包装层(HarnessAgent):负责 per-call 的身份绑定与极端情况兜底;
- 推理内核(ReActAgent):负责 Hook 事件驱动 + ReAct 循环 + 工具执行;
- 共享对象层:三个对象是所有 Hook 的协作底座,不属于任何 Hook,被所有 Hook 读写。
3. 构建阶段(Builder.build())
能力注入发生在一次性的构建阶段,构建完成后运行期不再改变 hook 链或 toolkit 组成:
✗可选 的 hook 只在满足条件时装配:CompactionHook需调用.compaction(...);SandboxLifecycleHook需filesystem(SandboxFilesystemSpec);ToolResultEvictionHook需.toolResultEviction(...)。
4. Hook 事件管道
ReActAgent 在 ReAct 循环的各个关键节点触发事件;Hook 在对应事件上按 priority 升序执行。下表是完整的 Hook × 事件矩阵:
priority 的排布体现了设计意图:
- 0:纯日志,最先运行,不干扰任何事件;
- 5/6/10:记忆与压缩,在推理循环外围处理上下文生命周期;
- 50:沙箱生命周期与工具结果卸载,在 acting 阶段就地处理;
- 80:子 agent 注入,先于工作区注入——因为子 agent 信息需要出现在 system prompt 里;
- 900:最后写 system prompt(WorkspaceContextHook)和持久化(SessionPersistenceHook)——保证它们叠加在所有前置处理之上,且记忆先 flush 再 snapshot。
5. call() 生命周期时序
6. 状态流转
状态在 harness 里有三个层次,从短到长: 核心规律:Memory是调用内的”工作内存”,随call()结束通过两条路持久化;WorkspaceSession保证”下次同 sessionId 还记得这一轮”;MEMORY.md+ FTS 索引保证”长期事实不随 session 丢失”。