# 01 使用入口学习记录

## 本节目标

本节目标是先把 pi 的入口链路跑通，不急着深入所有源码。重点理解一次用户输入如何从 CLI 进入会话层，再进入 agent 执行层，最后由 agent loop 产生模型输出、工具调用和事件流。

## 我们怎么学的

1. 先从仓库结构判断模块分层。
2. 再追 `pi` 命令入口，确认 `bin.pi` 指向 `dist/cli.js`，源码入口是 `packages/coding-agent/src/cli.ts`。
3. 继续读 `main.ts`，确认它负责参数解析、模式选择、session/runtime 创建。
4. 选择 `print mode` 作为第一条主线，因为它比 interactive mode 更干净。
5. 追到 `AgentSession.prompt()`，理解它不是直接请求模型，而是做输入预处理和调度。
6. 追到 `Agent.prompt()`，理解它启动一次 agent run，并构造 context snapshot 和 loop config。
7. 最后读 `runLoop()`，理解 pi agent 的核心不是一问一答，而是事件驱动的循环。

## 已确认的主链路

```text
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.json`
  - `bin.pi = dist/cli.js`
- `packages/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：

```bash
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 核心。

```mermaid
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 的事件时序图

```mermaid
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 snapshot`
  - `systemPrompt`
  - `messages`
  - `tools`
- `loop config`
  - `model`
  - `reasoning`
  - `getApiKey`
  - `streamFn`
  - `convertToLlm`
  - `transformContext`
  - `beforeToolCall`
  - `afterToolCall`
  - `getSteeringMessages`
  - `getFollowUpMessages`

然后交给 `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 是事件驱动循环

本节最终结论：

```text
一次 prompt 会触发持续的事件流，并在 runLoop 里反复执行
“模型生成、工具调用、工具结果回填、继续生成”，
直到没有工具、steering 或 follow-up 才结束。
```

这就是为什么它不是普通的一问一答。

## 实操观察

我们建议用 JSON mode 观察事件流：

```bash
pi --mode json -p "Read README.md and summarize it"
```

你实际看到的前半段事件包括：

```text
session
agent_start
turn_start
message_start(user)
message_end(user)
message_start(assistant)
message_update(thinking_start)
message_update(thinking_end)
```

这些事件对应源码中的：

- `runPrintMode()` 输出 session header
- `runAgentLoop()` 发出 `agent_start`
- `runLoop()` 发出 `turn_start`
- user message 被加入上下文并发出 start/end
- `streamAssistantResponse()` 创建 partial assistant message
- provider 流式返回 thinking/text/toolcall 更新

如果模型实际调用工具，还会继续看到：

```text
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 完成。

关键闭环：

```text
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 层和模型层的边界
