01 使用入口学习记录
本节目标
本节目标是先把 pi 的入口链路跑通,不急着深入所有源码。重点理解一次用户输入如何从 CLI 进入会话层,再进入 agent 执行层,最后由 agent loop 产生模型输出、工具调用和事件流。
我们怎么学的
- 先从仓库结构判断模块分层。
- 再追
pi命令入口,确认bin.pi指向dist/cli.js,源码入口是packages/coding-agent/src/cli.ts。 - 继续读
main.ts,确认它负责参数解析、模式选择、session/runtime 创建。 - 选择
print mode作为第一条主线,因为它比 interactive mode 更干净。 - 追到
AgentSession.prompt(),理解它不是直接请求模型,而是做输入预处理和调度。 - 追到
Agent.prompt(),理解它启动一次 agent run,并构造 context snapshot 和 loop config。 - 最后读
runLoop(),理解 pi agent 的核心不是一问一答,而是事件驱动的循环。
已确认的主链路
pi 命令
-> packages/coding-agent/src/cli.ts
-> packages/coding-agent/src/main.ts
-> runPrintMode / InteractiveMode / runRpcMode
-> AgentSession.prompt()
-> Agent.prompt()
-> runAgentLoop()
-> runLoop()
-> streamAssistantResponse()
-> executeToolCalls()
-> agent_end
关键源码位置
packages/coding-agent/package.jsonbin.pi = dist/cli.jspackages/coding-agent/src/cli.ts- CLI 入口,只做运行环境初始化,然后调用
main(process.argv.slice(2))。 packages/coding-agent/src/main.ts- 解析参数、选择 app mode、创建
SessionManager、创建 runtime/session。 packages/coding-agent/src/modes/print-mode.ts- 单次 prompt 模式,调用
session.prompt(),最后输出 assistant 文本或 JSON 事件。 packages/coding-agent/src/core/agent-session.ts- 会话层入口,处理扩展命令、skill、prompt template、鉴权、compaction、retry、session 持久化。
packages/agent/src/agent.ts- 执行层状态机,维护 messages/tools/systemPrompt,启动
runAgentLoop()。 packages/agent/src/agent-loop.ts- 核心循环,负责模型流式输出、工具调用、工具结果回填和停止条件。
第一次运行 Pi 看什么
建议第一遍用 JSON mode:
pi --mode json -p "Read README.md and summarize it"
观察重点:
- 先看到
session:说明 CLI 层已经创建或恢复 session。 - 再看到
agent_start:说明已经进入一次 agent run。 - 看到
turn_start:说明进入一轮模型/工具循环。 - 看到
message_start(user):说明用户输入已经被AgentSession.prompt()变成消息。 - 看到
message_start(assistant)和message_update:说明 provider stream 正在回传 assistant 内容。 - 如果有工具调用,先出现 tool call 增量,再出现
tool_execution_start/end:前者是模型提出请求,后者才是本地工具执行。 turn_end不一定等于整个 prompt 结束;如果工具结果回填后还要继续推理,会有下一轮 turn。agent_end才表示这次 agent run 没有待处理工具、steering 或 follow-up。
CLI mode 分发图
print、json、interactive、RPC、SDK 是不同输入输出外壳,不是不同 agent 核心。
flowchart TD
CLI[pi CLI / SDK / RPC entry] --> Main[main.ts mode dispatch]
Main --> Print[print mode]
Main --> Json[json mode]
Main --> Interactive[interactive mode]
Main --> Rpc[RPC mode]
Print --> Session[AgentSession.prompt]
Json --> Session
Interactive --> Session
Rpc --> Session
Session --> Agent[Agent.prompt]
Agent --> Loop[runAgentLoop / runLoop]
判断方式:
- print mode:适合只看最终文本。
- json mode:适合观察事件流。
- interactive mode:多了 TUI、快捷键和实时渲染。
- RPC mode:把事件交给外部客户端消费。
- SDK:让程序直接复用 agent/session 能力。
- 这些入口最终都会围绕
AgentSession -> Agent -> runLoop的共用链路。
一次 prompt 的事件时序图
sequenceDiagram
participant User
participant CLI
participant AgentSession
participant Agent
participant Loop as runLoop
participant Provider
User->>CLI: pi --mode json -p ...
CLI->>AgentSession: prompt(message)
AgentSession->>Agent: prompt(messages)
Agent->>Loop: runAgentLoop(context, config)
Loop->>Provider: streamAssistantResponse(context)
Provider-->>Loop: text/thinking/toolCall events
Loop-->>AgentSession: agent events
AgentSession-->>CLI: JSON lines
我们形成的职责边界
AgentSession
AgentSession 是产品会话层。它负责把用户输入变成 agent 可执行的消息,并处理 CLI/TUI/RPC 共用的产品逻辑。
它关心:
- session 持久化
- 扩展命令
- skill 和 prompt template 展开
- model 和 auth 检查
- compaction 和 retry
- 事件转发
- 系统提示词和工具集合的构建
Agent
Agent 是执行层状态机。它不关心 CLI 怎么输入,也不关心 session 文件怎么保存。
它关心:
- 当前
messages - 当前
tools - 当前
systemPrompt - 是否正在 streaming
- steer/followUp 队列
- 如何启动
runAgentLoop()
agent-loop
agent-loop 是核心执行函数。它负责一轮或多轮模型调用和工具执行。
它关心:
- 把
AgentMessage[]转成 LLM message - 调用 provider stream
- 接收
text/thinking/toolCall/done - 执行工具
- 把
toolResult加回上下文 - 判断是否继续或结束
关键理解
1. AgentSession.prompt() 不直接请求模型
我们认为它主要做输入进入 agent 前的预处理和调度。
它会:
- 处理
/command - 触发 extension input hook
- 展开 skill command 和 prompt template
- streaming 时走 steer/followUp 队列
- 检查 model 和 auth
- 组装 user message
- 调用
_runAgentPrompt(messages)
最终才进入底层 agent.prompt()。
2. Agent.prompt() 不是模型调用
Agent.prompt() 启动的是一次 agent run。
它会先构造:
context snapshotsystemPromptmessagestoolsloop configmodelreasoninggetApiKeystreamFnconvertToLlmtransformContextbeforeToolCallafterToolCallgetSteeringMessagesgetFollowUpMessages
然后交给 runAgentLoop()。
3. runLoop() 有两层 while
内层 while 处理当前连续推理链:
- pending/steering message
- assistant response
- tool call
- tool result
- 下一次 assistant response
外层 while 处理 agent 准备停下之后的 follow-up:
- 当前工具链和 steering 都结束后
- 再检查 follow-up
- 如果有 follow-up,把它作为下一轮 pending message 继续跑
4. pi agent 是事件驱动循环
本节最终结论:
一次 prompt 会触发持续的事件流,并在 runLoop 里反复执行
“模型生成、工具调用、工具结果回填、继续生成”,
直到没有工具、steering 或 follow-up 才结束。
这就是为什么它不是普通的一问一答。
实操观察
我们建议用 JSON mode 观察事件流:
pi --mode json -p "Read README.md and summarize it"
你实际看到的前半段事件包括:
session
agent_start
turn_start
message_start(user)
message_end(user)
message_start(assistant)
message_update(thinking_start)
message_update(thinking_end)
这些事件对应源码中的:
runPrintMode()输出 session headerrunAgentLoop()发出agent_startrunLoop()发出turn_start- user message 被加入上下文并发出 start/end
streamAssistantResponse()创建 partial assistant message- provider 流式返回 thinking/text/toolcall 更新
如果模型实际调用工具,还会继续看到:
message_update(toolcall_start / toolcall_delta / toolcall_end)
tool_execution_start
tool_execution_end
toolResult
turn_end
agent_end
这些事件的初学者读法:
session:会话信息 header,不是 agent loop 事件。agent_start:一次 agent run 开始。turn_start:一轮模型/工具循环开始。message_start:一条 user 或 assistant message 开始。message_update:assistant message 的增量更新,可能是 thinking、text 或 toolCall。tool_execution_start:本地工具开始执行,说明完整 toolCall 已经生成并通过校验。tool_execution_end:本地工具执行结束,成功和失败都会进入后续结果处理。turn_end:当前 turn 结束,不代表整个 agent run 一定结束。agent_end:本次 prompt 对应的 agent run 完成。
关键闭环:
message_update(toolCall 增量)
-> 完整 assistant message
-> tool_execution_start/end
-> toolResult 回填到 Context.messages
-> 下一轮模型调用继续推理
本节完成度
已完成:
- 找到
pi命令入口。 - 理解
main.ts的 mode 分发。 - 理解 interactive、print、json、rpc 是外壳差异,不是不同 agent 核心。
- 理解
AgentSession.prompt()的会话层职责。 - 理解
Agent.prompt()的执行层职责。 - 理解
runLoop()的内外循环。 - 用 JSON mode 观察到真实事件流。
未展开,后续章节继续:
- 默认工具如何定义和暴露给模型。
- provider 如何把模型 payload 转成统一 stream event。
read/write/edit/bash的执行细节。- session tree、compaction 和 branching。
- TUI 如何订阅事件并渲染。
下一节方向
下一节进入 02 模型接口:理解 tool calling。
目标是读懂:
packages/ai的Context- provider stream event
- assistant message 中的
toolCall - tool result 为什么必须回填到上下文
convertToLlm()为什么是 agent 层和模型层的边界
Source: 01-entry-runtime-notes.md