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):

~/.pi/agent/sessions/--<cwd 路径转义>--/<时间戳>_<uuid>.jsonl
{"type":"session","id":…,"version":3,"cwd":…}          ← 头部
{"type":"message","id":"a","parentId":…, message:{…}}  ← 消息记录
{"type":"compaction","id":"b","parentId":"a", …}       ← 压缩记录(见下节)
{"type":"message","id":"c","parentId":"a", message:{…}}← a 的另一个孩子:分支!

树形结构让分叉成为免费属性:回到历史任意一点换个方向继续,不需要新文件,往同一文件追加一个指向旧 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 核心极简的代价,是把大量功能推给了扩展。四个扩展面,对应四种不同的成本模型:

扩展面形态上下文成本适合
ExtensionsTypeScript 模块工具 schema 常驻需要代码执行的能力(新工具、钩子、UI)
SkillsSKILL.md + 附件(Agent Skills 标准)仅摘要在场,正文按需加载流程性知识(怎么做发布、怎么写迁移)
Prompt templatesmarkdown + {{参数}}展开时才进上下文可复用的提示词
ThemesJSON外观

扩展的 API 面覆盖了"几乎一切":注册工具/命令/快捷键、拦截事件(tool_callsession_before_compactproject_trust……)、替换/新增 UI 组件(编辑器、footer、状态栏、overlay)。官方示例目录(examples/extensions/)里,plan mode、sub-agent、权限门、自定义压缩——那些被核心"拒绝"的功能,全部以扩展形式给出参考答案。这是理解 pi 最关键的一点:"不做"不是"不能",而是"不替你决定"。

扩展还能打成 pi packages(package.jsonpi 清单),通过 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 的"不做"清单 →