coding-agent:harness 层
pi-coding-agent 是把 pi-ai、pi-agent-core、pi-tui 组装成产品的最顶层。它做的事情很杂——会话、压缩、扩展、技能、主题、认证、四种运行模式——但贯穿其中的设计思想只有一条:把 agent 的每一次呼吸都变成可观察、可持久、可替换的结构。本篇挑四个最能体现这条思想的子系统讲。
会话:单文件 JSONL 树
pi 的会话文件是 append-only 的 JSONL,每行一个带 type 的记录,记录之间用 id/parentId 串成一棵树(格式详见 docs/session-format.md):
树形结构让分叉成为免费属性:回到历史任意一点换个方向继续,不需要新文件,往同一文件追加一个指向旧 parentId 的记录就行。pi 的三个会话操作因此有非常清晰的语义:
/tree:可视化整棵树,跳到任意节点继续(就地切换分支);/fork:从当前分支上某条用户消息处,复制出一个新会话文件;/clone:把当前分支原样复制成新文件。
落盘的记录是完整的 AgentMessage(usage、stopReason、thinking、UI 专属消息都在),发给 LLM 时才经 convertToLlm 裁剪——上一篇讲的"边界转换"在这里落地:文件留住一切,模型只看到该看的。
上下文压缩:有损的是上下文,不是历史
长会话必撞上下文窗口,pi 的 compaction 策略(core/compaction/compaction.ts,docs/compaction.md):
- 触发:
contextTokens > contextWindow - reserveTokens(reserve 默认 16384,给回复留余量);溢出报错后恢复性重试;/compact手动。 - 下刀:从新消息往回累积,保留约
keepRecentTokens(默认 20k)的最近消息;切点只在 turn 边界,绝不在 toolResult 处切(它必须和自己的 toolCall 待在一起);单个巨型 turn 超预算时还有 split-turn 处理。 - 摘要:调 LLM 生成结构化摘要(目标、决定、动过的文件、当前状态、下一步),且把上一次摘要作为迭代上下文传入——第二次压缩记得第一次讲了什么。
- 落盘:摘要存为
CompactionEntry(含firstKeptEntryId),重建上下文 = 摘要 + 从firstKeptEntryId起的消息。被压缩的旧消息永远留在文件里,/tree随时可以回到压缩前的完整历史。
"有损的是喂给模型的上下文,不是事实记录"——这句话值得写进每个 agent 开发者的笔记本。
扩展系统:核心激进的另一面
pi 核心极简的代价,是把大量功能推给了扩展。四个扩展面,对应四种不同的成本模型:
扩展的 API 面覆盖了"几乎一切":注册工具/命令/快捷键、拦截事件(tool_call、session_before_compact、project_trust……)、替换/新增 UI 组件(编辑器、footer、状态栏、overlay)。官方示例目录(examples/extensions/)里,plan mode、sub-agent、权限门、自定义压缩——那些被核心"拒绝"的功能,全部以扩展形式给出参考答案。这是理解 pi 最关键的一点:"不做"不是"不能",而是"不替你决定"。
扩展还能打成 pi packages(package.json 加 pi 清单),通过 npm/git 分发——生态由此生长,核心由此保持小。
上下文文件与项目信任
两个"安静但重要"的设计:
AGENTS.md 分层加载:全局(~/.pi/agent/AGENTS.md)→ 父目录逐级 → 项目根,全部拼进系统提示词的 <project_context> 段。个人偏好、项目约定、monorepo 级联,各归其位。要替换整个系统提示词也有 .pi/SYSTEM.md(替换)与 APPEND_SYSTEM.md(追加)。
项目信任(project trust):项目的 .pi/ 目录可以带扩展和设置——也就是说,git clone 一个仓库就可能在启动时执行作者的代码。pi 的处理:交互模式启动时先问再载;信任决策存 ~/.pi/agent/trust.json;决策前只加载全局资源。配合默认 YOLO 的执行模型(见下一篇),pi 的安全姿态很诚实:提醒你看清风险在哪,然后自己选——需要硬边界就容器化(官方文档给了 Gondolin micro-VM、Docker、OpenShell 三种模式)。
给造 agent 的你的启示
- 会话格式要设计成"事后可审计、可后处理"的:append-only、自描述、树形。
- 压缩是"上下文的重载点",不是"历史的删除键"。
- 核心只留非留不可的;其余变成可分发的扩展——但扩展 API 要舍得给足(工具、事件、UI)。
- 任何"加载项目本地代码"的能力,都必须先问。
动手对照:实战篇第 06 章实现同款 JSONL 树,第 07 章实现同款压缩。下一篇:pi 的"不做"清单 →