用户第二次向 agent 发消息时,运行时需要确定上一轮发生了什么、应从哪段历史继续,以及模型输入超过窗口时应删除或总结哪些内容。本文据此对比 Pi 的 @earendil-works/pi-agent-core harness 和 Claude Code。
本文分析的 Pi 对象是 packages/agent/src/harness/,不包含 Pi CLI 的 packages/coding-agent 会话实现。两边都会将会话写入 JSONL,也都会用摘要替代部分旧历史。Pi harness 将历史、当前分支和部分运行配置写入一棵 entry 树;Claude Code 将对话记录、恢复状态和本轮请求优化分别交给 transcript、快照和 query 路径上的多个步骤处理。
本文基于 Pi agent-core harness 和我之前基于泄露版进行的 Claude Code 源码分析 做对比。文中的代码路径与行号均以这些提交为准。
一次请求的处理过程
假设用户已经和 agent 对话十轮,现在补充一句“继续修改刚才的文件”。系统需要读取磁盘上的持久化记录,处理新消息的写入、重启后的状态恢复和用户回退重试时的分支,最后将记录投影为 provider 请求。这个过程需要处理以下问题:
| 要回答的问题 | Pi agent-core harness | Claude Code |
|---|---|---|
| 磁盘里保存什么 | 一棵 SessionTreeEntry 树 | transcript 消息加多类附属记录 |
| 从哪里继续 | leafId 指向当前树节点 | loader 结合 transcript、压缩边界和保留段恢复视图 |
| 本轮带哪些历史 | 从当前 leaf 回溯一条路径,再投影消息 | 在 messagesForQuery 上按条件做多步变换 |
| 历史太长怎么办 | 写入 compaction entry,用摘要替代旧路径 | 先缩减工具输出和局部历史,必要时完整 compact |
后续各节依次说明持久化模型、写入与追加、重启恢复、分支保留和 Context 投影。压缩、SubAgent 隔离以及工具输出和缓存的处理会影响这些步骤,需要单独说明。
持久化消息以外的会话状态
用户恢复会话时,agent 还需要知道当时使用的模型、可用工具和文件操作状态。缺少这些信息时,恢复后的运行条件可能与中断前不一致。
Pi agent-core harness 方案
Pi harness 将这些信息统一写成 SessionTreeEntry。其中,message 表示消息,model_change、thinking_level_change 和 active_tools_change 表示运行配置变化,compaction 表示历史摘要,branch_summary 表示被离开路径的摘要,leaf 表示一次导航跳转(packages/agent/src/harness/types.ts:382-464)。每条 entry 都有 id、parentId 和 timestamp。
沿一条历史路径扫描 entry 时,deriveSessionContextState() 可以重新得到这条路径最后使用的 model、thinkingLevel 和 activeToolNames;sessionEntryToContextMessages() 只选择消息和摘要,并将其转换为模型可读取的消息(packages/agent/src/harness/session/session.ts:41-138)。这些派生值描述会话记录。当前 AgentHarness 发请求时,模型、思考等级和工具仍由 harness 持有的当前字段提供,消息来自 session.buildContext()。
Claude Code 方案
Claude Code 的做法是将不同用途的信息分开记录。TranscriptMessage 保存用户、assistant 和工具相关消息,并带有 parentUuid、logicalParentUuid、isSidechain、agentId 等字段(src/types/logs.ts:221-231)。同一 JSONL 还会出现 file-history-snapshot、attribution-snapshot、content-replacement、marble-origami-commit 和 marble-origami-snapshot。例如,content-replacement 保存工具调用 ID 和模型实际看到的替换文本:超大的工具输出放到 session 目录中,Context 只保留较小的替换内容;resume 时重放同一替换文本,避免改变 token 数和 prompt cache 前缀(src/types/logs.ts:173-185、src/utils/toolResultStorage.ts:369-388)。
Pi harness 沿一条 entry 路径重放记录;Claude Code 先读取 transcript,再按需要读取文件历史、Todo、collapse 等记录。两边都保存消息以外的状态,但这些记录的归属不同。
将新记录追加到正确的历史位置
agent 可能在旧消息处回退后继续对话,也可能同时产生多项写入。新记录必须追加到正确的历史位置,否则恢复时会将新消息接回错误分支。
Pi agent-core harness 方案
Pi harness 使用两个字段:parentId 指向这条 entry 之前的历史,leafId 表示当前继续写入的节点。追加普通 entry 时,程序将当时的 leafId 写入 parentId;写入成功后,新 entry 成为新的 leaf。appendTail 将写入排成 promise 队列,因此两次同时发起的写入不会读到同一个过期 leaf;单次写入失败后,后续写入仍可继续(packages/agent/src/harness/session/session.ts:237-255)。
用户跳到旧节点时,moveTo() 保留历史 entry,并追加一条 leaf entry 记录跳转前位置和 targetId,然后将内存中的 leaf 移到目标节点。后续消息从目标节点继续并形成新路径;如果调用方要求生成摘要,branch_summary 会先写入新路径(packages/agent/src/harness/session/session.ts:400-421)。Pi 的 JSONL 因此有两种顺序:文件顺序是实际写入顺序,当前对话顺序需要从 leaf 沿 parentId 向上回溯。
Claude Code 方案
Claude Code 的 recordTranscript() 先检查哪些 UUID 已经写入,只把新增消息交给 insertMessageChain(),并只允许新增片段的前缀作为 startingParentUuid。这避免压缩后保留的旧消息被误接回已经被摘要替代的前缀(src/utils/sessionStorage.ts:1391-1448)。普通会话位于 ~/.claude/projects/<project>/<sessionId>.jsonl;subagent 的 sidechain 在同一 session 目录下单独保存(src/utils/sessionStorage.ts:198-257)。
备注:什么是 sidechain?
sidechain 是主会话之外的一份独立 transcript,用来记录某个 subagent 或 forked agent 的执行过程。它有自己的 JSONL 文件和消息链,不会被当作主会话的普通历史直接拼进下一轮 Context。主 agent 可以根据任务结果读取或引用它,但主会话的继续路径仍由自己的 transcript 决定。这样可以保留子任务的调用、工具输出和中间过程,又避免这些细节默认占满主会话的上下文。
完整压缩后,Claude Code 会写入一条 parentUuid: null 的 compact boundary。恢复时,loader 遇到该边界便停止沿 parentUuid 读取更早的历史;logicalParentUuid 保留逻辑关联,保留段的重连信息用于构造当前可继续的 transcript 视图。该机制确定摘要后从哪里继续恢复;Pi 则在同一文件内切换树分支。
进程重启后恢复当前状态
内存中的索引、当前节点和文件历史会在进程退出时消失。恢复过程需要从 JSONL 重建可继续的会话,并避免将旧工具输出或已压缩历史重新加入 Context。
Pi agent-core harness 方案
Pi harness 恢复时,JSONL 后端读取所有 entry,ArraySessionIndex.replace() 建立 entries、按 ID 查找的 byId 和当前 leaf。普通 entry 会把 leaf 推进到自己;leaf entry 则将 leaf 定位到它记录的 targetId(packages/agent/src/harness/session/jsonl-repo.ts:242-252、array-session-index.ts:82-99)。程序随后从这个 leaf 向 parentId 回溯,无需从根节点推断应选择哪个子节点(array-session-index.ts:167-186)。
恢复过程先得到当前路径。buildSessionContext() 再沿该路径重放状态 entry;遇到 compaction entry 时,用其中的摘要替代对应的旧历史,然后生成 SessionContext = { messages, model, thinkingLevel, activeToolNames }。同一个文件可以保留所有分支,本轮 Context 只使用当前 leaf 对应的路径。
Claude Code 方案
Claude Code 恢复时先重建可继续的 transcript 视图,并在 compact boundary 处停止读取早期前缀。restoreSessionStateFromLog() 再恢复 file history、attribution、context-collapse commit/snapshot 和 Todo;从最后一条 TodoWrite 反向提取 Todo 只用于 Todo v2 未启用的路径(src/utils/sessionRestore.ts:95-150)。worktree 与 agent 设置也在恢复流程中处理。
“恢复”与“发请求”是两个阶段。恢复阶段读取记录并重建运行时对象;下一轮 query 重新计算消息数组。content-replacement 需要在恢复阶段重放,是为了不把外置的大工具输出重新放回消息数组。文件、工具定义和部分说明则可能来自当前进程的配置,并不都来自 Session 文件。
用户改用另一条方案时如何保留历史
用户可能让 agent 回到前一条消息,换一种方案继续。系统需要保留原来的尝试供查看,使新尝试成为当前上下文,并在必要时将离开的过程压缩为摘要。
Pi agent-core harness 方案
Pi harness 在同一个 Session 树中处理分支。导航时,它计算旧 leaf 与目标节点路径的共同祖先;旧 leaf 到共同祖先之间的 entry 是离开的历史。调用方要求摘要且这段历史存在时,harness 将其写成 branch_summary,再从目标节点继续(packages/agent/src/harness/compaction/branch-summarization.ts:71-100、session.ts:400-421)。新路径包含摘要和读写文件清单,旧路径仍保留在日志中。
Claude Code 方案
Claude Code 的 /branch 会新建一个 session ID 和新的 JSONL 文件。它复制原 transcript 中非 sidechain 的主会话消息,按复制后的顺序重建 parentUuid 和 sessionId,并用 forkedFrom = { sessionId, messageUuid } 记录来源;content-replacement 也会复制,以免新 Session 恢复时重新注入大工具输出(src/commands/branch/branch.ts:56-172、:274-280)。
运行时 forked agent 是另一种操作。它用于隔离执行,例如让一个没有工具权限的 agent 生成 compaction 摘要;它复用父请求的 cache-safe 参数,但使用独立 ToolUseContext,并可把记录写到 sidechain(src/utils/forkedAgent.ts:464-555)。
Pi harness 将“另一条尝试”保存在同一棵 Session 树中;Claude Code 的 /branch 将其保存为新的 Session 文件。两边都保留来源,但读取和恢复的入口不同。
根据会话记录构造模型请求
磁盘中的完整历史通常不能直接发给模型。系统需要选择当前对话路径,补齐当前运行条件,并在不破坏工具调用顺序的前提下减少输入。
Pi agent-core harness 方案
Pi harness 先由 leafId 选出一条 entry 路径,再重放状态、执行 compaction transform,并投影出 context.messages。最后,AgentHarness 将这组消息与当前模型、思考等级、工具和独立生成的 system prompt 组合为 provider 请求:
entries = getBranch(leafId)
context = session.buildContext(entries)
request = {
systemPrompt,
messages: context.messages,
model: harness.model,
thinkingLevel: harness.thinkingLevel,
tools: harness.activeTools,
}代码中的 buildContext() 完成的是“从会话历史得到消息”的工作;AgentHarness.createTurnState() 负责再把当前 harness 的配置加入请求(packages/agent/src/harness/agent-harness.ts:395-440)。自动压缩何时触发属于宿主的调度策略,不由这一段固定投影逻辑决定。
Claude Code 方案
Claude Code 的 messagesForQuery 是请求路径上会反复处理的工作数组。以下步骤用于在不破坏工具调用顺序的前提下缩短输入。它们是否运行取决于功能开关、模型能力与当前状态,并非每轮都会执行(src/query.ts:379-467):
- 工具结果预算:工具输出过大时存到磁盘,消息中留下引用和预览,优先降低最容易膨胀的输入。
- Snip(裁剪):删去一部分可省略的历史,并把节省的 token 数交给后续判断。
- Microcompact(工具结果压缩):按
tool_use_id清理旧工具结果;缓存模式下生成cache_edits,由 API 请求层应用删除。 - Context collapse(局部摘要):用较小摘要替代一小段历史。原消息仍在 transcript 中,collapse commit 只保存这次替换的范围和摘要占位。
- Autocompact(完整压缩):输入仍达到阈值且未被 Context Collapse 抑制时,先尝试 session-memory compaction;没有结果才执行完整的
compactConversation()。
Pi 主要确定当前使用哪条历史路径;Claude Code 还需要确定该路径中的哪些工具输出和旧消息可以放入本轮窗口。因此,两边构造 Context 的复杂度不同。
模型窗口不足时如何使用摘要继续执行
直接删除旧消息会丢失任务目标、已修改文件和工具调用关系;保留全部消息又会超过模型窗口。压缩需要减少 token,同时保留继续执行所需的事实。
Pi agent-core harness 方案
Pi harness 先找安全切点。findValidCutPoints() 不把 toolResult 作为切点,避免保留侧只剩工具结果却没有对应的 tool call。切点若落在一个 turn 中间,compact() 会先单独总结被切掉的 turn 前缀,再和已有摘要组合(packages/agent/src/harness/compaction/compaction.ts:328-366、:762-793)。已有摘要会作为 previous summary 增量更新,读写过的文件也被提取为结构化清单,补足自然语言摘要可能漏掉的文件路径。
压缩会生成一条新的 compaction entry,原始 entry 不会删除。后续 defaultContextEntryTransform() 构造 Context 时用摘要替代早期路径,因此压缩减少的是本轮输入,日志中的历史仍保留。摘要请求使用独立 sessionId 与 cacheRetention: "none",即这次一次性摘要调用不保留主会话的 cache retention(packages/agent/src/harness/compaction/compaction.ts:118-138)。
备注:什么是 cache retention?
provider 的 prompt cache 会暂存一段请求前缀,例如 system prompt、工具定义和前面的消息;下一次请求前缀相同时,provider 可以复用这段已处理内容,减少输入 token 的重复计算。
cacheRetention是 Pi 向 provider 表达“这次请求的缓存应保留多久或是否保留”的参数。这里设为"none",表示 compaction 的一次性摘要请求不请求保留其缓存;因此摘要的大段输入不会占用或干扰主会话后续请求使用的缓存。
Claude Code 方案
Claude Code 的完整压缩由 compactConversation() 执行。它先运行 pre-compact hooks,再用没有工具权限的 forked agent 生成摘要;摘要 prompt 仍过长时,从最旧的 API round 开始裁掉后重试(src/services/compact/compact.ts:387-491、:1136-1200)。该调用使用与主线程一致的 cache-safe 参数。
摘要生成后,Claude Code 还需要让下一轮请求知道当前工作环境,这是 Pi harness 在本节不直接处理的问题。完整压缩后的请求会重新加入最近读取的文件、当前 plan、已激活 Skill、工具定义增量、agent 列表和 MCP 指令。这些信息与摘要、compact boundary 和保留尾部消息共同构成下一轮请求,但并不都属于摘要条目。
自动压缩会为摘要输出预留空间,再从有效窗口减去 13,000 token buffer;连续三次自动压缩失败后熔断。收到 413 时,query loop 尝试 reactive compact;成功就用 post-compact messages 重试,没有恢复方案则返回 prompt-too-long,并停止执行可能继续增加输入的 stop hooks(src/services/compact/autoCompact.ts:62-90、:160-351;src/query.ts:1119-1182)。
SubAgent 如何隔离会话、上下文和存储记录
主 agent 将任务交给其他 agent 时,子任务需要获得足够的上下文和工具,但中间消息、工具输出和失败细节不应默认加入主会话。需要分别查看子 agent 的 Context 来源、运行记录位置和结果回传方式。
Pi agent-core harness 方案
packages/agent/src/harness 没有内建的 SubAgent 或 delegation API。AgentHarness 每个 turn 都从它绑定的一个 Session 构造 Context,运行一个 agent loop,再把消息写回同一个 Session(packages/agent/src/harness/agent-harness.ts:395-440、:656-689)。SessionRepository.fork 可以复制会话,navigateTree 可以在历史树中导航,但它们是会话存储能力,不能直接等同于创建子 agent。
Pi 仓库中最接近 SubAgent 的实现位于 packages/coding-agent/examples/extensions/subagent/。它是需要用户自行安装的示例扩展,不是 agent-core harness 的正式能力。父 agent 调用 subagent 工具后,扩展为每项任务启动一个独立 pi 子进程(packages/coding-agent/examples/extensions/subagent/index.ts:267-339)。
这个子进程显式接收的是任务文本、子 agent 定义中的 system prompt、可选模型、可选工具和工作目录;代码没有把父 agent 的完整消息历史、Session ID、压缩摘要或工具结果传入。它固定以 --no-session 启动,因此使用内存 Session,不创建持久化的 JSONL 子 Session,也没有 parentSession 关系(packages/coding-agent/examples/extensions/subagent/index.ts:294-330;packages/coding-agent/src/main.ts:312-320)。
子进程通过 JSON Lines stdout 返回完成消息和工具结果。示例扩展收集这些数据,将最终 assistant 文本包装成父 agent 看到的普通工具结果;完整收集结果放在 tool result 的 details 中,供当前 UI 使用(packages/coding-agent/examples/extensions/subagent/index.ts:342-377、:666-690)。父 Session 保存的是父侧的 assistant 与 tool-result 消息,不会因此生成一个可恢复、可浏览的子 Session 日志。
Claude Code 方案
Claude Code 的本地 Task / subagent 运行在独立 query loop 中,并拥有 agentId、独立的可变运行时状态和 sidechain。普通 Task 的初始消息只有任务 prompt,不默认继承主会话完整历史;fork subagent 才显式接收父消息,并继承父线程已渲染的 system prompt、工具定义和相关模型配置,以复用请求前缀缓存(src/tools/AgentTool/runAgent.ts:368-378;src/tools/AgentTool/AgentTool.tsx:483-512、:603-635)。
子 agent 的记录写入主 session 目录内的 subagents/agent-<agentId>.jsonl,旁边的 metadata 文件保存 agent 类型、任务描述和可选 worktree 路径(src/utils/sessionStorage.ts:247-303)。普通 subagent 的 sidechain 主要保存自己的消息;fork subagent 还会把继承的父消息写入 sidechain。这样 fork 即使不读取主 transcript,也能从自己的文件恢复完整上下文(src/utils/sessionStorage.ts:1230-1261、:4190-4235)。
子 agent 完成时,Claude Code 不会把 sidechain 中的每条工具调用合并进主 transcript。它提取最终文本、任务状态、输出文件路径和用量,排入一条 <task-notification> 作为主 agent 的后续输入;完整执行过程仍留在 sidechain,主 agent 需要细节时再通过 task output 或 sidechain 读取(src/tools/AgentTool/agentToolUtils.ts:597-637;src/tasks/LocalAgentTask/LocalAgentTask.tsx:246-261)。
还需要区分 compaction 使用的 fork。compactConversation() 调用 runForkedAgent() 生成摘要时,会为了 prompt cache 复用父请求的关键前缀,但只运行一轮,并通过 createCompactCanUseTool() 拒绝所有工具调用。它的摘要直接用于重建主会话下一轮的消息数组,没有 Task 注册和 <task-notification> 回传,因此不能把它当作处理用户子任务的 SubAgent(src/services/compact/compact.ts:1125-1134、:1179-1200)。
Pi 的示例扩展采用独立进程、内存 Session 和工具结果回传;Claude Code 的本地 subagent 采用独立 query loop、可恢复 sidechain 和任务通知回传。Pi 的父子 agent 只显式传递任务和结果;Claude Code 保存更多子任务过程,并为普通任务和继承父 Context 的 fork 提供不同路径。
工具输出、缓存和失败处理对 Session 的影响
coding agent 的工具输出可能远大于用户消息。每轮都将完整输出带回模型会很快超过窗口;恢复后替换文本变化会破坏 prompt cache,使相同前缀无法复用。
Claude Code 方案
Claude Code 先将大工具输出外置,再持久化 content-replacement,从而让 resume 后继续使用同一段替换文本。microcompact 的 cache_edits 不直接改写消息,而由 provider 请求层处理缓存删除。为判断请求前缀是否因内容变化而失去缓存复用,请求前会记录 system prompt、工具 schema、模型和 cache control 等状态;响应后,只有 cache read token 相比前次同时满足相对跌幅和绝对跌幅条件时,才记录一次 cache break。cached microcompact 的预期删除不计入 break,compaction 后会重置比较基线(src/services/api/promptCacheBreakDetection.ts:247-698)。
Pi agent-core harness 方案
Pi harness 在工具输出和缓存上的职责更窄:compaction 确保 tool call 与 tool result 不被切断,并把读写文件清单加入摘要;appendTail 确保一次落盘失败不会让后续写入永远阻塞(packages/agent/src/harness/session/session.ts:237-255)。agent-core harness 没有实现 Claude Code query loop 中的自动压缩和 cache-break 归因管道;这只说明这部分不在本文分析的 harness 模块中,不代表 Pi CLI 或其他宿主没有额外策略。
Claude Code 还为失败循环设置了边界:autocompact 连续失败三次后熔断;413 的 reactive compact 失败后不再进入 stop hook,避免 hook 继续增加消息,又再次触发 413。
两套运行时处理的主要问题不同
Pi harness 与 Claude Code 都不将“模型消息数组”视为完整 Session。两边都会保存原始记录、在重启后恢复状态,并在请求前生成较小的 Context。两者将复杂度放在不同位置。
Pi agent-core harness:以可回放的会话历史为中心
Pi harness 以 entry 树为中心。消息、模型切换、工具集合、压缩和历史跳转都作为 entry 保存;leafId 选择当前路径,Context 通过重放该路径得到。其数据模型需要处理以下问题:
- 当前会话正在继续哪一条历史。
- 这条历史是在什么模型、思考等级和工具配置下产生的。
- 回到旧节点后,如何保留原路径并从目标节点创建另一条路径。
这种设计以可回放会话事实为中心。历史导航、分支、审计和状态恢复都有对应的数据结构;查看一个 Session 时,可以从 leaf 出发追溯本轮消息和配置的来源。实现需要维护 entry 的父关系、文件追加顺序、索引和写入队列。自动压缩阈值、缓存归因、子代理调度等策略则需要由宿主在 harness 之外组合。
Pi harness 可用于构建可嵌入其他产品的 Agent Runtime,或需要保存、检查和回放多条对话路径的场景。宿主可以自行决定 UI、模型策略、自动压缩和多 agent 协作方式。harness 提供 Session 和 Context 的基础能力,不包含完整 coding agent 产品的全部控制面。
Claude Code:管理长任务的连续执行与请求资源
Claude Code 不使用单一 Session 树作为中心对象,而是组合可恢复的 transcript 与本轮请求的资源管理。它分别保存文件历史、任务、工具输出替换、collapse 等状态;每次请求前,再在 messagesForQuery 上处理大工具输出、局部裁剪、局部摘要、完整压缩和缓存相关变换。该设计需要处理以下问题:
- 长时间编程任务恢复后,哪些文件、任务和工具状态仍然可用。
- 大量命令输出和历史消息怎样不超过模型窗口。
- 在缩短输入的同时,怎样尽量保持 prompt cache 可复用,并避免 413 或重复压缩失败。
这种设计以连续执行和请求资源管理为中心。它对长任务、大工具输出、Skills、MCP、subagent、缓存和失败重试提供了相应的工程处理;普通 Task、fork subagent 和 compaction fork 也各自有存储和回传路径。一次请求的实际 Context 来自 transcript、sidechain、快照、当前环境和多项 query 变换,排查某段内容为何进入模型输入通常需要跨多个模块追踪。
Claude Code 可用于完整的 coding agent 产品,尤其适合持续读取大量文件、执行命令和委派子任务的场景。在这些场景中,模型窗口、延迟、缓存成本和恢复可靠性都是运行时约束。它为这些需求提供了更多内建机制,但宿主替换其中某个环节的成本也更高。
实现差异
| 维度 | Pi agent-core harness | Claude Code |
|---|---|---|
| 主要设计中心 | 当前 Session 路径及其可回放事实 | 长任务的可继续执行与请求资源管理 |
| 数据模型 | entry 树、parentId、leafId、状态重放 | transcript、sidechain、快照和 compact boundary |
| Context 的主要来源 | 当前 leaf 对应路径的投影 | transcript 加上每轮 query 变换和当前运行环境 |
| 长上下文策略 | 将摘要作为 entry,替换路径中的旧消息投影 | 工具输出外置、局部裁剪/摘要、完整压缩、环境重新附加 |
| 多 agent | harness 未内建调度;示例扩展使用隔离进程和工具结果回传 | 内建 Task、fork 和 sidechain,任务结果通过通知回到主流程 |
| 优势 | 历史、分支和状态的归属清晰;便于宿主组合策略 | 对长 coding 任务、工具输出、缓存和恢复有较完整的内建处理 |
| 代价 | 产品级调度与资源管理需要宿主补齐 | 状态分散在多个记录和请求步骤,排查 Context 来源更复杂 |
选型需要根据首要约束判断。如果需要让宿主精确控制会话历史、分支和状态重放,Pi harness 的数据模型更直接;如果需要让 coding agent 在长任务和大量工具输出下继续执行,Claude Code 的请求侧管道覆盖更多约束。
Pi CLI 在 harness 之外提供的能力
本文的主要比较对象不包含 Pi CLI,但 Pi CLI 不只有 harness。CLI 在 packages/coding-agent 中提供面向终端产品的能力:SessionManager 负责保存和加载 JSONL 会话、维护当前 leaf 与历史导航;AgentSession 负责将用户输入、工具执行和 turn 生命周期接入运行时;CLI 层还实现了基于上下文阈值和 overflow 的自动 compaction,并提供扩展、Skills、agent 定义等产品入口(packages/coding-agent/src/core/session-manager.ts、agent-session.ts:1943-2157、core/compaction/compaction.ts:125-136)。
agent-core harness 提供可复用的 Session、Context 和 agent loop 原语;Pi CLI 是使用并补充这些原语的终端宿主。本文比较 harness 与 Claude Code,用于说明两种 Runtime 接口的边界,不构成 Pi CLI 与 Claude Code 的完整产品功能对比。
以上结论只覆盖本文对应的 Pi agent-core harness 与 Claude Code 源码模块,不涵盖 Pi CLI 的完整会话实现,也不比较任一方案在所有产品中的优劣。
