综合
把前面的知识串起来:一个完整系统的设计,一个对照 pi 的动手项目,一份速记清单。
综合设计:企业内部的 Coding Agent 平台
场景:为公司 5000 名研发提供 coding agent 平台,在 Web 和 IDE 里提交任务(修 bug、写测试、代码审查),agent 在隔离环境里改代码并提交 MR。按“需求 → 架构 → 核心流程 → 深挖”展开。
基础需求与估算
- 功能:提交任务、实时进度、中途插话或中止、审批敏感操作、产出 MR、查看历史。
- 规模:高峰 10% 同时在用,约 500 个并发任务;单任务 5–30 分钟、20–80 个 turn。
- 非功能:代码不外泄、任务互相隔离、成本可控、可审计、厂商故障可降级。
进阶架构
- 提交任务 → 任务服务写会话、入队。
- Runtime worker 领取任务(带租约),从预热池拿沙箱,拉代码。
- 循环:经 LLM 网关请求模型 → 工具调用发到沙箱执行 → 每步追加事件日志并发布到事件总线 → 推送服务转给前端。
- 敏感操作触发审批:任务持久化后挂起,释放 worker;沙箱按等待时长决定保留还是先快照再释放(见下表)。
- 完成后在沙箱跑测试,通过则提交 MR,释放沙箱。
深入值得深挖的点
| 问题 | 思路 | 相关章节 |
|---|---|---|
| worker 挂了 | 租约过期由其他 worker 接手,重放事件日志;悬空调用按幂等规则处理 | 10 |
| 等审批占资源吗 | 不必占 worker:持久化后释放,审批结果作为事件重新入队。但沙箱、磁盘、进程、租约仍要管:短等待保留沙箱(设租约和过期时间);长等待先把工作区快照(未提交的修改打成 patch 或推到内部 WIP 分支,记下镜像和依赖版本),再释放沙箱,恢复时新建沙箱并应用快照;后台进程(如启动的测试服务)不能跨快照保留,恢复后要重新拉起。审批超时自动拒绝并通知 | 10、13 |
| 沙箱冷启动慢 | 预热池;按语言 / 仓库准备基础镜像;依赖缓存 | — |
| 代码和凭据安全 | 沙箱默认断网只放行制品库;凭据不进沙箱,由执行层代理;全量审计 | 13 |
| 成本 | 按用户 / 团队预算;前缀缓存 + 粘性路由;小模型做摘要;缓存预热按期望收益决策 | 6 |
| 厂商故障 | 网关熔断降级;跨厂商切换时处理 thinking 块;降级组合提前评测 | 3、14 |
| 并发写同一文件 | 沙箱内按文件串行;同一会话单写者 | 7、10 |
| 前端断线 | 事件带序号,重连时从事件日志补发 | 11 |
| 效果怎么衡量 | MR 合并率、人工修改量、测试通过率;离线评测回归 | 15 |
| 可扩展性 | 团队通过插件注册内部工具和审批规则,核心不改;插件钩子失败即拦截 | 12 |
| review 跟不上 | agent 产出的 MR 远多于人能审的量:合并前自动跑测试、类型检查、LLM 审查;按风险分级,低风险改动轻审,核心模块重审 | 9、15 |
| 内部系统怎么接 | 平台侧提供远程 MCP 网关(统一鉴权、审计);常用流程写成 skill 发布到团队仓库 | 8、16 |
| 长任务跨会话 | 任务服务保存功能清单和进度文件,worker 换了也能从 git 和进度文件接着干 | 9 |
- 已有证据这套“提交任务 → 云端沙箱 → 产出 PR”的形态已是主流产品形态:Codex 云端任务、GitHub Copilot coding agent(均为 2025-05)、Claude Code 网页版(2025-10)都是这样。
- 作者判断平台的瓶颈从“生成”转向“验证和 review”。设计时把自动验证和审查流程当作一等组件。
- 作者判断工程师的角色转向写需求、设计验证环境、审查结果(OpenAI 的 harness engineering 实验就是这个方向)。平台要支持一个人同时派出、追踪、验收多个 agent。
- 作者判断这张架构图里,“Agent Runtime”这一格的代码会变少(脚手架变薄),而“工具执行层”“会话存储”“离线评测”这几格会越来越重。
动手项目:用 Java 实现一个 mini harness
每一步都对照 pi 的对应源码。做完之后,你对每个设计点都有亲手处理过的理解。测试从第 1 天就开始:先写一个脚本化的假模型(第 15 章),之后每加一个功能,就补上对应的单测(截断不执行、异常不中断、恢复后不重复写……)。第 10–11 天只是在此基础上加真实模型的评测集。
最小 loop
HttpClient + Jackson 直接调一家模型 API,实现 read_file、run_command,跑通第 4 章的循环;同时写好脚本化假模型和第一个单测,并给每个任务写下成功标准。对照:ai/README.md Quick Start、agent-loop.ts。
护栏
最大轮数、参数校验、两阶段执行、超时杀进程树、头尾两种截断、危险命令拦截。对照:executeToolCallsParallel、bash.ts、truncate.ts。
edit 工具
实现 oldText → newText 替换,含模糊匹配、唯一性检查、多处修改、按文件加锁。对照:edit-diff.ts、file-mutation-queue.ts。
会话与压缩
PostgreSQL 只追加事件表,支持恢复和分支;token 估算 + 结构化摘要压缩,不在 toolResult 处切。对照:session-manager.ts、compaction.ts。
服务化
Spring Boot:提交任务、SSE 推送(带序号续传)、中止、steering;OpenTelemetry 埋点;记录缓存命中率。对照:docs/json.md、agent.ts。
评测
补齐边界单测(steering 位置、恢复);20 个任务的评测集,每个跑 3 次,每次运行前重置仓库和沙箱、固定模型和预算,算 pass@1、pass@3 和 pass^3。这个规模适合练手和发现明显问题,几个点的差异可能只是波动(第 15 章)。对照:fauxProvider。
插件与总结
用 SPI 实现工具注册和 tool_call 拦截(失败即拦截);写一篇“我的实现 vs pi”的设计取舍总结。
消融与长任务
换两个不同厂商的模型,逐个关掉 system prompt 里的提醒和额外工具跑评测,记录哪些脚手架对哪个模型有用(第 1、15 章);再给一个需要多次会话的任务加上功能清单 JSON + 进度文件 + git 提交的接力机制(第 9 章)。这两项最能说明你理解“harness 会怎么变”。
进阶总结模板(STAR)
背景:为了搞清楚 coding agent 的内部原理,用 Java 从零实现了一个 agent harness,并逐模块对照开源项目 pi 的源码。 任务:工具调用循环、edit 工具、会话持久化与恢复、上下文压缩、流式推送、安全拦截、评测。 行动: - 循环:两阶段执行(串行校验与审批、并行执行),超时杀进程树,截断响应不执行…… - edit:精确 + 模糊匹配、唯一性检查、按 realpath 加锁防丢失更新…… - 会话:只追加事件表 + 树结构,按 turn 边界恢复,悬空写操作用执行端认得的幂等键去重或先查外部状态…… - 上下文:结构化摘要、不在 toolResult 处切,前缀保持稳定,缓存命中率从 X% 到 Y%…… 结果:20 个任务 × 3 次,pass@1 = X%,pass^3 = Y%;改进工具错误信息后,有 N 个任务从失败变为稳定通过;每个合格任务的平均费用下降 W%。 反思:正则拦截不是安全边界;重试只应放在理解语义的那一层;……
数字一定要真实测出来,别人会顺着每一个数字追问。样本只有 20 个任务时,用“几个任务从失败变成通过”来描述改进,比报一个百分点的提升更诚实。
速记清单与自测
先记骨架
再记细节
规律:已练进模型的多是“替模型思考”的技巧;已成标配的多是协议和计费机制,讨论变少恰恰说明它们已经普及;已淘汰的多是太重的抽象或超前于模型能力的设想。
延伸阅读
- Anthropic:Building Effective Agents;Effective Context Engineering for AI Agents;Effective harnesses for long-running agents;Code execution with MCP
- OpenAI:Harness engineering: leveraging Codex in an agent-first world(2026-02)
- Manus:Context Engineering for AI Agents: Lessons from Building Manus(2025-07)
- MCP 2026-07-28 规范说明:blog.modelcontextprotocol.io;Agent Skills 规范:agentskills.io
- Mario Zechner(pi 作者):MCP vs CLI
- METR:Measuring AI Ability to Complete Long Tasks(2025);Chroma:Context Rot(2025)
- ReAct: Synergizing Reasoning and Acting in Language Models(2022)
- 后训练:InstructGPT(Training language models to follow instructions with human feedback,2022);Tülu 3(2024);DeepSeek-R1(2025)
- 检索:LightRAG(2024);Microsoft GraphRAG(2024)
- Simon Willison:The lethal trifecta for AI agents
- MCP 规范:modelcontextprotocol.io
- pi 源码:github.com/earendil-works/pi,重点文件见附录
pi 源码地图
pi 的代码在 earendil-works/pi(原 badlogic/pi-mono),TypeScript 编写。先建立地图,各章“pi 的做法”里的源码引用才能对上位置。本手册核对的是 commit cdaf6c2(2026-10-11)。文中的路径多是缩写,省略了 packages/、coding-agent/src/ 这类前缀,按下面的地图补全即可。建议现在就 clone 一份并切到这个版本:git clone https://github.com/earendil-works/pi && git -C pi checkout cdaf6c2。之后的版本可能改名或挪位置,对不上时以这个 commit 为准。
基础四层结构
pi:内置工具、AgentSession、会话存储、压缩、扩展、skill、四种运行模式Agent 与 agentLoop:循环、事件、队列、钩子。核心代码约 2500 行,最值得精读进阶一条消息的旅程
你在终端里敲下“修一下这个测试”并回车,代码依次经过这些地方:
- coding-agent/src/main.ts → modes/解析命令行,选择交互 / print / json / rpc 模式
- core/agent-session.ts · AgentSession.prompt()展开 prompt 模板、处理扩展命令、检查是否需要压缩,把用户消息交给 Agent
- core/system-prompt.ts按 section 拼出 system prompt:角色、工具清单、规则、AGENTS.md、skill 索引、cwd
- agent/src/agent.ts · Agent管理状态和 steering / follow-up 队列,调用 agentLoop
- agent/src/agent-loop.ts · runLoop()循环:transformContext → convertToLlm → 流式请求 → 执行工具 → 回填结果
- ai/ · models.stream()把统一格式转换成具体厂商的 HTTP 请求,把 SSE 响应转换回统一事件
- coding-agent/src/core/tools/*.tsread / bash / edit / write 的实际实现
- core/session-manager.ts每条消息追加写入 JSONL 会话文件
- core/extensions/沿途在各个事件点调用扩展的处理器(可以拦截工具、改写上下文)
深入读源码前的 TypeScript 速查
| Java | TypeScript / Node |
|---|---|
CompletableFuture | Promise + async / await;Promise.all ≈ allOf |
Iterator | AsyncIterable,for await (const ev of stream) 消费流式事件 |
sealed interface + switch | 可辨识联合:switch (event.type) |
| Bean Validation / JSON Schema | TypeBox:Type.Object({...}) 同时得到 TS 类型和 JSON Schema |
SPI / ServiceLoader | export default function (pi) {...} |
Thread.interrupt() | AbortSignal,层层传给工具 |
a != null ? a.b() : null | a?.b();x ?? [] 是默认值 |
| 单线程 + 事件循环 | Node 的 JavaScript 默认跑在单个主线程上,没有多线程抢同一段代码;但 await 之间仍会交错,所以 pi 照样需要“文件修改队列”(第 7 章)。用了 worker_threads 或多进程,还要另外处理并发 |