用户旅程¶
这一章从用户输入一条消息开始,按真实代码路径走一遍。理解这条链路后,再看 agent loop、harness、扩展、压缩会更自然。
本章涉及的入口函数和行号集中列在源码索引。
启动阶段¶
典型 CLI/SDK 创建流程在 packages/coding-agent/src/core/sdk.ts 的 createAgentSession():
- 解析
cwd和agentDir。 - 创建或复用
AuthStorage、ModelRegistry、SettingsManager、SessionManager。 - 创建
DefaultResourceLoader,加载扩展、skills、prompts、themes、项目上下文文件。 - 从已有 session 中恢复模型和 thinking level;如果没有,则从 settings 或 provider 默认模型选择。
- 创建
Agent,传入: convertToLlmWithBlockImagesstreamFntransformContext- steering/follow-up 模式
- session id、transport、thinking budgets
- 创建
AgentSession,构建工具 registry、扩展 runner、系统提示。
用户输入阶段¶
入口在 AgentSession.prompt(text, options)。
flowchart TD
A["prompt(text)"] --> B{"以 / 开头?"}
B -->|是| C["尝试执行扩展命令"]
B -->|否| D["input 扩展事件"]
C -->|命中命令| Z["命令自行处理,结束"]
C -->|未命中| D
D --> E["展开 /skill 与 prompt template"]
E --> F{"agent 正在 streaming?"}
F -->|是| G["按 steer/followUp 入队"]
F -->|否| H["校验模型与认证"]
H --> I["必要时先做 compaction"]
I --> J["构造 user message + nextTurn custom messages"]
J --> K["before_agent_start 扩展事件"]
K --> L["Agent.prompt(messages)"]
几个细节值得注意:
- 扩展命令优先于普通 prompt,因为命令可能只是 UI 操作或 session 操作,不一定要进入 LLM。
input扩展事件发生在 skill/template 展开之前,因此扩展可以拦截或改写原始用户输入。- streaming 时必须指定
streamingBehavior,避免用户输入到底是打断式 steering 还是结束后 follow-up 产生歧义。 before_agent_start可以追加 custom message,也可以替换本轮系统提示。
Agent 运行阶段¶
Agent.prompt() 进入 packages/agent/src/agent.ts,再调用 runAgentLoop()。
核心过程:
- 把用户消息加入当前 context。
- 发出
agent_start、turn_start、message_start/end。 - 调用
streamAssistantResponse(),将AgentMessage[]先经过transformContext,再经过convertToLlm,最后传给 provider。 - provider 返回流式 assistant event,loop 持续发出
message_update。 - assistant 结束后检查 tool call。
- 执行工具,生成
toolResultmessage。 - 调用
prepareNextTurn,让产品层刷新会话、模型、thinking level、上下文。 - 如果还有 tool call、steering、follow-up,则继续下一轮。
工具执行阶段¶
工具执行在 packages/agent/src/agent-loop.ts。
Pi 默认是并行工具执行,但设计上很谨慎:
- 对每个 tool call 先顺序做 preflight:
- 查找工具。
prepareArguments兼容性修正。- TypeBox/JSON Schema 参数校验。
beforeToolCallhook,可阻止执行。- 如果某个工具标记
executionMode: "sequential",或全局配置 sequential,则整批顺序执行。 - 并行模式下,允许执行的工具并发运行。
tool_execution_end可以按完成顺序发出,便于 UI 及时反馈。- 写入 LLM 上下文的 tool result message 按 assistant 原始 tool call 顺序生成,保证上下文可重放、可预测。
事件与持久化阶段¶
AgentSession 订阅 AgentEvent,在 _handleAgentEvent() 中做三件事:
- 先把事件翻译给
ExtensionRunner,例如message_end、tool_execution_start、turn_end。 - 通知 UI/RPC/SDK listener。
- 在
message_end时把 user/assistant/toolResult/custom message 追加到SessionManager。
这种顺序让扩展有机会在 message 持久化之前改写 message。例如 message_end handler 可以返回同 role 的 replacement,AgentSession 会原地替换对象,使 agent state、后续事件和 session persistence 保持一致。
结束后处理¶
Agent 一次运行结束后,AgentSession._handlePostAgentRun() 会判断是否需要继续:
- 如果最后 assistant 是可重试错误,则自动重试。
- 如果上下文接近窗口上限或发生 overflow,则触发 compaction,然后
agent.continue()。 - 如果
agent_end扩展 handler 新增了队列消息,则继续。 - 否则结束,等待下一次用户输入。
这解释了为什么 Pi 的用户旅程不是简单 request/response,而是一个可能被工具、队列、扩展、压缩和重试多次延展的运行事务。