docs: 实施设计第 9 节按 ADR-0006 重写(引擎长驻子进程)

原第 9 节写的是进程内 new Agent() 的接法,与 ADR-0006 冲突。重写为:为什么子进程、平台装 npm 只为拿 RpcClient 与类型、五块实现拆分、一次对话链路、进程模型(一进程一会话 + 空闲回收待定 + 进程复用暂不做)、工具面扩展桥、接入方式对比、推进顺序。同时订正架构设计 5.2 对比表里 Pi「驱动=进程内库」「工具面=进程内注册工具」两处过期描述。
This commit is contained in:
zh 2026-09-24 11:33:34 +08:00
parent f9e7923982
commit 84e4d6b504
2 changed files with 86 additions and 48 deletions

View File

@ -286,8 +286,8 @@ type RuntimeEvent =
| 部件 | Pi | Codex | 共同契约 |
|-|-|-|-|
| 驱动 | 进程内库 | 子进程 app-server | AgentRuntime 接口 |
| 工具面 | 进程内注册工具 | MCP 服务,并关掉它自带的 shell 与文件工具 | 两边都落到 Core.invoke |
| 驱动 | 长驻子进程(RpcClient 驱动,JSONL 协议) | 子进程 app-server | AgentRuntime 接口 |
| 工具面 | RPC 无注册工具通道,靠 Pi 侧扩展桥回调平台 | MCP 服务,并关掉它自带的 shell 与文件工具 | 两边都落到 Core.invoke |
| 事件 | subscribe 回调 | 协议事件流 | 统一的 RuntimeEvent |
| 审批 | 不内置,由 Core 出确认卡 | 自带审批,需映射到 Core 确认卡 | 统一的审批请求与响应 |
| 会话 | 平台存 | 平台存,镜像它的事件 | 平台自己的会话格式 |

View File

