用户旅程

这一章从用户输入一条消息开始,按真实代码路径走一遍。理解这条链路后,再看 agent loop、harness、扩展、压缩会更自然。

本章涉及的入口函数和行号集中列在源码索引

启动阶段

典型 CLI/SDK 创建流程在 packages/coding-agent/src/core/sdk.tscreateAgentSession()

  1. 解析 cwdagentDir
  2. 创建或复用 AuthStorageModelRegistrySettingsManagerSessionManager
  3. 创建 DefaultResourceLoader,加载扩展、skills、prompts、themes、项目上下文文件。
  4. 从已有 session 中恢复模型和 thinking level;如果没有,则从 settings 或 provider 默认模型选择。
  5. 创建 Agent,传入:
  6. convertToLlmWithBlockImages
  7. streamFn
  8. transformContext
  9. steering/follow-up 模式
  10. session id、transport、thinking budgets
  11. 创建 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()

核心过程:

  1. 把用户消息加入当前 context。
  2. 发出 agent_startturn_startmessage_start/end
  3. 调用 streamAssistantResponse(),将 AgentMessage[] 先经过 transformContext,再经过 convertToLlm,最后传给 provider。
  4. provider 返回流式 assistant event,loop 持续发出 message_update
  5. assistant 结束后检查 tool call。
  6. 执行工具,生成 toolResult message。
  7. 调用 prepareNextTurn,让产品层刷新会话、模型、thinking level、上下文。
  8. 如果还有 tool call、steering、follow-up,则继续下一轮。

工具执行阶段

工具执行在 packages/agent/src/agent-loop.ts

Pi 默认是并行工具执行,但设计上很谨慎:

  1. 对每个 tool call 先顺序做 preflight:
  2. 查找工具。
  3. prepareArguments 兼容性修正。
  4. TypeBox/JSON Schema 参数校验。
  5. beforeToolCall hook,可阻止执行。
  6. 如果某个工具标记 executionMode: "sequential",或全局配置 sequential,则整批顺序执行。
  7. 并行模式下,允许执行的工具并发运行。
  8. tool_execution_end 可以按完成顺序发出,便于 UI 及时反馈。
  9. 写入 LLM 上下文的 tool result message 按 assistant 原始 tool call 顺序生成,保证上下文可重放、可预测。

事件与持久化阶段

AgentSession 订阅 AgentEvent,在 _handleAgentEvent() 中做三件事:

  1. 先把事件翻译给 ExtensionRunner,例如 message_endtool_execution_startturn_end
  2. 通知 UI/RPC/SDK listener。
  3. message_end 时把 user/assistant/toolResult/custom message 追加到 SessionManager

这种顺序让扩展有机会在 message 持久化之前改写 message。例如 message_end handler 可以返回同 role 的 replacement,AgentSession 会原地替换对象,使 agent state、后续事件和 session persistence 保持一致。

结束后处理

Agent 一次运行结束后,AgentSession._handlePostAgentRun() 会判断是否需要继续:

  1. 如果最后 assistant 是可重试错误,则自动重试。
  2. 如果上下文接近窗口上限或发生 overflow,则触发 compaction,然后 agent.continue()
  3. 如果 agent_end 扩展 handler 新增了队列消息,则继续。
  4. 否则结束,等待下一次用户输入。

这解释了为什么 Pi 的用户旅程不是简单 request/response,而是一个可能被工具、队列、扩展、压缩和重试多次延展的运行事务。