27 KiB
多业务系统智能体平台 · 架构设计
来源:飞书文档(原文,A0-G 评审快照,2026-09-23 导入) 权威版本:本仓库。导入后飞书副本停止更新,改动走仓库提交与 ADR 流程。
**一句话:**用户在前台说需求,Pi 这个大脑把它翻译成工具调用,Core 门禁决定能不能做,Provider 翻译成各系统自己的接口,最后由业务系统执行。
本文覆盖四件事:客户端与服务端的边界、项目代码结构、控制面与智能体运行时的实现、Provider 的接入方式。文中的目录与能力名称为设计示例。
**与现有项目的关系:**本次是对现有 APS 项目的重构。现有代码不保留,只作借鉴;新系统从零实现。项目仍在开发阶段,没有客户试用数据,因此不考虑与既有数据、表结构、接口版本的兼容与迁移——旧的一切随旧代码一起退役。
落地细节(Provider 套件怎么装、多 Sub-agent 怎么协作、数据与界面、文档规范与测试)见配套文档:多系统智能体平台 · 实施设计
1 客户端与服务端
flowchart TB
subgraph CLIENT[客户端]
DESK["Electron 桌面端"]
WEBUI["Web 浏览器"]
end
subgraph SERVER["Agent Platform Server"]
ENTRY["HTTP / SSE 入口"]
APP["应用服务
会话 · 任务 · 事件发布
平台持有会话与业务记录"]
ENGINE["统一 AgentEngine 契约
开发 / 部署启动时注入一个实现"]
subgraph RUNTIME["引擎实现二选一 · 每个部署实例只运行一种"]
PIAD["PiAdapter"]
CDAD["CodexAdapter"]
PI["Pi agent-core
原生状态由适配器管理"]
CODEX["Codex app-server
原生状态由适配器管理"]
PIAD --> PI
CDAD --> CODEX
end
CORE["平台 Core
可信身份 · 代码策略 · 持久审批
幂等执行 · 审计"]
SUITE["Provider 接入
契约 · 路由 · 适配"]
end
S1["APS"]
S2["ERP"]
S3["EMS"]
DESK --> ENTRY
WEBUI --> ENTRY
ENTRY --> APP
APP --> ENGINE
ENGINE --> PIAD
ENGINE --> CDAD
PI -->|工具桥接| CORE
CODEX -->|工具桥接| CORE
APP -->|能力查询 · 审批决策 · 执行状态| CORE
CORE --> SUITE
SUITE --> S1
SUITE --> S2
SUITE --> S3
classDef default fill:#ffffff,stroke:#8a96a3,stroke-width:1.2px,color:#243247;
classDef selected fill:#fff3e8,stroke:#d78842,stroke-width:1.5px,color:#563418;
classDef ext fill:#fafafa,stroke:#8a96a3,color:#243247,stroke-dasharray:5 4;
class ENGINE selected;
class S1,S2,S3 ext;
style CLIENT fill:#f8fafc,stroke:#b7c0c9,color:#243247
style SERVER fill:#f8fafc,stroke:#b7c0c9,color:#243247
style RUNTIME fill:#ffffff,stroke:#b7c0c9,stroke-dasharray:5 4,color:#526174
linkStyle default stroke:#8a96a3,stroke-width:1.3px;
整体分两段:客户端只做界面与本地能力;服务端是引擎中立的平台服务,内部再分入口与应用服务、引擎适配、控制面、适配器四块。Pi 与 Codex 二选一,在开发或部署期选定,整段链路对客户端透明。APS、ERP、EMS 始终是平台外部的独立系统。
| 段 / 层 | 打个比方 | 负责 | 不负责 |
|---|---|---|---|
| 客户端 | 前台 | 界面、工作区、本地能力(文件、打印、设备)、断网缓存 | 业务判断、权限判断、持有业务凭据 |
| 应用服务 | 传达室 + 调度员 | HTTP / SSE、会话与任务、能力查询、审批流转、事件推送 | 业务规则、直接调业务系统 |
| Agent 引擎 | 大脑 | 理解目标、挑工具、把结果讲成人话;Pi 或 Codex 二选一 | 碰凭据、判断权限、直连业务库 |
| 平台 Core | 门禁 + 调度台 | 决定能用哪些能力、以谁的身份、要不要审批,并留痕 | 业务规则、业务数据 |
| Provider 接入 | 翻译官 | 把统一调用翻译成某个系统的 API,再翻译回统一回执 | 业务决策、持有模型上下文 |
| 业务系统 | 车间 | 自己的数据、权限、规则和状态,最终执行 | 因为平台放行就执行 |
2 项目结构
一个 monorepo,按客户端与服务端两段组织。服务端是引擎中立的平台服务:应用服务、引擎适配、控制面、Provider 接口各归其位,引擎在适配层内部选定;客户端只通过一套接口与其通信。业务系统的代码不在这个仓库里。(开发期例外:APS 暂放本仓库 aps/ 目录,形态见 ADR-0010;迁出条件也写在里面。)
agent-platform/
├── client/ # 客户端:桌面与浏览器共用
│ ├── app/ # React 界面:对话、工作区、审批
│ └── desktop/ # Electron 壳:窗口、本地守护、离线升级
├── server/ # 服务端:引擎中立的平台服务
│ ├── entry/ # HTTP / SSE 入口、会话、鉴权
│ ├── app/ # 应用服务:请求编排、能力查询、审批流转、事件推送
│ ├── agent/ # 引擎适配:统一接口 + Pi / Codex 两个实现
│ ├── core/ # 控制面
│ │ ├── registry/ # 能力目录、实例与路由
│ │ ├── identity/ # 平台用户 ↔ 各系统账号
│ │ ├── policy/ # 允许 / 拒绝 / 需审批
│ │ ├── credentials/ # 凭据托管与取用
│ │ ├── execution/ # 授权、幂等、调用网关
│ │ └── audit/ # 证据链
│ ├── provider-sdk/ # Provider 契约与契约测试
│ └── workflows/ # 跨系统顺序流程
├── providers/ # aps / erp / ems,只放适配器
├── contracts/ # 客户端与服务端共享的接口类型
├── deploy/ # 部署与实例配置
└── docs/ # 架构与接入文档
| 目录 | 职责 | 不该出现什么 |
|---|---|---|
| client/app | 对话、工作区、审批界面(React) | 业务判断、直接调 Provider |
| client/desktop | Electron 壳:窗口、本地守护、离线升级 | 业务逻辑、绕开服务端直连 Provider |
| server/entry | HTTP / SSE 入口、会话与鉴权、装配各模块 | 业务规则、直接调业务系统 |
| server/agent | 引擎适配层:统一接口,以及 Pi / Codex 两个实现;唯一允许 import 引擎 SDK 的地方 | 业务逻辑、直接 HTTP 调业务系统 |
| server/core | 能力目录、身份、策略、凭据、执行、审计 | import 任何具体 Provider |
| server/provider-sdk | Provider 接口、Schema、契约测试、跨进程调用 | 具体系统的业务语义 |
| server/workflows | 跨系统顺序流程定义 | 协议与字段细节 |
| providers/aps | APS 适配器:manifest、adapter、字段映射 | 排产算法、业务数据 |
| contracts | 客户端与服务端共享的类型与接口定义 | 任何运行时实现细节 |
**依赖方向:**client → server/entry → server/app → server/agent → server/core → provider-sdk ← providers。反向依赖用 dependency-cruiser 或 eslint 的 no-restricted-imports 卡在 CI 里,避免几个月后又缠成一团。
3 一次调用怎么走
用户说:“把下周的排产计划往后推一天。”
sequenceDiagram
participant C as 客户端
participant A as 应用服务
participant E as Agent
participant K as 平台 Core
participant P as Provider
participant B as 业务系统
Note over A,E: Agent 通过统一接口接入,Pi / Codex 在开发或部署期选定
C->>A: 消息与身份令牌
A->>A: API 鉴权、会话管理
A->>K: 按身份与会话请求可用工具
K-->>A: 工具清单与授权作用域
A->>E: 消息、上下文与受限工具集
E->>K: 受限工具桥:结构化调用
K->>K: 校验身份、作用域与策略
Note over E,K: 业务审批由 Core 管理,与引擎命令权限分开
opt 策略要求业务审批(通过后继续)
K->>K: 持久化审批单 approvalId
K-->>A: 待审批事件与影响范围
A-->>C: 业务审批卡:参数与资源版本
C->>A: 审批决定与身份凭据
A->>K: approvalId、决定与审批人
K->>K: 校验审批人及授权 grant
end
K->>K: 复核权限与资源版本
K->>K: 持久化 executionId 与幂等键
K->>P: 受控执行与短期凭据
P->>B: 业务 API 调用
B->>B: 校验自身业务规则
B-->>P: 执行回执
P-->>K: 结构化回执
K->>K: 持久化执行结果与审计
K-->>E: 工具结果与证据引用
E-->>A: 统一格式的 Agent 事件
A-->>C: 流式呈现结果与状态
| 边界 | 传什么 | 谁决定 |
|---|---|---|
| 客户端 → 服务端 | 消息与身份令牌;不含业务凭据 | 客户端发起,服务端受理 |
| 应用服务 → 引擎 | 用户目标、上下文、本会话可见的能力清单 | 应用服务装配,引擎不决定权限 |
| 引擎 → Core | capabilityId 与参数;不含凭据,不自报身份 | 引擎提议,Core 裁决 |
| Core → Provider | 主体、实例、连接、executionId、deadline、参数、幂等键、授权引用 | Core |
| Provider → 业务系统 | 原生 API 调用 | Provider |
| Provider → Core | 状态、结果引用、jobId 或 errorKind | 以业务系统的事实为准 |
| Core → 客户端 | 结果与证据引用,经 SSE 流式下发,不回灌凭据 | Core |
记住一句就够:引擎只能提议,Core 决定能不能做,业务系统最终说了算。所以换引擎或换模型都不影响安全,加系统也不用动引擎。
4 控制面 Core 怎么实现
Core 不是所有请求的中转站,而是一组被多方调用的确定性后端模块:应用服务在运行前查能力、在审批时落决定、在事后查执行;Agent 在运行中提出工具调用。这些调用都是普通代码调用,不需要再放几个 Agent 来判断。
五个服务,首版放在同一个进程里,共用 PostgreSQL:
| 服务 | 具体做什么 | 正确性的关键 |
|---|---|---|
| 身份 Identity | 验证登录身份,查用户所属租户、项目角色,以及对应业务系统的账号映射 | 身份来自可信登录链路;不接受模型自报的 userId |
| 策略 Policy | 用代码判断角色、能力、目标资源与操作类型,返回允许 / 拒绝 / 需审批 | 默认拒绝;每次真正执行前都检查,不能只在展示工具列表时过滤 |
| 审批 Approval | 把待确认动作与状态落库,界面提交确认或拒绝,校验确认者是会话发起人 | 确认绑定具体动作、参数、目标与版本;不能变成通用通行证 |
| 执行 Execution | 建执行记录,管幂等、并发、超时,调用 Provider,跟踪异步任务 | 防重复执行;超时不等于失败,结果未知时先查询 |
| 审计 Audit | 记录请求人、批准人、操作、参数摘要、结果与证据引用 | 记录完整且关联一致,并限制修改权限;普通日志文件不够 |
**以「发布排产计划」为例:**Agent 提出「发布计划 P,版本 V」→ Core 确认身份 → 判断策略 → 需要则建审批单并推送确认卡 → 批准后复核权限与资源版本 → 用唯一执行编号与幂等键调 Provider → 保存回执与审计记录。成功记回执,异步受理跟踪 jobId,结果未知进待核实。
三个容易被低估的地方:
| 常见误解 | 实际情况 |
|---|---|
| 审批就是一个弹窗 | 弹窗只是界面。真正的审批是数据库里的状态与授权记录,进程重启、Agent 退出后依然存在;聊天里说一句「同意」不能批准任意操作 |
| 执行就是发请求,失败就重试 | 对方可能已经执行成功而回执丢失。只有业务系统支持可靠幂等或状态查询时才能安全重试,否则必须保留「结果未知」 |
| Core 放行就代表能执行 | Core 查的是平台权限与授权条件;业务系统仍要校验自己的规则。审批后计划被改,应由版本校验阻止发布旧方案 |
**为什么要这么做:**业务操作是否被允许、是否已执行、是否需要审批,由这套代码决定,不随所选引擎变化。前提是每个引擎的业务调用都必须经过 Core,不能存在绕开的路径。
4.1 协作与权限以业务系统为准
平台不做协作模型:没有审批转派、委托、多人计数、角色策略、双人分离。这些是业务系统(APS、ERP)本来就有的能力,平台重复实现一遍必然和它们的权限模型对不上。人的协作发生在业务系统里,平台只负责把人映射进去、把动作调出去、把过程记下来。
| 职责 | 谁负责 | 说明 |
|---|---|---|
| 能否做(权限、角色、多级审批) | 业务系统 | 用该用户在业务系统里的身份判定;平台不复制这套规则 |
| 用什么身份做(账号映射) | 平台 | 把登录用户映射到各系统账号,调用时使用用户自己的身份,业务系统的权限随之生效 |
| 是不是想这么做(意图确认) | 平台 | 确认卡防的是 Agent 误解意图,不是防越权;答的只是发起人本人 |
| 做过什么(审计与执行记录) | 平台 | 业务系统只知道账号改了数据,不知道经由 Agent、模型建议了什么、用户确认了什么 |
**一句话:**权限归业务系统,映射与确认归平台。动作在业务系统里已有审批流的,交给它审;只有跨系统编排这类没人能替你决定意图的动作,才由平台出确认卡。
**登录态从哪来(ADR-0012):**用户在每个系统上各自绑定一次——首选跳转该系统的授权登录(OAuth2 / OIDC),拿回来的 token 只以密文存在平台的凭据代理里;老系统没有标准登录时退化为「账号密码一次性换 token」,密码用完即弃、绝不落盘。平台不实现业务系统的登录页,也不把业务 token 下发给客户端。
4.2 确认与执行记录只存一张表
现有实现用三张表(审批请求、一次性凭据、审批事件)承载一件事。单用户场景下可以合并成一张待确认动作表:请求参数摘要、状态(待确认 / 已确认 / 已执行 / 已拒绝)、执行回执、幂等键、有效期。
**为什么能合:**批准与执行由同一个人、同一个进程完成,不需要把"批准"与"执行"拆成两个可独立消费的凭据;审批事件与审计记录重复,只保留审计。**不能省的两件事:**待确认状态必须跨进程重启存活(用户可能过一会儿才回来点确认),执行只能生效一次(幂等键加执行记录)。
4.3 代码落地(A4 已实现)
| 服务 | 代码位置 | 一句话契约 |
|---|---|---|
| 身份 | server/core/identity/context.ts |
平台用户 → 各系统账号;未绑定默认拒绝,一个系统停用不影响另一个 |
| 策略 | server/core/policy/engine.ts |
只返回允许 / 拒绝 / 需审批;顺序固定(可见性 → 身份 → 会话范围 → 动作分级),可换成 OPA 一类引擎 |
| 凭据 | server/core/credentials/broker.ts |
库里只有 AES-256-GCM 密文;按 execution 签发一次性句柄,用完即废 |
| 确认 | server/core/approval/store.ts |
确认与执行记录同表;绑定参数摘要、目标版本与有效期,重启后仍在 |
| 审计 | server/core/audit/log.ts |
只追加的哈希链,verify() 能定位被改过的那一条 |
五个模块由 server/core/control-plane/control-plane.ts 绑在一起注入 Core.invoke——引擎可以不认识它们,但绕不过它们。
**平台不覆盖业务系统的拒绝:**Provider 回 forbidden 就是最终结果,平台不改写成成功、不自动重试;审计里记的是「业务系统拒绝」。平台能加限制,不能替业务系统放行。
**开发期例外:**Provider 在 manifest 里声明 requiresIdentity: false 表示该系统没有账号概念(假 Provider 与联调桩),真实系统默认需要绑定。
5 引擎适配
5.1 适配层的边界规则
Pi 以 npm 包形式发布(MIT),装进 monorepo 当依赖,不需要 git submodule。首版把 Pi 作为长驻子进程运行:用官方 RpcClient 驱动(它负责拉起进程、按 id 关联响应、把事件推给监听者),协议是标准输入输出的 JSONL。这样与 Codex 的进程模型一致,也把智能体循环从平台的事件循环里隔离出去。
**硬前提:**Node ≥ 22.19.0,部署镜像要跟上。具体接入方式、代码示例与推进顺序见实施设计。
| 边界规则 | 说明 |
|---|---|
| 只有一个入口 | 只有 server/agent 允许 import Pi,其它模块必须通过这一层 |
| 工具只来自能力目录 | 不注册文件、shell、网络类工具,避免绕开 Core |
| 每次都过 Core | 工具的 execute 内部只调 core.invoke,不允许自己发 HTTP 请求到业务系统 |
| 按身份给工具 | 工具列表按用户身份过滤;切换用户要重建会话,不能复用工具集 |
| 凭据不进工具 | 凭据在 server/core/execution 注入,工具参数与模型上下文里出现凭据即视为缺陷 |
| 会话落库 | 消息与执行记录存 PostgreSQL;Pi 只负责内存态与事件流 |
5.2 两个引擎与切换方式
两个引擎都以长驻子进程运行,循环模型一致:平台拉起进程、发提示、收事件、发取消,互不理解对方的内部协议。差异集中在工具面——Codex 可通过 MCP 或实验性动态工具接收平台能力,Pi 需要在 pi 侧写一个扩展,把工具调用以本地 HTTP 回调平台,平台再走 Core.invoke。所以适配层要抹平的是工具面,不是事件循环。
目标是可替换,不是行为一致:三个固定点不动,两个实现可换。
**切换发生在开发或部署期,不是用户使用中。**一个部署实例只运行一种引擎,整段链路对客户端透明;这省掉了跨引擎会话交接、双引擎并行和审批跨引擎延续等一大堆复杂度。同一会话不中途换引擎。
| 固定点 | 内容 | 为什么 |
|---|---|---|
| 领域类型 | 会话、消息、事件、回执都是我们自己的类型 | 一旦运行时的类型渗出来,换的时候就是全平台改造 |
| 工具出口 | 任何运行时的工具调用都落到 Core.invoke | 这是门禁,也是唯一能统一两个运行时的地方 |
| 状态 | 会话与执行记录由平台保存 | 运行时只借循环、不借状态,换的时候不用迁数据 |
接口只描述「怎么驱动一个循环」,不描述「允许做什么」:
interface AgentRuntime {
createSession(input: {
subject: Subject;
system?: string;
tools: ToolDescriptor[]; // 能力目录投影
history?: Message[]; // 平台保存的历史,可恢复
}): Promise<RuntimeSession>;
}
interface RuntimeSession {
send(text: string, opts?: { signal?: AbortSignal }): AsyncIterable<RuntimeEvent>;
abort(): void;
}
type RuntimeEvent =
| { type: "text"; delta: string }
| { type: "tool_call"; callId: string; name: string; args: unknown }
| { type: "tool_result"; callId: string; receipt: ExecutionReceipt }
| { type: "approval_required"; request: ApprovalRequest }
| { type: "turn_end"; usage: Usage }
| { type: "error"; kind: ErrorKind };
| 部件 | Pi | Codex | 共同契约 |
|---|---|---|---|
| 驱动 | 长驻子进程(RpcClient 驱动,JSONL 协议) | 子进程 app-server | AgentRuntime 接口 |
| 工具面 | RPC 无注册工具通道,靠 Pi 侧扩展桥回调平台 | MCP 服务,并关掉它自带的 shell 与文件工具 | 两边都落到 Core.invoke |
| 事件 | subscribe 回调 | 协议事件流 | 统一的 RuntimeEvent |
| 审批 | 不内置,由 Core 出确认卡 | 自带审批,需映射到 Core 确认卡 | 统一的审批请求与响应 |
| 会话 | 平台存 | 平台存,镜像它的事件 | 平台自己的会话格式 |
**难点只有一个,而且是已知的:**工具面的接法两边不同——Codex 有现成的 MCP 通道,Pi 要靠扩展桥。所以「兼容」的实质是两个工具面都要实现,且都必须汇到 Core.invoke。这也是为什么切换成本集中在工具面,而不是事件循环。进程管理那部分反而是共用的:启停、超时、崩溃重启、日志轮转、父进程看门狗只写一套。
**不实现别人的协议。**Codex 的协议只活在它的适配器内部,客户端与 Core 都不感知;服务端与客户端之间只认我们自己的一套接口(HTTP / SSE)。协议的所有权比协议的兼容性重要。
**推进方式:**现在只做四件事——定义引擎接口、守住三条纪律、写引擎进程管理与 Pi 扩展桥、写一个假引擎跑一致性测试;Codex 驱动等到真要切换时再写。
**验收方式就是一套一致性测试:**两个驱动跑同一套用例——多轮对话、工具调用与回执、事件顺序、取消、错误分类、会话恢复。过不了就不算兼容。
**做不到的部分要说清楚:**运行时独有的能力(Pi 的插话与追问、Codex 的沙箱)用能力声明暴露,界面按开关走,不做统一封装;审批粒度不可能完全对等,Codex 审批命令与补丁,我们审批业务动作,只能近似映射;提示词与工具描述格式不同,要各调一份。
6 Provider 契约
Provider 是能力适配器,不是业务系统,也不承载模型上下文。接口只三个方法:
interface Provider {
describe(): Promise<ProviderManifest>; // 声明能力
invoke(ctx: ExecutionContext, call: CapabilityCall): Promise<ExecutionReceipt>;
health(): Promise<HealthStatus>;
}
interface Capability {
id: string; // 如 aps.schedule.publish
description: string; // 给模型看的用途说明
effect: "read" | "draft" | "write"; // draft:只写隔离草稿
mode: "sync" | "async";
inputSchema: JsonSchema;
outputSchema: JsonSchema;
idempotency: "none" | "key" | "natural";
errors: ErrorKind[];
}
Provider 还要声明登录方式(ADR-0012),平台按声明走同一条绑定流程,不自己实现业务系统的登录页:
auth.kind |
用于 | 平台做什么 |
|---|---|---|
oauth |
有标准授权登录的系统(首选) | 发起跳转 → 收回调 code → 用 exchange() 换 token |
password-exchange |
老系统,没有标准登录 | 收一次账号密码 → exchange() 换 token,密码用完即弃 |
apikey |
系统级集成、无人值守 | 管理员配置,密钥挂在租户或实例上 |
| 不声明 | 没有账号概念的自建系统(联调桩) | 视为 requiresIdentity: false |
Provider 的开发步骤、运行机制与契约测试见实施设计。
**能力声明的取舍:**read 不改变状态;draft 只写隔离草稿,不走审批但必须声明清理方式;write 必须绑定唯一目标、幂等键与授权引用。sync 直接返回结果,async 先返回 jobId。
7 桌面端怎么选
桌面端在客户端这一侧,只做界面与本地能力;引擎与 Core 都在服务端。服务端可以集中部署在内网、与客户端同机部署,或混合部署(本地只保留设备与文件能力)。桌面壳的选择不影响智能体架构,真正决定选型的是“服务端部署在哪、工厂有没有网”。
| 方案 | 体积 | Node 支持 | 工厂离线环境 | 结论 |
|---|---|---|---|---|
| Electron | 150MB 起 | 内置,Pi 可直接跑 | 自带 Chromium,不依赖系统组件 | 首版推荐 |
| Tauri v2 | 10-20MB,另加 Node sidecar | 需自带 sidecar 二进制 | 依赖系统 WebView2,老机器可能缺失 | 包体或内存成为硬指标时再评估 |
| 纯浏览器 / PWA | 0 | 无 | 依赖现场浏览器版本 | 不需要本地设备能力时最省 |
**首版选 Electron:**工厂离线 PC 不用赌 WebView2,离线升级、进程守护、单实例都有成熟做法。Tauri 包体小,但没有 Node 运行时,主进程逻辑要用 Rust 重写且依赖系统 WebView2;等包体或内存成为硬指标时再评估。
| 桌面端边界 | 说明 |
|---|---|
| 不直连 Provider | 所有业务调用走平台 API,桌面端只是入口 |
| 不持有业务凭据 | 凭据只存在平台凭据代理,本地不落长期密钥;设备证书加用户登录双重身份,换设备或换人都可追溯 |
| 本地 runner 只做本地事 | 打印、扫码、文件、设备采集可以本地做,业务写操作仍要过 Core |
| 离线只缓存不越权 | 断网时排队与重试,恢复后仍要过策略与审批,不能本地放行 |
8 首版范围
| 先做 | 暂不做 |
|---|---|
| 一个 TypeScript / Node 平台服务 + Pi 运行库 + PostgreSQL | OPA、消息总线、Temporal / Camunda |
| Electron 桌面壳,复用同一套 Web 界面 | Tauri 迁移 |
| Provider 跑在平台侧,进程内或边车 | 可视化编排 |
| 代码内声明的顺序流程 + 每步结果落库 | 配置化 DAG 引擎 |
| APS 三个能力跑通,外加 ERP 或 EMS 一个只读能力 | 一次接入全部系统 |
| 验收项 | 标准 |
|---|---|
| 身份 | 不同系统的登录态可独立失效;桌面端能定位到设备与用户 |
| 权限 | 业务拒绝不会被 Core 放行 |
| 幂等 | 同一写请求重试不重复生效,或明确返回结果未知 |
| 停用 | 停用 Provider 后不再接收新调用 |
| 可观测 | 一个系统失败时能看清各步骤的实际结果 |
9 附录:ChatGPT 客户端的架构
记录另一个真实产品的做法,供参考,不是本项目采用的方案。一句话概括:宿主负责策略与界面,独立进程负责循环与执行,能力以 MCP 子进程接入,审批用 RPC 回流。证据来自本机进程实测与开源仓库源码,官方文档站在当前环境访问不到。
| 部件 | 形态 | 职责 |
|---|---|---|
| 外壳 | ChatGPT.exe(Electron),9 个进程 | 窗口与交互,以及宿主能力:文件、浏览器、电脑操作、插件 |
| agent 后端 | codex app-server(Rust),1 个进程 | 模型调用、智能体循环、工具编排、沙箱执行、审批策略 |
| 能力进程 | MCP 服务器(Node),22 个进程 | 每个启用的插件一个,作为工具接入后端 |
| 状态 | ~/.codex | 会话与回放记录、会话索引、历史文件,以及线程历史、状态、日志等数据库 |
| 通信 | 标准输入输出;源码还支持本地 socket 与 WebSocket | 自有 app-server 协议(v1 / v2),RPC 风格;审批请求从 agent 回流到宿主 |
| 结论 | 做法 | 说明 |
|---|---|---|
| 可借鉴 | 后端长驻,不随请求起进程 | 模型与工具状态可以复用,冷启动只发生一次 |
| 可借鉴 | 能力独立进程或独立服务 | 插件崩溃不拖垮 agent;与本项目 Provider 独立部署是同一个思路 |
| 可借鉴 | 审批从 agent 回流到宿主 | 策略留在宿主,agent 不自己放行;与 Core 做唯一裁决者一致 |
| 不照搬 | 直接采用它的 app-server 协议 | 内部演进的协议,两代并存,跟着走会被绑住 |
| 不照搬 | 把会话状态交给它 | 状态归它所有,我们拿不到控制权,还会出现两份真相 |
| 不照搬 | 按它的规模铺进程 | 实测整棵树 54 个进程、约 3.25 GB,工厂现场需要评估 |
形态可学,实现自建:Core 是唯一裁决者,Provider 独立部署且只暴露契约,客户端只做界面与本地守护。要切换引擎的做法见 5.2。