会话与运行时

前两篇分别讲了"地图"和"工具"。这一篇讲把工具、模型、事件流缝在一起的那层——会话与运行时。对应源码:

  • core/session-manager.ts — JSONL 会话文件与树操作
  • core/compaction/ — 上下文压缩
  • core/model-runtime.ts — 模型目录与 provider 认证
  • core/agent-session.ts — 总装
  • sdk.ts — 对外的程序化入口

SessionManager:磁盘上的树

极简主义篇讲过会话文件的格式(JSONL、id/parentId 树)。这里补实现细节。

SessionManager 是个 class(core/session-manager.ts:791),构造靠静态工厂:

// 创建或恢复 cwd 对应的会话;不传 sessionDir 则新建文件
const sm = SessionManager.create(cwd, sessionDir?, { sessionName? });
// 不走磁盘的内存版(SDK 场景)
const sm = SessionManager.inMemory();

磁盘布局按工作目录归档:~/.pi/agent/sessions/--Users-mario-my-project--/<timestamp>_<uuid>.jsonl——把 cwd 的路径分隔符替换成 -- 当目录名,同一会话目录下的文件按时间排序,--continue 就是取最新的一个。

文件条目分四类:session(header,version 3)、messagemodel_changecompaction。每条带 id/parentId,新条目追加到当前 leaf 之下。关键操作:

  • newSession(parentId?):从当前会话分叉——写入新 header(parentSession 指向旧文件),同目录新建文件。这就是 /new 命令和分叉式回滚的实现。
  • getTree():从 entries 构建树,返回所有节点,供 /tree 导航和 TUI 的树选择器使用。
  • fork/分支切换:从树中任意节点重新加载上下文(约 session-manager.ts:1352loadEntriesFrom/buildSessionContext 一线)——沿该节点的祖先链收集 message,作为新的 agent 上下文。撤销一次工具调用?选中调用前的节点即可。

compaction 条目是树上的特殊节点:firstKeptEntryId 标记保留区的起点,summary 是压缩摘要。后续构建上下文时遇到它,就用"摘要 + 保留区消息"代替原始历史。

Compaction:作为第一条的 hook

压缩机制本身(transformContext 触发、agentic 摘要生成,阈值 contextWindow - reserveTokens,reserve 16384)在前面的篇章已展开。这里值得记录的是它在扩展机制里的位置:transformContext 是 pi 全部扩展钩子的第一个——编号 01。扩展系统里管上下文生命周期的钩子一族(transformContextprepareMessagestransformSystemPrompt)都以压缩为原型:在消息发给 LLM 之前,给你一次改写的机会。理解了 compaction,就理解了 pi 扩展钩子的一半设计语汇。

ModelRuntime:模型的统一门面

ModelRuntime(core 里)实现 pi-ai 的 Models 接口,把三件事揉在一起:

  • 模型目录:内置目录(300+ 模型条目)与 ~/.pi/agent/models.json 的用户自定义合并,定期后台刷新(远端目录拉取);
  • 认证解析:provider 环境变量 → OAuth 凭证 → apiKey 配置的优先级链,解析出某个模型本次调用该用的凭证;
  • 统一接口:对上层只暴露"给我一个 model + credentials,我返回 stream 函数",与 pi-ai 的 getModel/completeSimple 形状一致。

ModelRuntime.create(...) 是标准入口;registerProvider 允许扩展注册新 provider。CLI 的 /login/models、Ctrl+P 换模型,都只是这个门面的 UI。

AgentSession:总装与四种模式

core/agent-session.ts 把以上一切组装成一个对象:持有 Agent(pi-agent-core)、SessionManagerModelRuntimeSettingsManagerResourceLoader,订阅 agent 事件流,把事件翻译给 UI,把 UI 的输入(steer/followUp/abort)翻译回 agent。

四种运行模式——interactive(TUI)、print(-p)、RPC(--mode rpc)、SDK(createAgentSession)——消费的是同一条事件流。这是"UI 是外围"原则的最终兑现:TUI 的 AgentEventVisualizer、print 模式的 stdout 渲染器、RPC 的 JSON 行协议,只是同一事件流的三种渲染器。新增一种前端不需要动 agent 一行代码。

SDK 一览

sdk.tscreateAgentSession(options) 是最小入口,默认配上 DefaultResourceLoader(扫描 skills/extensions/主题)和 ModelRuntime

import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";

const modelRuntime = await ModelRuntime.create(process.cwd(), {
  settingsManager,       // 可选,提供 provider 凭证
  reloadModels: true,    // 可选,后台刷新模型目录
});
const { session } = await createAgentSession({
  cwd: process.cwd(),
  modelRuntime,
  sessionManager: SessionManager.inMemory(),  // 不落盘
});

session.subscribe((event) => { /* 同一事件流 */ });
await session.prompt("Fix the bug in main.ts");

你拿到的是与 CLI 完全同构的 session:prompt、steer、abort、事件订阅、树导航。给 agent 套个 Web 前端、接进 CI、做成聊天机器人,都从这个入口开始。用 pi 重构一章有另一组更贴近 Agent 类的示例。

小结

至此三张源码导读拼齐了全貌:main.ts 引导agent loop 跑圈(pi-agent-core)→ 工具执行(tools/ + 四个横向机制)→ 会话落盘与恢复(SessionManager/compaction)→ 模型门面(ModelRuntime)→ 模式渲染(四种 UI 消费同一事件流)。想再深入,带着这张地图回到 源码地图 逐个文件读即可。