@ -3,6 +3,8 @@
> **来源**:飞书文档([原文](https://scneju2wtbd7.feishu.cn/docx/SNW8dLae7ozvBVxY7CDc2pLSnEe),A0-G 评审快照,2026-09-23 导入)
> **权威版本**:本仓库。导入后飞书副本停止更新,改动走仓库提交与 ADR 流程。
> **A0.10 补记**:1.4 已补入单机 SQLite 文件路径与跨库写法约定(ADR-0007);飞书副本未同步。
> **2026-09-24 修订(A3.1 前置)**:第 9 节按 ADR-0006 重写——引擎长驻子进程、平台装 npm 包只为拿 RpcClient 与类型、新增进程模型与扩展桥说明;原「进程内
ew Agent()」的写法作废。
承接《多系统智能体平台 · 架构设计》,把落地问题定到“谁做、放在哪、走什么流程”。本文只写落地约束,不重复分层原理。
@ -435,16 +437,20 @@ Provider 先写适配器,再以包的形式安装和管理。开发步骤与
# 9 引擎怎么接入
架构设计只定了边界与契约,这里写怎么落地。
架构设计只定了边界与契约,这里写怎么落地。**引擎以长驻子进程运行**(ADR-0006):平台拉起进程、发提示、收事件、发取消,不做进程内嵌入。
Pi 以 npm 包形式发布(MIT),直接装进 monorepo 当依赖,不需要 git submodule。agent-core 只提供智能体循环与事件流,工具由我们自己注册,默认不带文件、shell 这类工具。**硬前提:**Node ≥ 22.19.0,部署镜像要跟上。
## 9.1 为什么是子进程
| 接入方式 | 适用场景 | 说明 |
|-|-|-|
| npm 依赖(推荐) | 常规开发 | 装 @earendil-works/pi-agent-core 与 pi-ai,锁定版本;升级就是改 package.json |
| 改源码:submodule 或 fork | 必须改 Pi 源码时 | 构建与升级成本高,还要自己维护 patch,并持续跟上游同步 |
| 子进程跑 pi-coding-agent CLI | 不建议 | 它自带 read / bash / edit / write 工具,等于给模型开了本机 shell |
| Python 平台 + Node sidecar | 平台栈必须是 Python 时 | 把 Pi 包成独立 agent 服务,Core 仍只通过 Core.invoke 与它交互 |
| 理由 | 说明 |
| --- | --- |
| Codex 只能子进程 | Rust 二进制,无法进程内嵌入 |
| 不占平台事件循环 | 智能体循环在独立进程,长任务不拖累其它会话的响应 |
| 进程模型统一 | 两个引擎一致,进程管理(启停、超时、崩溃重启、日志轮转)只写一套 |
| 历史在平台 | 平台保存会话与执行记录,引擎进程可以随时重启并按历史恢复 |
## 9.2 平台装什么依赖
平台依赖 Pi 的 npm 包,目的是**拿 `RpcClient` 与类型**,不是把 Agent 起在平台进程里。循环跑在子进程。
```json
{
@ -457,52 +463,84 @@ Pi 以 npm 包形式发布(MIT),直接装进 monorepo 当依赖,不需
}
```
**创建 Agent(位于 server/agent):**
**硬前提:** Node ≥ 22.19.0,部署镜像要跟上。
## 9.3 实现拆成五块
> ADR-0006 只定边界(进程模型、统一接口、工具面、遗留项),下面五块是按该决策拆出的实现视图。
| 模块 | 位置 | 职责 |
| --- | --- | --- |
| 引擎接口 | `server/agent/runtime.ts` | `AgentRuntime` / `RuntimeSession` / `RuntimeEvent`;平台只认这三个类型 |
| 进程管理 | `server/agent/process.ts` | 拉起、守护、崩溃重启、超时、日志轮转、父进程看门狗;两个引擎共用 |
| Pi 适配 | `server/agent/pi/adapter.ts` | 用官方 `RpcClient` 建会话、发提示、订阅事件、发取消 |
| 事件映射 | `server/agent/pi/events.ts` | Pi 事件 → `RuntimeEvent` → 协议 v1 的 `PlatformEvent`(见 `docs/spec/protocol.md`) |
| 工具桥 | `server/agent/pi/tool-bridge.ts` + Pi 侧扩展 | 扩展注册工具 → 回调平台 loopback → `Core.invoke` |
对应任务:接口与假引擎 A3.1 / A3.2;子进程接入 A3.3;工具桥 A3.4;进程管理 A3.10;扩展桥 A3.11。
## 9.4 一次对话的链路
1. 应用服务按身份取能力清单,过滤后作为工具描述交给适配层
2. 适配层拉起(或复用)Pi 子进程、建会话、注入 system prompt + 工具集 + 平台保存的历史
3. 发送用户消息,Pi 流式回事件
4. Pi 要用工具 → 扩展发本地 HTTP 到平台 → 平台走 `Core.invoke` → 回执回给扩展
5. 平台把事件映射成 `PlatformEvent`:落库、经 SSE 推给客户端
6. 取消:平台发 cancel,Pi 结束这一回合,推 `run.state = idle`
三条纪律(架构设计 5.1):工具只来自能力目录、每次都过 `Core.invoke`、凭据不进工具参数与模型上下文。
## 9.5 进程模型
| 项 | 首版做法 | 原因 |
| --- | --- | --- |
| 进程粒度 | **一个会话一个引擎进程** | 会话间不串状态,崩溃互不影响 |
| 空闲回收 | A3.10 定:空闲时长与进程数上限 | 平台持有历史,进程随时可杀,下次按历史重建 |
| 进程复用 | 首版不做 | Pi 支持会话切换、Codex 支持多线程;进程数成为负担时再评估 |
先例数据:ChatGPT 客户端在本机实测 54 个进程、约 3.25 GB。工厂现场的单机配置要评估,这也是把空闲回收放进 A3.10 的原因。
## 9.6 工具面:Pi 需要扩展桥
事件面两个引擎一致,差异集中在工具面:
| 引擎 | 工具怎么进 | 状态 |
| --- | --- | --- |
| Codex | MCP 或实验性动态工具 | 切换时再写 |
| Pi | RPC 命令集没有「注册工具」通道,必须在 Pi 侧写扩展,把工具调用以本地 HTTP 回调平台 | A3.11 |
扩展桥的鉴权复用平台既有模式:loopback 随机端口 + nonce 注入 + origin 校验。原型可参考旧仓库 `poc/pi-aps/extensions/aps.ts`(只读、baseUrl + token + 超时)。
```typescript
// server/agent/src/runtime.ts
import { Agent } from "@earendil-works/pi-agent-core";
import { createModels } from "@earendil-works/pi-ai";
import { anthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
const models = createModels();
models.setProvider(anthropicProvider()); // 也可换成 OpenAI 兼容端点
const model = models.getModel("anthropic", "claude-sonnet-4-6");
export function createAgent(core: Core, subject: Subject, caps: Capability[]) {
const agent = new Agent({
initialState: { systemPrompt: SYSTEM_PROMPT, model },
streamFn: models.streamSimple.bind(models),
// Pi 侧扩展示意:工具不自己发业务请求,只回调平台
registerTool("aps_schedule_preview", { schema }, async (args) => {
const res = await fetch(`${platformBase}/internal/tool-call`, {
method: "POST",
headers: { "content-type": "application/json", "x-nonce": nonce },
body: JSON.stringify({ capabilityId: "aps.schedule.preview", params: args }),
});
agent.state.tools = caps.map((cap) => toTool(cap, core, subject));
agent.subscribe((event) => publishRuntimeEvent(toRuntimeEvent(event))); // 先转成平台事件,界面对引擎无感
return agent;
}
if (!res.ok) throw new Error(`tool bridge failed: ${res.status}`);
return await res.json(); // 平台侧收口到 Core.invoke
});
```
**把 Core 能力包成工具:**工具名用点号转下划线,返回值原样交给模型,失败直接 throw,Pi 会把它当工具错误反馈给模型。
## 9.7 接入方式
```typescript
// server/agent/src/tool-bridge.ts
export function toTool(cap: Capability, core: Core, subject: Subject): AgentTool {
return {
name: cap.id.replace(/\./g, "_"), // aps.schedule.publish → aps_schedule_publish
label: cap.description,
description: cap.description,
parameters: cap.inputSchema,
execute: async (_id, params, _signal, onUpdate) => {
const receipt = await core.invoke({ subject, capabilityId: cap.id, params });
if (receipt.status === "failed") throw new Error(receipt.errorKind);
if (receipt.status === "accepted")
onUpdate?.({ content: [{ type: "text", text: "已受理,执行中" }], details: receipt });
return { content: [{ type: "text", text: JSON.stringify(receipt) }], details: receipt };
},
};
}
```
| 方式 | 适用场景 | 说明 |
| --- | --- | --- |
| npm 依赖(推荐) | 常规开发 | 装包是为了 `RpcClient` 与类型;**循环跑在子进程**,不在平台进程内 |
| 改源码:fork | 必须改 Pi 源码时 | 构建与升级成本高,要自己维护 patch 并跟上游同步 |
| 子进程跑 pi-coding-agent CLI | 不建议 | 它自带 read / bash / edit / write,等于给模型开了本机 shell |
| 进程内嵌 Agent | 不采用 | ADR-0006 已否;会占平台事件循环,且与 Codex 的进程模型不一致 |
**推进方式:**现在只做三件事——定义引擎接口、守住三条纪律、写一个假的引擎实现跑一致性测试,约 3–5 天;Codex 驱动与工具面桥接等到真要切换时再写,约 2–4 周。
## 9.8 推进顺序
| 步 | 内容 | 工期 |
| --- | --- | --- |
| 1 | 定义引擎接口、守住三条纪律、写假引擎跑一致性测试 | 3–5 天(A3.1 / A3.2) |
| 2 | Pi 子进程接入 + 工具桥 + 扩展桥 | A3.3 / A3.4 / A3.11 |
| 3 | 进程管理与空闲回收 | A3.10 |
| 4 | Codex 驱动与工具面切换 | 真要切换时再写 |
# 10 配套资源
架构分层与目录结构见架构设计的第 1–3 节,引擎适配与 Provider 契约见第 5–6 节;本项目的任务分解、优先级、验收标准与推进状态见下面的任务表,默认按阶段分组查看。