Harness 设计¶
Pi 有两个相关概念:低层 Agent/agentLoop,以及更高层的 harness/session runtime。packages/agent/src/harness/agent-harness.ts 提供了通用 harness;packages/coding-agent/src/core/agent-session.ts 则是 coding-agent 产品层的 harness 实现。
本章涉及的具体类型、hook 与 save point 行号见源码索引。
为什么需要 Harness¶
低层 loop 只会执行消息和工具,但真实产品还需要:
- 加载系统提示、skills、prompt templates、项目说明。
- 选择模型、thinking level、transport、请求 header。
- 保存消息、模型切换、工具切换、session name 等状态。
- 提供扩展 hook。
- 管理 steering/follow-up/nextTurn 队列。
- 在 provider 请求前后插入逻辑。
- 做 compaction、branch summary、retry。
Harness 就是这些“运行环境语义”的承载层。
AgentHarness 的关键设计¶
源码:packages/agent/src/harness/agent-harness.ts
AgentHarness 维护一份 turn state:
interface AgentHarnessTurnState {
messages: AgentMessage[];
resources: AgentHarnessResources;
streamOptions: AgentHarnessStreamOptions;
sessionId: string;
systemPrompt: string;
model: Model<any>;
thinkingLevel: ThinkingLevel;
tools: TTool[];
activeTools: TTool[];
}
每个 turn 开始时都会创建快照。好处是:
- 扩展或应用可以在 turn 间修改模型、工具、thinking level。
- 当前 provider 请求使用稳定配置,避免运行中被外部突变影响。
prepareNextTurn后重新读取 session context 和配置,支持工具调用后状态变化。
Hook 边界¶
AgentHarness.createLoopConfig() 把 harness hook 映射到底层 loop:
| Loop hook | Harness 事件 | 用途 |
|---|---|---|
transformContext |
context |
修改即将发给模型的消息 |
beforeToolCall |
tool_call |
阻止或审计工具调用 |
afterToolCall |
tool_result |
改写工具结果 |
prepareNextTurn |
flush session writes + create turn state | 工具结果后刷新上下文/模型 |
getSteeringMessages |
steer queue | 工作中插入消息 |
getFollowUpMessages |
follow-up queue | 工作结束后续问 |
这个映射让低层 loop 保持通用,又允许上层接入复杂策略。
Session 写入策略¶
AgentHarness 有一个 pendingSessionWrites 队列。它不会让扩展随意直接改低层 context,而是把写操作排队,在 save point 时 flush:
messagemodel_changethinking_level_changeactive_tools_changecustomcustom_messagelabelsession_infoleaf
保存点主要在 turn_end 和 agent_end。这让 session 的持久化顺序和 agent 事件顺序一致,也避免 hook 中途写入破坏当前 turn 的上下文。
Provider 请求扩展点¶
createStreamFn() 在 provider 请求前会:
- 从
getApiKeyAndHeaders获取认证信息。 - 合并 stream options 与 auth headers。
- 触发
before_provider_request,允许 patch headers、metadata、timeout、transport、cache retention。 - 调用
streamSimple。 - 在 response 后发出
after_provider_response。
这让诸如自定义 provider header、审计日志、代理、缓存 affinity、实验参数等能力可以在 harness 层完成,而不用侵入 provider 实现。
Coding Agent 的 Harness 化¶
AgentSession 是面向 coding agent 的具体 harness。它比通用 AgentHarness 更贴近产品:
- prompt 前处理扩展命令、input hook、skill/template 展开。
- 管理内置工具和扩展工具。
- 在
message_end写入SessionManager。 - 将
AgentEvent翻译成 extension event。 - 自动重试和上下文压缩。
- 提供
sendCustomMessage()、sendUserMessage()、steer()、followUp()等 API。
换句话说,AgentSession 把 agent-core 的通用能力绑定到 Pi 的 CLI/SDK/RPC 语义上。
错误处理¶
通用 harness 会把不同子系统错误归类成稳定 code:
sessioncompactionbranch_summaryhookauthbusyinvalid_stateinvalid_argument
这比直接抛原始异常更适合 SDK/RPC,因为调用方可以按 code 做 UI 提示或恢复策略。
设计启发¶
- Harness 是 agent runtime 与产品体验之间的隔离层。
- 每个 turn 使用配置快照,能避免异步突变造成难以复现的问题。
- Hook 应该有明确的合并规则、短路规则和错误策略。
- 会话写入最好经过统一队列和 save point,而不是在任意 hook 中直接落盘。