02 · 核心概念
这是 pi-web 的概念地图:抓住下面这几个概念,后面每一章都能对号入座。
本章只画地图、不深潜。凡是给了「详见」跳转的地方,都会在对应专章展开代码级细节。
Agent Source(agent 源)
一个 agent source 是 pi-web 载入对象的入口描述,可以是:
- 一个本地目录(绝对路径),
- 一个 git 源(解析/拉取后落到本地目录)。
源解析(agent-source-resolver spec)做三件事:
- 解析目录或 git → 本地工作目录;
- 入口探测(
entry-probe.ts)— 优先package.json#pi-web.entry覆盖,否则按index.ts>index.js>index.mjs取首个存在者;都没有则无入口; - 双模式判定 + 信任策略 → 生成一份
spawnSpec(子进程怎么起,由@blksails/pi-web-protocol定义类型)。
上手:把一个本地目录当 agent source
examples/hello-agent 是一个最小 custom agent(目录里有 index.ts,默认导出一个 AgentDefinition)。把它设为默认源起服务:
# custom 模式:examples/hello-agent 目录内有 index.ts,被判定为 custom 入口
PI_WEB_DEFAULT_SOURCE="$PWD/examples/hello-agent" pnpm dev
# 浏览器打开 http://localhost:5173,新建会话即以该源起一个常驻子进程PI_WEB_DEFAULT_SOURCE由server/bootstrap.ts:73(经lib/app/config.ts:97)读取,缺省是builtin:default-agent。pnpm dev是scripts/dev-all.mjs,并发拉起 API server(:3000)与 Vite dev server(:5173);开发期浏览器打开:5173,/api请求由 Vite 代理到:3000。hello-agent刻意省略model,会继承~/.pi/agent/settings.json的默认 provider/model —— 任意pi登录都能开箱跑通。
预期结果:选源页里出现该源,新建会话后能与 agent 对话并触发它的 echo 工具。
双模式载入(Dual-mode)
| 模式 | 触发条件 | spawn 目标 |
|---|---|---|
| custom | 探测到入口(index.ts/js/mjs 或 pi-web.entry 覆盖) | bootstrap runner(node <runner-bootstrap.mjs> --agent <entry> --cwd <work>):jiti 载入用户入口 → 归一化为 AgentDefinition → createAgentSessionRuntime → runRpcMode |
| cli | 源里无入口 | pi CLI:node <piCliEntry> --mode rpc(工作目录经 spawnSpec.cwd 设置,pi CLI 无 --cwd 标志) |
关键决策:两模式对外是同一套 RPC 协议。 底层 RPC 实现完全相同,前后端桥接完全复用,只是 spawn 的目标进程不同。这让 pi-web 既能跑任意自定义 agent,也能把通用 pi coding agent 当 Web 服务提供,而无需两套前端。
custom 模式怎么写入口、归一化为
AgentDefinition,见 08 自定义 Agent 开发;cli 模式与全局pi-web命令行见 18 CLI。
Session(会话)
一个会话 = 一个常驻 agent 子进程。
- 建会话(
POST /api/sessions)→ 解析源 → spawn 子进程 → 返回sessionId; - 会话期间该进程常驻,前端经 SSE 订阅其事件流;
PiSession(session-enginespec)负责事件广播、生命周期、以及扩展 UI 挂起表(权限弹窗等待用户响应)。
会话注册表是 SessionStore 接口(packages/server/src/session/session-store.ts:12),默认实现 InMemorySessionStore(内存,同文件 :39),但接口外置——为未来 Redis / Durable Object 等分布式后端预留接缝。
一会话一进程 + SSE 长连接 = 有状态服务。这是 pi-web 不能跑 Serverless/Edge、横向扩容需按
sessionId粘性路由的根本原因。详见 03 系统架构。
历史会话可在 会话列表里浏览并一键恢复(按
sessionId重新订阅其事件流),详见 14 会话列表。
会话生命周期状态(就绪握手)
子进程「已 spawn」不等于「能接收 prompt」:pi 事件流里没有 session_start / ready 锚点,服务端一旦标记会话可用,往往早于 agent 真正能处理命令。pi-web 为此定义了一层与通道活动态(active/stopping/stopped)正交的业务就绪态:
| 生命周期态 | 含义 |
|---|---|
initializing | 子进程已起、就绪探针尚未成功(默认初态,失败安全:未确认即不可发送)。 |
ready | 就绪探针首条响应到达,agent 可接受 prompt。 |
error | 探针超时 / 子进程就绪前早退,会话不可用。 |
ended | 正常停止 / 就绪后子进程退出。 |
- 服务端以只读探针
getCommands()的首条响应判定真实就绪(packages/server/src/session/pi-session.ts:447-471)。 - 就绪态经一条粘性
control: session-status帧广播;新订阅者订阅时会回放当前态,防止早期帧丢失(packages/protocol/src/transport/session-status.ts:21-41)。
前端据此决定「输入框何时可发消息」;断线重连时也靠回放拿回当前态。这是理解「会话何时可用」的基础概念。
RPC 通道(PiRpcChannel)
后端核心是一条传输无关的 RPC 通道:
// packages/server/src/rpc-channel/pi-rpc-channel.ts
interface PiRpcChannel {
send(line: string): void; // 写一行 JSONL 到下游(local 即子进程 stdin)
onLine(cb: (line: string) => void): Unsubscribe; // 注册按行回调,返回取消订阅句柄
close(): Promise<void>; // 关闭通道并干净退出
health(): ChannelHealth; // 查询通道健康(alive / exitCode / signal)
}PiRpcProcess(packages/server/src/rpc-channel/pi-rpc-process.ts)是它的 local 实现(基于node:child_processspawn);SpawnSpec(子进程怎么起)由@blksails/pi-web-protocol拥有并导出,是单一事实来源;- 通道抽象为未来 e2b / ssh / device 等远程 host 预留。
子进程通信用 JSONL framing:严格按 \n 切、剥 \r,禁用 Node readline(它会误切 U+2028/2029)。消息分三类:response(命令应答)、event(流式事件)、extension_ui_request(扩展 UI 请求,如权限弹窗)。
pi-web 不直接用 SDK 内置的
RpcClient——它写死 spawnpi且未暴露 extension UI 子协议。pi-web 自写PiRpcProcess处理这三类消息。
两条正交的通信平面
RPC 通道之上,pi-web 有两条彼此正交的跨进程通信平面。建立起这个心智模型,很多设计就顺了:一条是对话流(聊天消息的单向渲染),另一条是权威表面(领域状态 + 命令的双向 CQRS 约定)。
平面一:事件 → UIMessage 翻译层(对话流)
这是聊天的枢纽。agent 子进程发出的 RPC 事件(文本增量、思考、工具调用、工具结果……)经翻译层转换为 AI SDK v5 的 UIMessage data-part,再经 SSE 推给浏览器的 useChat。
- 后端 RPC 桥用对真实子进程的集成测试保障;
- 前端翻译层是纯函数,用单元测试覆盖。
这条平面是单向的:agent → 浏览器,渲染一段会话消息流。
平面二:Surface 权威表面(领域状态 + 命令)
有些富交互 UI(如 Canvas 画布)不适合塞进聊天消息流:它需要一份结构化领域状态实时下行到前端,同时前端要能对该领域发起命令——且命令走结构化通道,不经 LLM。pi-web 用 Surface 权威表面范式承载这一场景:
- 权威快照在 agent 子进程(单写者,CQRS):状态经
control: "state"帧下行镜像到前端(packages/tool-kit/src/surface/create-surface.ts); - 命令从前端上行:
useSurfacehook 经 ui-rpc 通道发结构化命令给 agent 执行(packages/react/src/hooks/use-surface.ts); - 一个
domain(如canvas)= 一份权威表面。
这与平面一正交:聊天流渲染消息,Surface 平面同步领域状态。Canvas 工作台就端到端建立在这条平面之上(
domain=canvas)。概念足矣,createSurface/wireSurfaceBridge/useSurface的完整 API 见 04 Surface 权威表面栈。注:pre-spec 设计草案里把这套范式称作 AAS(Agent-Authoritative Surface);在 main 上它以 Surface 栈的形态实现,AAS 仅作设计词汇出现。
平面二的地基:状态注入桥(双向共享 KV)
Surface 之下是一条更基础的会话级共享 KV,权威同样在 agent 子进程:
- 下行(agent → UI):权威 KV 变更经 SSE
control: "state"帧镜像到前端,每 key 带单调递增rev(前端据此丢弃乱序/过期帧,packages/protocol/src/web-ext/state.ts:14-24); - 写回(UI → agent):前端经
POST /sessions/:id/state(packages/server/src/http/create-handler.ts:172)写回,同步 ack; - 作者面:agent 工具内经
getSessionState()(seam__piWebSessionState__,packages/tool-kit/src/session-state.ts:15)读写这份状态,供人机共读写。
examples/state-bridge-agent 就是这条桥的端到端示例(AI 用 increment/read_state 工具,人在 UI 里点按钮写回,双方看同一份实时状态)。写回端点与 control:"state" 帧的完整契约见 24 HTTP/SSE API 参考,作者面用法见 08 自定义 Agent 开发。
SSE 帧:数据帧与控制帧
前后端经 SSE(Server-Sent Events) 传流式数据,每帧携带 protocolVersion。顶层帧以 kind 判别两类(packages/protocol/src/transport/sse-frame.ts):
kind: "uiMessageChunk"— 数据帧,内嵌UiMessageChunk(text / reasoning / tool / data-part),直接喂 AI SDK(对话流平面);kind: "control"— 旁路控制帧,内层再以control判别。控制帧不止一类,概念上要记住这几种:session-status— 会话就绪握手(上文生命周期态,粘性);state— 状态注入桥 / Surface 的权威 KV 下行镜像;ui-rpc— Tier3 扩展 UI ↔ agent 的下行响应;- 另有
error/queue/stats/logs/session-state等。
早期文档曾说「SSE 只推 UIMessage」,这是不完整的——控制帧平面同样真实广播。
@blksails/pi-web-protocol是稳定契约,类型/schema 改动需语义化版本管理。控制帧的**完整枚举与「哪些真实发送」**见 24 HTTP/SSE API 参考。
附件的两条路径(概念预览)
附件不进 pi 协议,全在 pi-web 层。核心是**「引用而非 base64」**:历史与 context 里只放 att_<id> 引用,base64 仅在两个出口物化:
- 喂 LLM 识别(vision)— 上传图在该出口转 base64 给模型;
- 交 server 端 tool — 文件经
attachmentId参数在 runner 子进程内resolve为 path/url/bytes,工具产出再落库回流。
详见 09 附件系统。
其它能力面(概念预览)
除了对话流与 Surface,两块大能力面在此先点一句、留跳转:
- Web UI 扩展(5-tier) — agent 可经声明式扩展往 Web UI 的 21 个协议插槽挂内容(从内联小组件到隔离 iframe 表面
artifactSurface),是 pi-web 让 agent「长出 UI」的机制。注意它与上文 Surface 平面正交:5-tier 讲挂载位置,Surface 讲通信约定。详见 12 Web UI 扩展。 - Canvas 工作台 — 一个二创画布编辑器(舞台缩放/平移、工具轨、掩码标注、版本条、画廊、vision「解读」按钮),建立在 Surface 平面之上(
domain=canvas),由环境变量门控、默认关闭。详见 16 Canvas 工作台。
配置目录 ~/.pi/agent
凭据与默认值的来源:
auth.json— provider 凭据(pi登录后生成);settings.json— 默认 provider/model 等;models.json— 自定义 OpenAI-compatible provider(见 07 Provider 与模型)。
可经 PI_WEB_AGENT_DIR / PI_CODING_AGENT_DIR 覆盖目录。注意环境变量名是 PI_CODING_AGENT_DIR(不是 PI_AGENT_DIR)。
三个不变式(贯穿设计)
记住这三条,很多设计决策就自洽了:
- 单一身份 — 每个附件一个
att_<id>,全链路同一身份空间(含工具产出物)。 - 先落库后引用 — 任何附件先入对象存储拿到 id,再被消息/工具引用。
- base64 仅具名出口物化 — 平时只传引用,base64 只在「喂 LLM」与「工具读取」两个明确出口短暂出现,以省 context。
下一步 / 相关
- 这些概念如何落到层与数据流上 → 03 系统架构
- Surface 权威表面 / 状态注入桥的完整 API 与 Canvas 实例 → 04 Surface 权威表面栈
- 提到的
@blksails/pi-web-protocol、@blksails/pi-web-server等包边界 → 05 分层包 - 给自己的 agent 套 UI(custom 模式入口)→ 08 自定义 Agent 开发
- 附件三不变式的完整实现 → 09 附件系统
- 把通用 pi agent 当 Web 服务起(cli 模式 /
pi-web命令)→ 18 CLI - SSE 帧与控制帧的完整枚举、
POST /sessions/:id/state端点 → 24 HTTP/SSE API 参考 - 不熟的名词随时查 → 26 术语表