rpa_core 后端架构 · ELI5(程序员版)
给懂代码的人讲的架构:只讲职责边界、数据流和为什么这样切。
① 一句话总览
一份 JSON 菜谱(workflow)经过静态检查变成不可变的执行单(plan),厨师长(orchestrator)按单依次派活给各工种执行器,每一步的产出写进备菜台(scopes)、拍一张进度快照(checkpoint)、记一行日志(events.jsonl),最后交一份质检单(result.json)。
② 分层依赖(单向,禁止反向)
为什么单向:依赖箭头永远指向更底层——model 是纯类型,谁都能依赖它;runtime 不知道有 devserver 存在。这条规则由 check_architecture.py 机械检查(import 方向分析),改不动。
③ 各层职责(配厨房类比)
catalog catalog/loader.py
启动时读 commands/*.json 成 manifest,算 sha256 digest 封存。运行期间内容不可变。
类比:装订成册的工艺规范 + 封条编号。恢复时拿 digest 对账,目录变一个字节就拒绝续跑。
compiler compiler/compiler.py
开跑前静态检查:节点 id 查重、命令存在性、${...} 引用是否可解析、能力授权、unsafe 命令禁重试。通过才产出冻结的 ExecutionPlan。
类比:开灶前核对食材和授权,通过盖章,单子封存不许改。
orchestrator runtime/orchestrator.py
递归走 AST、派单给执行器、收 CommandResult 回执。管取消(Event 传播)、attempt 级超时竞争、退避重试、路径键完成集合、checkpoint 原子写入、终态收口。
类比:厨师长——只派活、看表、记账,不掌勺。
executors executors/
四类:Playwright 浏览器、UIA 桌面、Win32 桌面、Python 子进程。输入 CommandInvocation,返回 CommandResult(value/outputs/effects/diagnostics/error),绝不触碰编排器状态。
类比:各工种厨师——接单干活交回执,不改菜单。
④ 关键设计决策(工程权衡)
| 决策 | 原因 | 代价 |
|---|---|---|
无 FastAPI / 无框架(stdlib http.server) |
设计期工具无需 Web 框架;ADR 0006 明排,依赖面为零 | 没有现成的路由/校验/并发基础设施,全部手写(已覆盖 405/413/403 等边界) |
| catalog 不可变 + digest | 一次运行一份契约快照;digest 是 resume 的合法性门槛 | 运行中改 manifest 必须重跑(恢复时直接拒绝) |
handler 只回 CommandResult |
执行器不改编排器状态 → 状态单向流动、可测试、可换实现 | 多一次 JSON 往返的开销 |
| 用户 Python 走子进程 | 崩溃/恶意代码不伤编排器进程 | 进程启动开销 + stdin/stdout 序列化 |
checkpoint 写在 stepCompleted 事件之前 |
崩溃窗口语义:进度权威优先于日志——宁可丢一行日志,不可重放副作用 | 日志可能缺行,审计时需注意 |
unknown effect → indeterminate 终态 |
失败和"外部结果未知"是两种风险等级,绝不自动重试 | 调用方必须处理这个状态,不能折叠进 failed |
| devserver 不承载 run | 设计期工具与运行时隔离(ADR 0007),UI 崩了不影响已提交的 run | 运行态和控制态分离,跨进程看结果要读 artifacts 文件 |
| 桌面捕获走独立 agent 子进程 | UIA/COM 状态不污染 devserver;崩溃隔离 | 每次 pick 一个进程生命周期 |
⑤ 一次 run 的数据流
⑥ So what(对你意味着什么)
改代码时记三条线:状态只经 scopes 单向流动(任何 executor 直接改 orchestrator 状态 = 违规);崩溃恢复只在节点边界(路径键完成集合,forEach 按迭代跳过);证据比执行优先(写日志失败立刻中止 run,绝不假成功)。违反这三条的 PR 会被合同测试拦住。