本地工作台双轨演进:Local Loop (STA-214) 与企业云端链路
架构状态声明(ADR-0092 /
STA-214/#449/#453):本专题记录已收敛并进入实施队列(8 张实施票STA-215~STA-222均为ready-for-agent,统一汇总与验收分支为双仓Stanley/local-loop)的本地工作台本地化架构方案,以及其与当前生产基线(aida-agent7f2e519936b5/aida-desktop4346d79eb364)的精确边界对照。本功能尚未完成产品总装验收,不代表当前默认分支已切换线上行为。
一、为什么需要“第二条路”(双轨并存)
AidaWork 同时面对两类核心诉求截然不同的用户群体:
- 企业级深度 Agent 用户(保持原服务端链路)
需要跨组织权限治理、企业连接器受控写入(
VerifiedWriteAuthority)、审批流、多端共享任务状态与服务端持久账本。其对话归属(Owner)在服务端,由aida-agent的OrchestratorService/driveReActLoop驱动。 - 类 Codex 纯本地能力用户(
STA-214本地工作台独立对话) 只使用本机文件(File V4)、本地 Skill 脚本与本地 Plugin 能力。在原链路下,每一次本地工具调用都必须经历“服务端driveReActLoop挂起落库 → SSE 推送 → 桌面端执行 → HTTP 回执提交 → 服务端重水化续跑”的跨端往返。STA-214(ADR-0092) 的核心切法:在保留现有内部账号登录与主组织核验的前提下,将本地工作台的 ReAct 循环、上下文组装、工具编排、交互等待、Stop 与任务状态全部下沉到桌面端本地,云端仅作为**“内部租户准入 + 单次模型调用网关”**。
二、六项已收敛的核心架构决定
1. 循环内核与进程/线程拓扑(Main Pi 1.4.0 + 单一 worker_thread SQLite/AES)
- 循环宿主:固定采用
@earendil-works/pi-agent-core+pi-ai(1.4.0),由 Electron Main 直接托管窗口无关的本地 runtime,拥有循环推进、目录/资源权限与登录代次(Epoch)。 - 加密存储线程:单一
worker_thread独占 SQLite (WAL/FULL) + AES-GCM(绑定持久身份 AAD);safeStorage的 OS 密钥解封仍在 Main 完成,解封后的数据密钥(Data Key)transfer 到 Worker 线程。 - 工程收益:既避免了独立本地 Daemon 进程带来的跨进程权限桥与监督复杂度,又消除了同步加密落盘阻塞 Main 事件循环导致的 UI 卡顿与
Stop延迟。
2. 持久提交屏障(COMMIT 后 ack,失败全 Store 熔断)
- 现有
RuntimeWorkDeliveryStore只是服务端权威下的内存/尽力落盘交付缓存,绝不直接升级为本地任务真相。 - 新本地 Store 要求:用户提交受理、完整模型响应、工具派发意图、用户确认接受与工具回执,均须等待 Worker 线程完成同 generation 的 SQLite
COMMIT并返回 durableack后,才允许触发下一外部派发。 - 一旦发生
nack、超时或 Worker 异常退出,立即关闭整个 Store 的新外部派发,且已派发的原效果绝不自动重放。
3. 云端极简网关与生产 Credits 记账(STA-215 / aida-agent #337)
- 本地工作台独立对话不在服务端创建
AgentRun或WorkbenchRun。 - 云端复用生产 Credits 体系:
admitCredits + withChargeSubject + meteredModel,其中runId为空,按llmCallId记账,不新增数据库计费 schema(保留并发不预留、取消/错误流不计账、缺 usage 估算的既有生产边界)。
4. rawToolInputs 原文保留与拦截 Pi 内核隐蔽行为
- 防止 Schema 参数强转:Pi 内核会对 JSON Schema 参数做类型强制转换(例如将数字字符串转为
number)。因此v1 admission + SSE model单次模型合同显式保留原始rawToolInputs / rawArgumentsJson与assistantMessages回放合同,由 Aida 按唯一严格规则校验并冻结执行参数,确保确认弹窗摘要 = 持久记录 = 实际执行输入。 - 显式 Stop 与 Epoch 栅栏:Pi 的
beforeToolCall / afterToolCall异常默认会转为普通工具错误并继续下一轮,且取消后仍可能进入StreamFn。本地宿主在模型发送前与工具派发前设置硬性的stop-fence与identity-fence(校验当前登录代次 Epoch)。
5. File、Skill、Plugin 三领域共同进入首次试点
- 对话归属创建即固定:本地工作台与企业对话在创建时固定执行 owner,禁止因模型输出、附件类型或网络错误在运行中跨轨切换。
- 三领域能力边界:
- File V4(
STA-219 / #457):复用LocalFileV4Executor、SelectedFileHandle 与 ADR-0090 目录授权语义(对话级授权 vs 设备级“始终允许”授权),首版固定文本/CSV 读写与变更确认。 - Skill(
STA-220 / #458):支持documents/records与argv两种 ABI、脚本准备、人工复核与真实产物落盘(服务端make_docx不装配,但不禁止本地 Skill 生成 DOCX)。 - Plugin V2(
STA-221 / #459):复用PluginHost真实 Worker,支持record-store与#459新增的pending-view不可变夹具,由本地交互 owner 闭环承载视图交互。 - 明确排除项:OCR/扫描件/原文件云端材料不在首版样例内,入口明确不可用,防止借云端材料服务重新滑回服务端工作台 loop。
- File V4(
6. 并发、重启恢复与双平台验收边界
- 同对话串行、跨对话独立:单个对话内严格串行执行工具;等待或停止的对话不阻塞其他对话。
- Main 重启不自动续跑 Run:进程重启后,核对原调用与领域效果回执,只恢复可查看的历史、产物和已发生效果事实,不继续原 Run。
- 双平台配对(
STA-222 / #460):macOS (arm64) 直接宿主;Windows (x64) 采用 HomeWin WSL2 隔离 Agent + Windows 原生 Preview(通过host-stdio-bridge与 installed 启动器配对)。
三、原链路 vs 本地 Loop 核心边界替换对照表
| 架构边界 | 当前服务端工作台实现(基线证据) | STA-214 (Stanley/local-loop) 本地化目标 |
|---|---|---|
| 提交入口 | sendRuntimeWorkSubmission 调用服务端 /api/chat 建立云端绑定 |
本地工作台对话由 Main 持久受理,不创建服务端 AgentRun |
| ReAct 循环 | local-workbench-dispatch.ts 在服务端调用 driveReActLoop |
Main 内嵌 @earendil-works/pi-agent-core (1.4.0) + 本地上下文预算 |
| 持久状态权威 | 服务端 PG transcript + 本地 RuntimeWorkDeliveryStore 缓存 |
单一 worker_thread 托管 SQLite WAL/FULL + AES-GCM(COMMIT 后 ack) |
| 模型与计费 | 绑定云端 runId 的多轮编排 |
云端单次 SSE 模型网关 + admitCredits / withChargeSubject 按 llmCallId 记账 |
| 文件/脚本/插件执行 | LocalFileV4Executor / Skill / PluginHost 等服务端指令并 POST 回执 |
Main 本地直调既有执行器,本地保存交互与效果事实引用 |
四、8 切片实施队列与依赖关系(视图 slice-roadmap)
| 阶段 | Linear / 仓库权威规格 | 核心交付内容 | 集成完成前置 |
|---|---|---|---|
| Stage 1 | STA-215 / ai/aida-agent #337 |
云端单次模型网关、内部租户准入、生产 Credits (llmCallId)、rawToolInputs 合同与同步 6 个漂移生成文件 |
无 |
| Stage 1 | STA-216 / ai/aida-desktop #454 |
单一 worker_thread SQLite/AES 存储、持久身份 AAD、登录代次 Epoch 与中断核对 |
无 |
| Stage 2 | STA-217 / ai/aida-desktop #455 |
Main 内 Pi 1.4.0 循环、上下文预算、共同工具装配、等待与 Stop 栅栏(含 Windows commit p99 优化复验) | STA-215、STA-216 |
| Stage 3 | STA-218 / ai/aida-desktop #456 |
本地工作台独立对话 owner、类型化 preload/Main IPC、确认交互、历史恢复与产物 UI 投影 | STA-217 |
| Stage 4 | STA-219 / ai/aida-desktop #457 |
File V4 真实目录授权(ADR-0090)、确认、文本/CSV 读写与效果接入 | STA-218 |
| Stage 4 | STA-220 / ai/aida-desktop #458 |
Skill documents/records 与 argv 两种 ABI、脚本准备、人工复核与真实产物接入 |
STA-218 |
| Stage 4 | STA-221 / ai/aida-desktop #459 |
Plugin 真实 Worker、record-store、pending-view 不可变夹具与本地视图交互接入 |
STA-218 |
| Stage 5 | STA-222 / ai/aida-desktop #460 |
双仓 Stanley/local-loop 冻结 SHA、macOS arm64 + Windows x64 (host-stdio-bridge) 安装包与真实模型试点 |
STA-219、STA-220、STA-221 |
五、同一插件包双宿主对照:企业云端 Work vs 本地工作台(视图 plugin-dual-host,2026-11-03)
不可变插件包(如 attendance-tally、salary-smartcalc、aida-cli、aida.mail)本身不感知宿主是云端还是本地,由 plugin-local-execution.ts 与 Desktop PluginHost 在两条轨道间复用同一套 Worker 进程池与权限模型,同时物理隔离状态与账本键空间:
| 维度 | 企业云端轨(owner: 'enterprise_agent') |
本地工作台轨(owner: 'local_workbench',STA-221) |
|---|---|---|
| 工具广告与冻结 | PluginHost.listProjections 随聊天请求上报,服务端 C0 bindAdmittedWork 冻结 pluginToolsDigest |
registerLocalPluginWorkbench + createLocalPluginProvider 直接将已安装 Bundle 装配进本地 Main Pi 1.4.0 工具表,计算 pluginLocalExecutionDigest |
| 调用入口 | 服务端 InteractionKernel 派发 execute_plugin,Desktop 调 PluginHost.invokeForRequest(request) |
本地 Worker 线程完成 SQLite COMMIT 并返回 durable ack 后,调 PluginHost.executeLocal(request) |
| PluginCallJournal 键空间 | schemaVersion: 1,绑定云端 { interactionId },写 <stepId>.json 并维护 diagnostic-index/ 指针 |
schemaVersion: 2,绑定本地 { executionId, conversationId, turnId, toolCallId },写 hash('local:' + executionId).json(无云端诊断索引) |
| 原生视图与动作闭环 | aida_plugin_open_view 写服务端 runtime_events,按钮点击走 InteractionKernel.settle + 7 表 Join 唤醒 |
LocalPluginTaskInteractions 在本地 SQLite 持久化待决视图与 runtime.answer,按钮点击校验 executionDigest 后直调 host.executeLocal |
| 崩溃/重启恢复边界 | 服务端 recoverExpiredWorkCarrier 探测 expiredPluginActionRecoveryCode,Desktop 重连只读拉取 |
启动时 register-local-plugin-workbench.ts 调用 reconcileInterruptedPluginCalls 对齐 PluginCallJournal 与本地 Store,只恢复已发生事实与视图,不自动续跑原 Run |
aida-agent·packages/tool-client/src/types/plugin-local-execution.tsL30–75 ·pluginLocalExecutionRequestSchema/pluginLocalExecutionDigest·7f2e519936b5aida-desktop·apps/desktop/electron/plugin-host/register-local-plugin-workbench.tsL8–40 ·registerLocalPluginWorkbench·4346d79eb364aida-desktop·apps/desktop/electron/plugin-host/local-plugin-provider.tsL43–107 / L270–310 ·createLocalPluginProvider/executeLocal·4346d79eb364aida-desktop·apps/desktop/electron/plugin-host/plugin-call-journal.tsL195–240 / L300–310 ·schemaVersion: 2/local:·4346d79eb364