aps-agent/docs/implementation.md

553 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多系统智能体平台 · 实施设计
> **来源**:飞书文档([原文](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()」的写法作废。
承接《多系统智能体平台 · 架构设计》,把落地问题定到“谁做、放在哪、走什么流程”。本文只写落地约束,不重复分层原理。
章节按从基础到高级排列:先看数据与界面,再看 Provider 扩展,最后看多 Sub-agent 协作。
# 总览
先看两张图:一张是用户实际怎么用,一张是系统怎么搭。后面的章节都是这两张图的展开。
## 用户使用流程(桌面端)
```mermaid
flowchart TB
A["打开桌面端"] --> B["选项目 或 新建会话"]
B --> C["用自然语言说出目标"]
C --> D["服务端:Pi 理解目标并选能力"]
D --> E{"只读 还是 写操作"}
E -->|只读| F["工作区展示表格 / 甘特 / 图表"]
E -->|写入| G["生成变更草案"]
G --> H["确认卡:影响范围 · 参数 · 版本"]
H -->|拒绝| I["记录并说明原因"]
H -->|批准| J["Core 执行并留痕"]
J --> K["回执与证据回到对话"]
F --> L["追问 / 对比 / 导出"]
K --> L
classDef start fill:#ffffff,stroke:#9aa7b8,color:#243247;
classDef ui fill:#f5f8fc,stroke:#9aaec7,color:#243247;
classDef gate fill:#fff1e7,stroke:#f97316,color:#243247;
class A,B,C ui;
class E,H gate;
class D,F,G,I,J,K,L start;
```
关键点:只读直接出结果,写入一律先出草案再出确认卡;批准后由 Core 执行,回执与证据回到对话,不留在后台。
## 整体架构
```mermaid
flowchart TB
subgraph CLIENT[客户端]
DESK["桌面端 · Electron
界面 · 工作区 · 本地能力"]
WEBUI["浏览器"]
end
subgraph SERVER[服务端 · 引擎中立的平台服务]
APP["应用服务
会话 · 编排 · 能力查询 · 事件推送"]
ENGINE["引擎适配
统一接口:Pi 或 Codex"]
CORE["平台 Core
身份 · 策略 · 审批 · 执行 · 审计"]
SUITE["Provider 接入
契约 · 路由 · 适配"]
end
subgraph DATA[数据层]
PG[("PostgreSQL
项目 · 执行 · 审批 · 审计")]
JSONL[("会话记录
服务端权威,本地离线副本")]
OBJ[("文件存储
附件 · 产物")]
end
APS["APS 系统"]
ERP["ERP 系统"]
EMS["EMS 系统"]
DESK --> APP
WEBUI --> APP
APP --> ENGINE
ENGINE --> CORE
CORE --> SUITE
SUITE --> APS
SUITE --> ERP
SUITE --> EMS
APP --> PG
APP --> JSONL
APP --> OBJ
classDef ext fill:#fafafa,stroke:#7f8c9c,color:#243247,stroke-dasharray:5 4;
class APS,ERP,EMS ext;
```
\>
接入层只做界面;平台服务里入口、运行时、控制面、Provider 套件四段分开;数据层按结构化数据、会话文件、文件资产三类存放。桌面端与浏览器共用同一套平台服务。
# 1 数据管理
对象分五类:项目、会话、设置、工作区、运行与证据。
| 对象 | 作用域 | 关键内容 |
|-|-|-|
| 用户与租户 | 全局 | 身份、角色、所属租户 |
| 项目 | 租户内 | 工作目录、成员与角色(owner / editor / viewer)、业务上下文 |
| 会话 | 个人或项目 | 消息、工具调用、结论;个人会话私有且只读,写操作必须在项目会话内 |
| 工作区 | 用户 × 项目 | 打开的视图集合、当前选择、会话;可恢复上次状态 |
| 设置 | 系统 / 租户 / 项目 / 用户 / 会话 | 后一级覆盖前一级;会话级只在本次有效 |
| 运行与执行 | 会话内 | executionId、参数摘要、幂等键、状态、回执 |
| 审批 | 项目内 | 请求、一次性授权、决策事件 |
| 审计 | 全局 | 哈希链,只追加不可删 |
| 文件 | 项目内 | 元数据入库,内容放对象存储或本地目录 |
| 存储 | 放什么 | 备注 |
|-|-|-|
| PostgreSQL | 项目、权限、设置、运行、审批、审计等结构化数据;服务端模式的会话消息也在库里 | 桌面模式用 SQLite 承接同类数据 |
| 对象存储或本地目录 | 上传的文件、导出物 | 数据库只存元数据与校验和 |
| 会话 JSONL(桌面) | 每个会话一个文件,消息与工具调用按行追加 | 放 \~/.aps/sessions 下,见 1.4 |
| 桌面端 \~/.aps | 本机配置、缓存、日志、Provider 包 | 与服务器同构,便于同步 |
**四条规矩:**审计只追加不修改;写操作必须有项目归属(个人会话只读,写操作一律在项目会话内,因为审批、审计与证据都要求项目归属);会话删除是软删,运行与证据保留;文件删除要过审批。
## 1.1 会话里都有什么
| 内容 | 存什么 | 放哪 | 生命周期 |
|-|-|-|-|
| 消息 | 角色、内容块、时间、token 数 | 桌面 JSONL / 服务端 PostgreSQL,只追加 | 随会话软删 |
| 内容块 | 文本 / 图片 / 附件引用 / 工具调用 / 审批卡 / 视图引用 | 随消息 | 同上 |
| 上下文快照 | 实际发给模型的消息集合与压缩摘要 | 随会话文件 / 服务端库 | 用于复盘“当时模型看到了什么” |
| 工具调用 | capabilityId、参数摘要、结果引用、executionId | 随消息 | 只追加 |
| 审批记录 | 请求、决策、批准人、失效条件 | 关联消息与审批表 | 只追加 |
## 1.2 附件与产物
| 类型 | 来源 | 归属 | 存法 |
|-|-|-|-|
| 附件 | 用户上传 | 项目文件库 | 原文件 + 元数据 + 校验和;消息里只放引用 |
| 产物 | Agent 生成 | 项目 | 带版本号,记录产生它的 executionId,可对比与回滚 |
| 导出物 | 报告 / 表格 / 图纸导出 | 项目 | 发布为文件,保留生成参数与来源快照 |
| 视图快照 | 用户在工作区保存 | 用户 × 项目 | 只存定义与参数,不存渲染结果 |
| 规则 | 说明 |
|-|-|
| 大内容不进消息表 | 消息只放引用、摘要与校验和,正文与二进制进对象存储 |
| 附件按校验和去重 | 同一文件被多个会话引用只存一份 |
| 产物有版本 | 生成物不覆盖历史,能对比前后差异,能回滚到指定版本 |
| 权限跟着项目走 | 离开项目后失去项目文件访问权,个人会话里的引用同样失效 |
| 删除要留痕 | 附件删除需审批;产物保留版本历史,审计不删 |
## 1.3 上下文窗口
不把整段会话塞给模型。按 token 预算取「最近若干轮 + 滚动摘要 + 钉住的引用」,工具结果只进摘要与引用,需要细节时再调对应能力取回。压缩后的摘要与原始消息都保留,任何结论都能追到原始输入。
## 1.4 会话文件格式(服务端为主,本地 JSONL 副本)
架构改成客户端加服务端之后,会话默认由服务端保存;客户端本地用 JSONL 存一份离线副本,断网时可读、可导出。格式与 Pi 的会话文件一致:每个会话一个文件,一行一条 JSON,每行带 id 与 parentId 组成树,支持在同一个文件里原地分支。
| 存储形态 | 桌面端 | 服务端 |
|-|-|-|
| 会话消息 | \~/.aps/sessions/<项目>/<时间戳>\_<会话id>.jsonl | PostgreSQL |
| 会话索引 | 本地 SQLite(列表、搜索、未读) | PostgreSQL |
| 附件与产物 | 文件目录,JSONL 只存引用 | 对象存储 + 元数据表 |
| 执行、审批、审计 | 本地 SQLite,不放 JSONL | PostgreSQL |
| 单机库文件 | ~/.aps/aps.sqlite(与会话 JSONL 同级;备份即拷贝目录) | 不适用(用 PostgreSQL 自身的备份) |
| 导出与交换 | JSONL | JSONL |
**一行 JSON 的字段:**
```json
{"v":1,"type":"header","sessionId":"s-...","project":"...","ts":1757404800000}
{"v":1,"type":"message","id":"a1","parentId":null,"role":"user","ts":...,"content":[...]}
{"v":1,"type":"tool_call","id":"a2","parentId":"a1","capabilityId":"aps.schedule.preview","argsDigest":"sha256:..."}
{"v":1,"type":"tool_result","id":"a3","parentId":"a2","executionId":"exec-...","status":"accepted","jobId":"..."}
{"v":1,"type":"approval","id":"a4","parentId":"a3","approvalId":"apr-...","decision":"approved"}
{"v":1,"type":"summary","id":"a5","parentId":"a3","covers":["a1","a2","a3"],"text":"..."}
```
| 规则 | 说明 |
|-|-|
| 只追加 | 不改历史行;分支靠 id 与 parentId 组成树,不新建文件 |
| 带版本 | 文件头一行是会话元信息,含格式版本;加载时按版本迁移,旧文件可读 |
| 大内容在外 | 附件与二进制不进 JSONL,只放引用、摘要与校验和 |
| 不落敏感 | 参数只写摘要与规范化结果,凭据与敏感明文不落文件 |
| 索引另存 | 会话列表、搜索、未读数走本地 SQLite,不在打开时扫描全部文件 |
| 可直接使用 | 备份、导出、排查直接用文件;服务端导出也用同一格式,本地能打开 |
**为什么不把一切都放文件:**执行记录、审批与审计需要按条件查询、并发写入和防篡改,放进本地数据库更合适;JSONL 只承担会话消息流。共享与多端场景下,会话消息改由服务端 PostgreSQL 承接,本地 JSONL 作为离线副本与导出格式。两种存储实现同一个 SessionStore 接口,上层代码共用。
**跨库写法约定(ADR-0007):**时间统一存整数毫秒;主键由应用侧生成(ULID);JSON 列在 PostgreSQL 落 JSONB、在 SQLite 落 TEXT;金额与工时用整数最小单位;不用触发器、存储过程与数据库专属函数。一套方言无关 schema + 一个迁移工具,禁止两边各写一套。
## 1.5 谁是权威数据源
引擎在开发或部署期选定,会换;数据归属不能跟着换。应用服务持有会话与业务记录,Core 持有执行与审计,引擎的原生状态只用于同引擎恢复。
| 数据 | 权威在谁 | 说明 |
|-|-|-|
| 会话与消息 | 平台(应用服务) | 平台格式是权威;引擎只拿上下文,不拿所有权 |
| 上下文检查点与摘要 | 平台 | 发给模型的上下文由平台构建,压缩结果一并保存,可追溯 |
| 执行、审批与审计 | Core | 与引擎无关,进程重启或换引擎都继续存在 |
| 引擎原生状态 | 对应引擎 | Pi 的内存上下文、Codex 的 thread:只保存原生引用,用于同引擎恢复,不作为业务真相 |
| 客户端本地副本 | 平台(副本) | 桌面端 JSONL 只作离线读取与导出,冲突以服务端为准 |
# 2 界面设计
视觉与布局沿用现有 APS 项目的设计系统,不另起一套。现有界面已经跑过对话、视口、审批卡、证据链和桌面壳,直接继承比重新发明便宜,也让团队不用学第二套规范。
## 2.1 设计系统(沿用)
| 维度 | 规范 |
|-|-|
| 色彩 | 浅底 #FAFAF8 / 暗色 #1B1B1F;唯一强调色为工业橙 #F97316,只用于主操作与激活态;红黄绿只表达状态,不做装饰 |
| 侧栏 | 浅色微透明,激活项用浅色胶囊,层级靠明度差不靠边框 |
| 字体 | 系统栈 + Noto Sans SC;正文 14px / 1.65;数字用 tabular-nums 对齐 |
| 圆角 | 卡片 12px、对话气泡 16px、按钮与输入框 10px、胶囊 999px |
| 阴影与边框 | 低海拔柔和阴影,边框仅 1px 极浅色,不叠双重描边 |
| 间距 | 8pt 网格:8 / 12 / 16 / 24 / 32 |
| 暗色模式 | 跟随系统并支持手动切换,语义色配对暗色变体 |
| 动效 | 150–250ms ease-out;输出流式呈现;左说右动时右侧目标元素脉冲高亮一次 |
| 数据呈现 | KPI 用大数字卡片;表格弱化网格线;甘特用低饱和色板加悬浮细节卡 |
| 空态与加载 | 空态给引导与快捷指令;长任务给骨架屏与可取消进度 |
## 2.2 三栏布局(沿用)
桌面端与 Web 端同一套界面,只是外壳不同。右栏在现有项目里是排产视口,这一版泛化为工作区,可以承载任意 Provider 视图。
```text
┌──┬───────────────┬──────────────────────┬───────────────────────────┐
│ │ 项目/会话面板 │ 中:会话区 │ 右:工作区 │
│导│ ───────────── │ ┌────────────────┐ │ ┌─────────────────────┐ │
│航│ ▸ 新建会话 │ │ 面包屑:项目/会话│ │ 表格 / 甘特 / 图表 │ │
│轨│ ▸ 项目列表 │ │ 对话流(UI块) │ │ 对比 / 文件 / 网页 │ │
│ │ ▸ 待审批 │ │ 审批卡 / 证据链 │ │ 运行记录 │ │
│56│ ▸ 工作区 │ │ ────────────── │ │ │ │
│px│ ▸ Provider │ │ 输入框(多模态) │ │ │ │
│ │ ▸ 设置 │ │ │ │ │ │
└──┴───────────────┴──└────────────────┘──┴──└─────────────────────┘──┘
```
| 区域 | 职责 |
|-|-|
| 导航轨 56px | 新建会话、项目、待审批、工作区、Provider、设置,图标加悬浮提示 |
| 项目/会话面板 | 会话按项目分组,带状态徽章(运行中 / 待审批 / 已完成),支持搜索、归档、置顶 |
| 中栏会话区 | 对话流内嵌 UI 块;顶部面包屑(项目 / 会话 / 分支);底部多模态输入框 |
| 右栏工作区 | 可整栏收起或最大化,承载表格、甘特、图表、对比、文件与网页 |
## 2.3 UI 块协议(沿用并扩展)
模型不直接输出 HTML,而是输出结构化 UI 块,由前端按类型渲染。这套协议在现有项目里已经落地,继续沿用,只增加一个 Provider 类型。
| 块类型 | 用途 |
|-|-|
| text / markdown | 正文与说明 |
| confirm-card | 门禁确认卡:影响范围、参数摘要、目标版本 |
| evidence | 证据链:来源系统、时间、版本 |
| diff | 方案与分支对比 |
| gantt / load-heatmap / conflict-list | 排产类可视化 |
| scenario-cards / sensitivity | 方案卡组与敏感性分析 |
| report | 报告预览与导出 |
| media | 图片、音频转写、视频帧 |
| provider-view | Provider 声明的视图,由宿主转发数据(见第 4 节) |
和第 4 节的关系:Provider 可视化就是 UI 块协议的一个扩展点。Provider 只声明视图,平台用自己的组件渲染,不引入第二套渲染体系。
## 2.4 主要界面
| 界面 | 作用 | 关键交互 |
|-|-|-|
| 对话 | 主入口 | 消息流 + 工具调用卡 + 内联审批卡 + 内联图表 |
| 审批中心 | 待我审批 / 我发起的 / 已处理 | 影响范围、参数摘要、目标版本,可拒绝与转交 |
| 工作区 | 表格 / 甘特 / 图表 / 对比 / 文件 / 网页 | 与对话联动,点数字看来源 |
| Provider 管理台 | 安装、启停、健康、契约、日志 | 双面板:左清单右详情 |
| 运行记录 | 按 executionId 追溯 | 谁、什么时候、用什么参数、结果如何 |
| 设置 | 账号绑定、模型、安全、数据位置 | 账号绑定逐系统显示登录态 |
**与现有界面的差异只有四处:**右栏从排产视口泛化为工作区;导航轨增加工作区与 Provider 入口;UI 块协议增加 provider-view 类型,数据经宿主转发;审批聚合为独立界面,支持待我审批 / 我发起的 / 已处理。其余包括设计系统、三栏布局、自绘顶栏与桌面壳全部沿用。
## 2.5 交互原则
| 原则 | 说明 |
|-|-|
| 写操作都有确认卡 | 影响范围 + 参数摘要 + 目标版本,参数变了即失效 |
| 状态始终可见 | 排队、执行中、等待审批、已完成、结果未知 |
| 数字可追溯 | 关键数字能点开看到来源系统、时间与版本 |
| 失败显式 | 显示错误分类与是否可重试,不用话术圆场 |
| 不暴露实现细节 | 界面不出现凭据、内部 token、Provider 内部结构 |
# 3 工作区
工作区是右侧的多标签面板:把对话里的结果钉下来,慢慢看、反复比。它不是第二个业务系统,所有数据都经 Core 获取。
| 面板类型 | 内容 | 数据来源 | 可否编辑 |
|-|-|-|-|
| 表格 | 订单池、资源、物料清单 | 能力调用(只读) | 编辑生成变更草案,需审批 |
| 甘特 / 排程 | 计划、工序、资源占用 | 能力调用 | 拖拽生成草案,需审批 |
| 图表 | 负荷曲线、KPI、方案对比 | 能力调用 | 否 |
| 对比 | 方案 A/B、执行前后差异 | 两次执行结果 | 否 |
| 文档预览 | 报告、PDF、Word | 产物 | 否 |
| 文件浏览 | 项目目录树与文件预览 | 项目工作目录 | 上传、重命名走审批 |
| 图纸 / 图片 | CAD 转图、现场照片 | 项目文件 | 否 |
| 代码 / 文本 | 配置、工艺文件、脚本 | 项目文件 | 编辑走审批 |
| 网页 | 外部页面 | 系统浏览器或受控内嵌 | 否 |
| 运行记录 | 按 executionId 追溯 | 平台 | 否 |
组织方式:标签页 + 布局 + 当前选择,整体作为一个工作区状态保存,换设备也能恢复。对话里的卡片可以钉到工作区,工作区的当前选择也能回填给对话。
| 网页浏览 | 做法 | 说明 |
|-|-|-|
| 用户看网页 | 默认交给系统浏览器打开 | 最省事,也不把外部内容混进产品界面 |
| 需要内嵌时 | 受控面板,域名白名单,明确标识外部内容 | 禁止自动填表与凭据注入 |
| Agent 读网页 | 走 web.fetch 能力 | 域名白名单、体积上限、脱敏、全程审计;不给 Pi 通用 HTTP 工具 |
| 工作区边界 | 说明 |
|-|-|
| 不持有业务凭据 | 凭据只存在平台凭据代理,工作区只拿渲染所需的数据 |
| 数据都经 Core | 每次取数带用户身份与权限,越权直接拒绝 |
| 编辑先出草案 | 拖拽、改单元格生成变更草案,审批通过才写入业务系统 |
| 文件访问受限 | 文件浏览限制在项目工作目录内,不遍历整机 |
| 状态属于偏好 | 打开的标签、布局、列设置可跨端同步;不属于业务数据 |
# 4 Provider 可视化怎么集成
结论:平台定义视图契约,Provider 只做声明,渲染由平台负责;确有复杂交互时再开 iframe 沙箱。
| 方案 | 做法 | 适用 | 代价 |
|-|-|-|-|
| 平台渲染(推荐) | Provider 在 manifest 里声明视图,用平台的组件词汇表(表格 / 图表 / 表单 / 甘特 / 时间线 / 差异)描述界面;数据由 Core 提供 | 绝大多数业务视图 | 组件词汇表要覆盖得住场景,早期可能不够 |
| iframe 沙箱 | Provider 自带页面,平台嵌进面板 | 第三方 Provider、复杂交互 | 要严格 sandbox 与 CSP,且不能让它直接拿凭据 |
| 静态内容 | 只给 markdown 或图片 | 说明类内容 | 没有交互能力 |
数据只有一条通路:视图 → 宿主 postMessage → 平台校验身份 → Core.invoke → 返回。iframe 不允许直连业务系统,也不注入任何凭据。
| 视图契约 | 字段与取值 |
|-|-|
| 槽位 slot | nav.item / session.sidepanel / provider.detail.tab / message.inline |
| 类型 kind | schema(平台组件渲染)/ iframe(受信沙箱)/ markdown(说明) |
| 数据来源 | capabilityId + 参数模板,运行时由 Core 补齐身份与授权 |
| 刷新 | 手动 / 定时 / 订阅事件,统一由平台调度 |
首版只开两类视图:只读表格与图表,且只允许 schema 类型。iframe 留到接入第三方 Provider 时再开。
# 5 Provider 怎么开发与管理
Provider 先写适配器,再以包的形式安装和管理。开发步骤与运行机制如下,包结构与生命周期见后。
**开发步骤:**
```text
1. 定义能力:id、effect、mode、输入输出 Schema
2. 写 manifest:providerType、version、capabilities
3. 实现适配器:describe / invoke / health
4. 跑契约测试
5. 注册实例:地址、租户或工厂绑定、适配器版本
6. 绑定账号与路由
7. 灰度开放给引擎
```
| 运行机制 | 约定 |
|-|-|
| 凭据边界 | 首版 Provider 运行在平台信任域内,Core 按 execution 注入短期凭据,Provider 不落库、不缓存。外部团队接入时走跨进程模式,双向认证加一次性凭据。 |
| 异步任务 | Core 登记 jobId 并负责轮询或接收回调,引擎只收到“已受理”和“终态”两类事件,不自己空转。 |
| 审批 | Core 生成审批单(请求人、能力、目标实例、参数摘要、资源版本、影响范围),通过后写入执行授权,与主体、实例、参数摘要、版本和有效期绑定;参数或目标改变即失效。 |
| 幂等与超时 | 发请求前持久化 executionId 与幂等键,一个授权只启动一个逻辑执行。超时先查回执,只有能确认未执行或下游支持可靠幂等时才重试。 |
| 错误分类 | 统一映射为 invalid_argument、forbidden、conflict、rate_limited、unavailable、unknown_result;只有 rate_limited 与 unavailable 允许自动重试。 |
| 契约测试 | 必须覆盖 Schema 校验、幂等重放、权限拒绝、错误映射、超时与结果未知、异步受理与查询。 |
| 包内容 | 说明 |
|-|-|
| provider.json | id、版本、供应方、能力清单、视图声明、兼容的平台版本 |
| adapter | describe / invoke / health 的实现 |
| ui/(可选) | 视图描述文件,走第 4 节的契约 |
| tests/(可选) | 该 Provider 的补充契约用例 |
| 签名与摘要 | 包签名 + SHA256,安装前必须校验 |
| 阶段 | 动作 | 权限级别 | 说明 |
|-|-|-|-|
| 发现 | 扫描目录或仓库,只读清单 | 只读 | 不执行任何代码 |
| 安装 | 校验签名与摘要 → 解包到临时目录 → 契约校验 → 权限面变化提示 → 审批 → 落盘注册 | 需审批 | 会扩大可用能力面,必须走确认 |
| 启用 / 停用 | 切换实例状态 | 需审批 | 停用立即阻断新调用,在途任务保留查询 |
| 升级 | 旁路安装新版本 → 契约回归 → 审批 → 切换 | 需审批 | 保留上一版本目录,支持一键回滚 |
| 卸载 | 先停用 → 检查在途执行与绑定 → 解除绑定 → 删除文件 | 需审批 | 执行历史与审计不删 |
| 健康 | 定时探测 + 手动触发 | 只读 | 保留最近 N 次结果 |
落盘位置:桌面模式放 \~/.aps/providers/<id>/<version>/,服务端模式放平台的 provider 目录;注册表与实例绑定存数据库。
供应链约束:只允许内网 registry 或签名包,不从公网直接安装。Pi 自带的 pi install 是给开发者 CLI 用的,包拥有完整系统权限,不能直接搬进产品。
# 6 多 Sub-agent 与 Goal
先确认一个事实:**引擎不提供平台级的 Goal 与 Task**。Pi 的 agent-core 只有单个 Agent 循环(消息、工具、事件、steering、follow-up、abort 与思考预算),Codex 同样把它自己的多任务能力留给宿主。所以 Goal、Plan、Task 由平台定义,Sub-agent 是平台创建的一次受控运行,与所选引擎无关;每个运行记录它用的是哪个引擎。
| 概念 | 定义 | 关键字段 |
|-|-|-|
| Goal | 用户想达成的一件事 | goalId、标题、发起人、成功判据、涉及系统、期限、状态 |
| Plan | Goal 下的步骤序列 | planId、步骤、每步输入输出、可回滚点 |
| Task | 一个可独立执行的小任务 | taskId、角色、输入、输出 schema、工具子集、超时、预算 |
| Sub-agent | 执行某个 Task 的一次 Agent 运行 | runId、taskId、能力子集、事件流、结果 |
| 规则 | 说明 |
|-|-|
| 权限只减不增 | Sub-agent 的能力子集必须是父级授权范围的子集,每次调用仍过 Core |
| 写操作串行 | 同一 Goal 内的写操作串行;同一目标资源不允许并发写 |
| 只读可并行 | 调研类只读任务可以并行,结果汇总回主 Agent |
| 预算硬上限 | 每个 Sub-agent 有轮次、工具调用次数、超时三重上限,超限中止并回报 |
| 输出结构化 | Sub-agent 必须按输出 schema 返回,不把整段对话塞回主上下文 |
| 首版不互相对话 | Sub-agent 之间不直接通信,由父级编排,避免循环与成本失控 |
首版只做一种形态:主 Agent 派发只读调研任务(每个子任务一个能力子集),汇总后由主 Agent 出方案并走审批;写操作全部回到主 Agent 串行执行。
# 7 文档与规范管理
这一节定文档的归属与流转:权威会随阶段转移,但任何时刻只有一个权威。
分两个阶段。现在处于第一阶段:设计在飞书评审,评审通过后沉淀到仓库;沉淀完成即以仓库为准。
| 问题 | 规定 |
|-|-|
| 当前阶段 | 设计文档在飞书维护,方便评审与批注;仓库里保留一份镜像 |
| 沉淀时机 | 评审通过后,用脚本把飞书文档导出为 Markdown 写入仓库,一次一份,不手工誊抄 |
| 沉淀之后 | 仓库是唯一真相,飞书只作对外读物;冲突以仓库为准 |
| 放在哪 | docs/ 一个入口,分 product / architecture / development / runbook / adr;本平台的设计放在 architecture/multi-system-agent-platform/ |
| 什么时候改 | 改代码、改门禁、改能力清单、改契约,同一轮改文档,否则视为未完成 |
| 决策留痕 | 重大选择写 ADR:背景、选项、结论、影响 |
| 防漂移 | 用测试卡住:能力清单 ↔ 文档表格、schema ↔ 代码,对不上就红 |
| 导出方式 | 脚本把正文导出为 Markdown,画板随正文内联为 mermaid 代码块;文件头写入来源与同步方式 |
# 8 测试体系
| 层 | 测什么 | 例子 | 运行时机 |
|-|-|-|-|
| 单元 | Core 纯逻辑 | 策略判定、幂等键、参数规范化、错误分类映射 | 每次提交 |
| 契约 | Provider 是否合格 | schema 校验、幂等重放、权限拒绝、错误分类、超时与未知、异步受理 | 每次提交,接入新 Provider 必跑 |
| 引擎适配 | 引擎实现是否合格 | 多轮对话、工具调用与回执、事件顺序、取消、错误分类、会话恢复;用假引擎回放 | 每次提交,接入新引擎必跑 |
| 集成 | Core + Provider + 假业务系统 | 审批全链路、授权失效、并发写冲突 | 每次提交 |
| 端到端 | 从对话到执行 | 固定模型响应回放,跑通查询、试排、发布三条主线 | 每日 |
| 安全 | 边界是否守住 | 越权、凭据不出域、策略绕过、重放、停用后仍被调用 | 每次提交 + 发版前 |
| 桌面 | 壳与本地服务 | 启停、单实例、离线升级与回滚、断网恢复 | 发版前 |
| 漂移 | 文档与契约是否同步 | 能力清单 ↔ 文档、schema ↔ 代码 | 每次提交 |
| 性能 | 求解与批量查询基线 | 黄金算例时限、并发会话数 | 每日 |
三条原则:测试模型只测边界不测措辞,用固定响应回放;所有写路径必须有“重试不重复生效”的用例;每条验收标准都要有一条自动化用例对应。
# 9 引擎怎么接入
架构设计只定了边界与契约,这里写怎么落地。**引擎以长驻子进程运行**(ADR-0006):平台拉起进程、发提示、收事件、发取消,不做进程内嵌入。
## 9.1 为什么是子进程
| 理由 | 说明 |
| --- | --- |
| Codex 只能子进程 | Rust 二进制,无法进程内嵌入 |
| 不占平台事件循环 | 智能体循环在独立进程,长任务不拖累其它会话的响应 |
| 进程模型统一 | 两个引擎一致,进程管理(启停、超时、崩溃重启、日志轮转)只写一套 |
| 历史在平台 | 平台保存会话与执行记录,引擎进程可以随时重启并按历史恢复 |
## 9.2 平台装什么依赖
平台依赖 Pi 的 npm 包,目的是**拿 `RpcClient` 与类型**,不是把 Agent 起在平台进程里。循环跑在子进程。
```json
{
"dependencies": {
"@earendil-works/pi-agent-core": "0.85.1",
"@earendil-works/pi-ai": "0.85.1",
"typebox": "1.3.7"
},
"engines": { "node": ">=22.19.0" }
}
```
**硬前提:** 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
// 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 }),
});
if (!res.ok) throw new Error(`tool bridge failed: ${res.status}`);
return await res.json(); // 平台侧收口到 Core.invoke
});
```
## 9.7 接入方式
| 方式 | 适用场景 | 说明 |
| --- | --- | --- |
| npm 依赖(推荐) | 常规开发 | 装包是为了 `RpcClient` 与类型;**循环跑在子进程**,不在平台进程内 |
| 改源码:fork | 必须改 Pi 源码时 | 构建与升级成本高,要自己维护 patch 并跟上游同步 |
| 子进程跑 pi-coding-agent CLI | 不建议 | 它自带 read / bash / edit / write,等于给模型开了本机 shell |
| 进程内嵌 Agent | 不采用 | ADR-0006 已否;会占平台事件循环,且与 Codex 的进程模型不一致 |
## 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 节;本项目的任务分解、优先级、验收标准与推进状态见下面的任务表,默认按阶段分组查看。
[多业务系统智能体平台 · 架构设计](https://scneju2wtbd7.feishu.cn/docx/DPHSdeVtAo8bLExzbufcpRZ3nAb)
[多系统智能体平台 · 项目任务](https://scneju2wtbd7.feishu.cn/base/UTkYbMacdaz6O1sxzsEcd3BTnne)</bookmark>
任务表内含 1 条大任务、11 个里程碑、65 个工作包与 5 个验收门禁,另有 6 条待决策项;提供「按阶段分组」「按优先级」「待决策」三个视图。