Skip to main content

Pi 的 Session、Trace、Turn 是什么关系?Steer 和 Follow-up 怎么注入?

· 7 min read

Pi 在不同尺度上各有名字:Session 是磁盘上的工作单元,Trace 是一次完整的 agent 循环,Turn 是其中一轮模型响应,Steer 和 Follow-up 是两个不同时机的消息注入口。

  1. 三个时间尺度:Session(持久化)包含 Trace(agent_start→agent_end),Trace 包含 Turn(turn_start→turn_end)
  2. Session:一个 JSONL 文件,跨多次 Trace 复用,Pi 的 Session 是什么? 里讲过存储和分支
  3. Trace:一次用户 prompt 触发的完整运行。模型自己开多轮 不会 拆出多条 Trace
  4. Turn:一次 assistant 响应 + 它的 tool 批,是事件层最细的颗粒度
  5. Steer:每个 Turn 开头都会去取一次,抢下一轮 LLM 调用的发言权
  6. Follow-up:只有 Trace 想结束时才去取,本质是续命,不是打断
  7. Queue Modeone-at-a-time 一条一条发,all 攒齐一次性发
  8. 实战取舍:边跑边改用 steer,结束后追问题用 follow-up

三个时间尺度,从大到小嵌套

Pi 区分三个时间尺度,理解它们的边界就不会再把"会话""运行""轮次"混着用。

Session是最大单位:用户在某工作目录里持续工作的一段历史,落盘成一个 JSONL 文件。它跨越多次 Trace、跨模型切换、跨 /tree 回退。具体结构在 Pi 的 Session 是什么? 已经拆过,这里只关心它和另外两个的层级关系。

Trace对应一次 agent_startagent_end 事件对——一次用户输入触发、模型跑完所有内部轮次、最终停下。Trace 内部可能有很多 Turn,但模型自己决定再调一轮工具不会拆出新 Trace:只要还是同一次 prompt 触发的连续运行,就还在同一条 Trace 里

Turn是最细的颗粒度:一次 assistant 响应 + 这次响应带出的所有 tool 调用和它们的 result。事件上是 turn_start / turn_end 一对。

三者关系:Session 包含 Trace,Trace 包含 Turn。一次 Session 可以包含很多 Trace(每次新 prompt 一条),一条 Trace 包含很多 Turn(一次 prompt 内模型会跑多轮)。

agent loop 里两套队列轮询

runAgentLoop 是个双层循环,外层 while-true,内层 while-hasMoreToolCalls。Steer 和 Follow-up 就在两层循环的不同位置被轮询。

关键点:

  • Steer 队列在每个 Turn 开头被轮询(getSteeringMessages)。模型正在跑当前 Turn 时,用户在 TUI 里再敲一句话,不会打断当前轮——它只是排在下一轮 LLM 调用之前,等当前 turn_end 之后再注入。
  • Follow-up 队列只在内层循环退出之后才被检查——也就是"模型想停下来了"那一刻。Follow-up 不是引导下一轮,是把本来要结束的 Trace 续上一条新的 pendingMessages,让外层循环再跑一圈

所以 Steer 和 Follow-up 的差异不是"哪种消息更重要",是注入的时间点不同:前者是 Turn 边界,后者是 Trace 边界。

Steer:抢下一轮 LLM 调用

Agent 跑任务时,用户在 TUI 里发现方向错了,敲一句话。Pi 的默认行为是 steer:把这条消息塞进 steering 队列。

// 包了扩展命令等处理后
if (this.isStreaming) {
if (options.streamingBehavior === "followUp") {
await this._queueFollowUp(expandedText, currentImages);
} else {
await this._queueSteer(expandedText, currentImages);
}
return;
}

队列是 PendingMessageQueue,有 enqueue / drain / hasItems 三个动作。steerenqueue 推入。等当前 Turn 跑完、下一个 Turn 开头,循环去 getSteeringMessages 取出来注入到 context 里。用户敲"等等,先别写测试",这条消息会在下一轮模型响应之前被插入 LLM 上下文,模型下一轮就能看到。

队列还有两种 mode:

Mode行为适用
one-at-a-time每轮只发一条默认;多次微调要分轮次消化
all攒齐一次性发想把多个补充点合并成一条 user msg

Follow-up:给 Trace 续命

Follow-up 是另一个队列,只在 Trace 准备结束时才被检查。具体在 agent-loop.ts 的第 261 行:

// Agent would stop here. Check for follow-up messages.
const followUpMessages = (await config.getFollowUpMessages?.()) || [];
if (followUpMessages.length > 0) {
pendingMessages = followUpMessages;
continue; // 重新进入外层循环,不发 agent_end
}
await emit({ type: "agent_end", messages: newMessages });

如果 follow-up 队列里有消息,外层循环不退出,把 follow-up 当作 pendingMessages 重新跑内层循环,整个过程不发新的 agent_start / agent_end——所以从事件流看还是同一条 Trace,但用户视角看就是"Agent 接着做下一件事"。

典型场景:Agent 跑完 git status,你说"顺便帮我提交一下"。如果 Agent 还没停(还在跑别的),用 steer;如果 Agent 已经停下来了(trace 已结束或正想结束),TUI 默认就用 follow-up 把它当成"下一段任务"接着跑。

Follow-up 也有 one-at-a-timeall 两种 mode,含义和 steer 一样。

实战怎么选

场景用什么为什么
Agent 还在跑,用户改主意steer抢下一轮注入,立即纠正
Agent 跑完了想加任务follow-up等 Trace 结束后自动续上
想合并多条补充成一个上下文follow-up + mode all一次性发,模型看到的是完整意图
想让模型分轮消化steer + mode one-at-a-time默认;逐条处理
用户敲完才发现没发出去已经被 isStreaming 抛错Pi 会要求显式选 streamingBehavior,不允许静默吞消息

streamingBehavior 是 streaming 时的强制要求:跑着的时候再发消息必须显式选 steer 还是 followUp这是有意的设计——用户和 Agent 协作时,分清楚"打断式注入"和"接力式注入"是决策的一部分。

一句话总结

  • Session 是磁盘上的工作单元,跨多次 Trace 复用
  • Trace 是一次 prompt 触发的完整 agent_start → agent_end,模型内部多轮不出 Trace
  • Turn 是一次 assistant 响应 + tool 批,事件层最细颗粒
  • Steer 在每个 Turn 开头注入,抢下一轮 LLM 调用
  • Follow-up 在 Trace 想结束时注入,续命而不是打断

知道这五个名词各管什么,写扩展、做 TUI、查 bug 的时候就不会把"消息流"和"任务生命周期"混在一起处理。


References

  1. Pi Session 管理:/new /tree /fork /clone /compact —— Kimi Gao, 2026-08-19
  2. Pi 的 Session 是什么?JSONL 树形结构撑起分支、回滚、压缩 —— Kimi Gao, 2026-07-04
  3. Pi 的 Compaction 是什么?自动压缩 + 分支摘要共享一套摘要格式 —— Kimi Gao, 2026-07-07