80 规范工作流系统 (Canonical Coding Workflow)
版本: v2.0 (Phase 1–5 + ADR Phase A–E) | 最后更新: 2026-04-09 | 灵感来源: oh-my-codex
状态: 全部五阶段实施完成,共 215+ 单元/集成/E2E 测试绿。详见
LIGHTWEIGHT_MULTI_AGENT_ORCHESTRATION_ADR.md。
1. 背景
现有 Coding Agent 允许用户直接下达任意指令,agent 可自行决定是否先澄清、是否先规划。实践中暴露两个问题:
- 需求不清就开写 — 用户给一句话,agent 直接改代码,改完才发现边界理解错了
- 多步骤任务无交接物 — agent 中途出错无法恢复,也无法让另一个 agent 接手
OMX 用 4 个固定入口 ($deep-interview → $ralplan → $ralph/$team) + .omx/ 持久目录解决了这两个问题。本模块把这套思路移植到 ChainlessChain,不替换现有 cowork / skills / coding-agent,而是作为一条 opinionated 默认路径叠加上去。
2. 目标与非目标
目标
- 为 Coding Agent 提供一条"澄清 → 规划 → 审批 → 执行"的固定默认路径
- 把阶段交接物(intent / plan / progress)固化成可读可编辑的文件
- 通过 Gate 强制阶段顺序,防止跳步
- 复用现有的四层 skill 加载器,0 侵入注册
非目标
- ❌ 不引入 tmux(Windows 生态差,Electron 集成复杂)
- ❌ 不替换 cowork / skills / coding-agent
- ❌ 不做自动审批(approved 必须显式触发)
- ❌ 不做 MVP 范围外的 lifecycle hooks(留给 Phase 4)
3. 架构
3.1 分层
┌────────────────────────────────────────────────┐
│ 用户 / Coding Agent 会话 │
└────────────────┬───────────────────────────────┘
│
▼ 调用 workflow 技能
┌────────────────────────────────────────────────┐
│ Workflow Skills (4 个 builtin) │
│ deep-interview / ralplan / ralph / team │
└────────────────┬───────────────────────────────┘
│
▼ 读写阶段状态
┌────────────────────────────────────────────────┐
│ SessionStateManager │
│ .chainlesschain/sessions/<id>/ 文件读写 │
│ + Gate 校验 (阶段顺序 + approved 标志) │
└────────────────┬───────────────────────────────┘
│
▼ fs
┌────────────────────────────────────────────────┐
│ 持久层 │
│ intent.md / plan.md / progress.log / mode.json │
└────────────────────────────────────────────────┘3.2 状态机
┌──────────┐
│ (none) │
└─────┬────┘
│ $deep-interview
▼
┌──────────┐
│ INTENT │ ──── $ralplan ────▶ PLAN (approved=false)
└──────────┘ │
│ $ralplan --approve
▼
PLAN (approved=true)
│
┌─────────────────┼─────────────────┐
│ │
▼ ▼
$ralph $team
│ │
▼ ▼
EXECUTE (single owner) EXECUTE (parallel)
│ │
└────────────── markDone ───────────┘
│
▼
DONE3.3 Gate 规则
| Gate | 位置 | 逻辑 |
|---|---|---|
| G1: 必须先 intent | SessionStateManager.writePlan | 不存在 intent.md 即 throw |
| G2: 必须 approved | SessionStateManager.appendProgress | plan.md frontmatter approved: false 即 throw |
| G3: ralph/team 入口 | handler.execute 开头 | readPlan + approved 双检查 |
| G4: sessionId 安全 | SessionStateManager.safeSessionId | 正则 ^[A-Za-z0-9._-]+$ 防路径穿越 |
4. 文件结构
4.1 新增代码
desktop-app-vue/src/main/
├── ai-engine/code-agent/
│ └── session-state-manager.js # 核心状态管理器
└── ai-engine/cowork/skills/builtin/
├── deep-interview/
│ ├── SKILL.md
│ └── handler.js
├── ralplan/
│ ├── SKILL.md
│ └── handler.js
├── ralph/
│ ├── SKILL.md
│ └── handler.js
└── team/
├── SKILL.md
└── handler.js
desktop-app-vue/src/main/ai-engine/cowork/__tests__/
└── workflow-skills.test.js # 25 tests4.2 运行时产物
<project>/.chainlesschain/sessions/<sessionId>/
├── intent.md # markdown, 手工可读写
├── plan.md # markdown + YAML frontmatter
├── progress.log # append-only text
└── mode.json # { stage, updatedAt }5. 关键设计决策
5.1 为什么用文件而非数据库?
- 可读可编辑 — 用户可以手动改 plan.md,对 agent 透明
- git 友好 — 工作流状态可以纳入版本控制
- 零依赖 — 不占用 SQLCipher 的表 quota
- OMX 对标 — 跟
.omx/保持同构,降低用户学习成本
5.2 为什么 session 放项目目录而非 appData?
- OMX 的
.omx/就在项目目录(跟代码一起迁移) - 项目作用域的状态更直观
appData/chainlesschain-desktop-vue/.chainlesschain/config.json继续管全局配置,不冲突
5.3 为什么 $ralplan --approve 独立而非自动?
- OMX 的经验:审批是最容易被跳过的步骤,必须显式触发
- 审批语义保留给未来的权限系统(例如企业版 RBAC 里审批需要特定角色)
5.4 为什么 $team 不真实 spawn 子 agent?
- 保持单一职责:workflow 技能只负责分派计划
- 真正的子 agent 生命周期归 cowork runtime 管
- 降低测试复杂度:handler 是纯函数式,易测
5.5 SessionId 的产生
- 用户不传:handler 自动生成
session-<timestamp> - 用户传:必须通过
safeSessionId正则校验 - 恢复:通过
context.sessionId或task.params.sessionId复用
6. 与现有系统集成
6.1 Skill 四层加载
无需改 skill-loader.js。新技能放 cowork/skills/builtin/ 即 bundled 层,加载器自动发现。
6.2 Coding Agent Session Service
Phase 2 未改动 coding-agent-session-service.js。Phase 3 会注入 SessionStateManager,把 coding-agent 的 sessionId 与 workflow 的 sessionId 绑定。
6.3 Cowork Runtime
$team 的输出是一个 assignments[] 数组,等 Phase 5 实现 team runtime 时可以直接喂给它。
7. 测试策略
7.1 Unit Test
workflow-skills.test.js — 25 tests:
- SessionStateManager: 9 tests (gate / CRUD / stage)
$deep-interview: 3 tests$ralplan: 4 tests$ralph: 3 tests$team: 6 tests
7.2 测试关键点
- 真实 tmp 目录 — 不 mock fs,避免 Vitest forks pool + CJS 内联的坑
- Gate 验证 — 每个 Gate 都有对应的 negative test
- 安全输入 — sessionId 路径穿越测试
- 辅助函数独立测试 —
parseSpec/distributeSteps单独断言
7.3 运行
cd desktop-app-vue
npx vitest run src/main/ai-engine/cowork/__tests__/workflow-skills.test.js预期:25 pass, ~250ms。
8. 后续 Phase 规划
| Phase | 内容 | 说明 |
|---|---|---|
| Phase 1 ✅ | 4 workflow skills | 已完成 |
| Phase 2 ✅ | SessionStateManager | 已完成 |
| Phase 3 ✅ | $xxx 解析器 + 分派器 + CLI 检查器 | 已完成 |
| Phase 3.5 ✅ | AIChatPage UI 集成(IPC + 输入拦截) | 已完成 |
| Phase 4 ✅ | Lifecycle hooks (.chainlesschain/hooks/*.js) | 已完成 |
| Phase 5 ✅ | Sub-runtime pool — 真 spawn 多个 Electron-main 子 runtime | 已完成 |
| Phase 6 | 文档站 + 模板 | cc init --template coding-workflow |
Phase 4 详情:生命周期 Hooks
设计目标:在不修改核心工作流代码的前提下,让用户 / 企业注入审计、通知、策略拦截、CI 触发等横切关注点。
文件布局:<projectRoot>/.chainlesschain/hooks/<event>.js,每个事件一个独立 JS 文件,存在才执行、缺失即跳过。
8 个事件:pre-intent / post-intent / pre-plan / post-plan / pre-execute / post-execute / pre-done / post-done。前 6 个由 4 个 workflow 技能主动调用;done 相关事件为 Phase 6 的 $done 技能预留。
Hook 合约:module.exports = async ({ event, sessionId, projectRoot, payload }) => any。也接受 { run } / { default } 形式。
Veto 语义:
pre-*hook 抛异常或超时 → runner 重新抛出 → 调用方(技能 handler)捕获后返回{ success: false, error }→ 状态文件不写入post-*hook 抛异常或超时 → 仅记录日志,不回滚;保证已完成的工作不被 hook bug 破坏
关键实现细节:
require.cache每次调用前清除,支持 hook 文件热编辑无需重启 Electron- 默认 30 秒超时,
Promise.race实现;可通过ctx.timeoutMs覆盖 - 所有技能 handler 通过
_deps.runHook注入,测试用例可覆盖 hook 行为而不依赖文件系统 - Hook 跑在 Electron 主进程,拥有完整 Node 权限 → 用户文档警示"只放自己可信的脚本"
Phase 5 详情:Sub-runtime Pool(真 spawn 多个 Electron-main 子 runtime)
设计目标:$team 不再只返回"路由规划",而是真正为每个 member 拉起一个独立的 Electron-main 子进程(via process.execPath + ELECTRON_RUN_AS_NODE=1),在 OS 级别做故障隔离,并把每个 member 的工作限定在自己的目录里以避免并发写冲突。
关键冲突分析(选型前做过的功课):
- CLI ↔ Desktop 互不冲突:CLI 只有
workflow-state-reader(只读)和 in-memorySubAgentRegistry(WS 用,不碰文件系统),desktop 独占写者身份。 - Sub-runtime ↔ Sub-runtime 会冲突:若多个子 runtime 共享同一
sessionId并发写progress.log/mode.json→ 错行、覆盖、YAML 坏掉。 - 解法 A(采纳):
sessionId 分片。每个 member 分配独立的<parentId>.m<idx>-<role>子 session,拥有自己独立的目录。 - 解法 B(弃用):对
progress.log加文件锁。会把并行退化成串行,失去意义。
文件与职责:
| 文件 | 角色 |
|---|---|
desktop-app-vue/src/main/sub-runtime/index.js | 子 runtime 入口(headless,无 BrowserWindow,无 DI,无数据库,无 MCP)。读 stdin JSON-lines,写 stdout JSON-lines,直接调 SessionStateManager。 |
desktop-app-vue/src/main/ai-engine/code-agent/sub-runtime-pool.js | 父进程 pool:spawn(process.execPath, [entry], { env: { ELECTRON_RUN_AS_NODE: "1" } }) + stdin/stdout 协议 + _deps 注入。 |
desktop-app-vue/src/main/ai-engine/code-agent/session-state-manager.js | 新增 memberSessionId / createMemberSession / listMemberSessions / readMemberProgress 四个 API。 |
desktop-app-vue/src/main/ai-engine/cowork/skills/builtin/team/handler.js | 改造为 new _deps.SubRuntimePoolCtor(...).dispatch(...);父 session 只作为聚合单写者追加一行 [team] dispatched NxR + 每个 member 的 ok/fail 摘要。 |
Stdio JSON-lines 协议:
# parent → child (stdin, 每行一个 JSON)
{ "cmd": "run", "projectRoot": "...", "sessionId": "parent", "assignment": { "memberIdx": 0, "role": "executor", "steps": ["a", "b"] } }
{ "cmd": "shutdown" }
# child → parent (stdout, 每行一个 JSON)
{ "type": "ready" }
{ "type": "progress", "memberId": "parent.m0-executor", "step": "a", "index": 0, "total": 2 }
{ "type": "done", "memberId": "parent.m0-executor", "success": true }
{ "type": "error", "memberId": "...", "error": "..." }
{ "type": "bye" }Member session 布局:
.chainlesschain/sessions/
├── parent-id/ ← 父 session(只读 + 聚合写者)
│ ├── intent.md
│ ├── plan.md (approved)
│ ├── mode.json
│ └── progress.log ← 父 handler 写(单写者,无竞争)
├── parent-id.m0-executor/ ← 子 runtime 0 独占
│ ├── intent.md
│ ├── plan.md (approved, steps=自己那份)
│ ├── mode.json
│ └── progress.log ← 子 runtime 0 自己写
└── parent-id.m1-reviewer/
└── ...生命周期与容错:
- 每个 assignment = 一次性子 runtime。
run完成后父发shutdown,子process.exit(0);500ms 宽限期后 SIGTERM 兜底。 - Pool 硬上限
HARD_CAP=6(和$teamsize cap 对齐),默认maxSize=4。 readyTimeoutMs=10s/runTimeoutMs=60s,超时自动降级为{ success: false, error: "..." }结果,绝不挂起。- 子 runtime 崩溃(exit before done)会被 pool 捕获并映射为失败结果,其他 member 不受影响。
- Pool 不持有任何状态 —— 没有 idle 重用;换取的是"失败隔离 = 进程边界"的清晰心智模型。
测试矩阵:
| 层级 | 文件 | 数量 |
|---|---|---|
| Unit — SessionStateManager member APIs | code-agent/__tests__/session-state-manager.test.js | 9 |
| Unit — Sub-runtime dispatcher | sub-runtime/__tests__/sub-runtime-entry.test.js | 6 |
| Unit — SubRuntimePool (mocked spawn) | code-agent/__tests__/sub-runtime-pool.test.js | 7 |
| Unit — team handler (fake pool injection) | cowork/__tests__/workflow-skills.test.js 更新 | (已存在) |
| Integration — 真 spawn 子进程 | tests/integration/sub-runtime-pool.integration.test.js | 2 |
已排除的反模式:
- ❌ 子 runtime 内部再起
CodingAgentBridge→ 进程爆炸(N × Electron-main + N × CLI-serve) - ❌ 走 WebSocket 通信 → 端口争用 + 认证复杂度 + 启动延迟;stdio 更简单可靠
- ❌ Worker Threads /
fork()→ 无法真正隔离 Electron main state,crash 会波及父进程 - ❌ 共享
sessionId加文件锁 → 退化成串行
9. 风险与权衡
| 风险 | 缓解 |
|---|---|
| 用户绕过工作流直接让 agent 改代码 | 工作流是可选的,不强制;但 Coding Agent 默认 prompt 会推荐 |
.chainlesschain/sessions/ 污染仓库 | 加入 .gitignore 模板;进阶用户可自行提交 |
| 多项目共用一个 session | 项目作用域隔离,sessionId 只在当前 projectRoot 有效 |
| plan.md 手工编辑破坏 frontmatter | readPlan 正则容错,解析失败回退为 approved: false |
| 长 progress.log 导致性能问题 | append-only,不读整个文件;Phase 4 可加 rotate |
10. 变更清单
Phase 1+2 新增:
desktop-app-vue/src/main/ai-engine/code-agent/session-state-manager.js(+220)desktop-app-vue/src/main/ai-engine/cowork/skills/builtin/deep-interview/{SKILL.md,handler.js}desktop-app-vue/src/main/ai-engine/cowork/skills/builtin/ralplan/{SKILL.md,handler.js}desktop-app-vue/src/main/ai-engine/cowork/skills/builtin/ralph/{SKILL.md,handler.js}desktop-app-vue/src/main/ai-engine/cowork/skills/builtin/team/{SKILL.md,handler.js}desktop-app-vue/src/main/ai-engine/cowork/__tests__/workflow-skills.test.js(25 tests)
Phase 3 新增:
desktop-app-vue/src/main/ai-engine/code-agent/workflow-command-parser.js— 纯函数解析器desktop-app-vue/src/main/ai-engine/code-agent/workflow-command-runner.js— in-process 分派器desktop-app-vue/src/main/ai-engine/code-agent/__tests__/workflow-command-parser.test.js(13 tests)desktop-app-vue/src/main/ai-engine/code-agent/__tests__/workflow-command-runner.test.js(4 tests)packages/cli/src/lib/workflow-state-reader.js— CLI 侧只读 readerpackages/cli/src/commands/session.js— 新增session workflow子命令packages/cli/__tests__/unit/workflow-state-reader.test.js(5 tests)
Phase 3.5 新增:
desktop-app-vue/src/main/ai-engine/code-agent/coding-agent-ipc-v3.js— 2 个工作流通道 + handlerdesktop-app-vue/src/preload/index.js—codingAgent.{check,run}WorkflowCommanddesktop-app-vue/src/renderer/types/electron.d.ts— 类型定义desktop-app-vue/src/renderer/pages/AIChatPage.vue—handleSubmitAgentAwareMessage正则拦截desktop-app-vue/src/main/ai-engine/code-agent/__tests__/coding-agent-ipc-v3.test.js(+3 tests)
Phase 4 新增:
desktop-app-vue/src/main/ai-engine/code-agent/workflow-hook-runner.js— hook 加载 / 执行 / 超时 / vetodesktop-app-vue/src/main/ai-engine/code-agent/__tests__/workflow-hook-runner.test.js(16 tests)deep-interview/ralplan/ralph/teamhandler 都通过_deps.runHook注入 pre-/post- 事件workflow-skills.test.js新增 4 个 hook 集成测试(post-intent 写入验证 + 3 个 veto 场景)
Phase 5 新增:
desktop-app-vue/src/main/sub-runtime/index.js— headless Electron-main 入口 + stdio JSON-lines 调度desktop-app-vue/src/main/sub-runtime/__tests__/sub-runtime-entry.test.js(6 tests)desktop-app-vue/src/main/ai-engine/code-agent/sub-runtime-pool.js— 父进程 pool +_deps.spawn注入desktop-app-vue/src/main/ai-engine/code-agent/__tests__/sub-runtime-pool.test.js(7 tests,全假 child)session-state-manager.js+93 行:memberSessionId/createMemberSession/listMemberSessions/readMemberProgresssession-state-manager.test.js(9 tests,新文件)team/handler.js改造:真调 pool.dispatch,把 dispatchResults 写进父 progress.logtests/integration/sub-runtime-pool.integration.test.js(2 real-spawn tests)
ADR Phase A–D 新增 (2026-04-09):
session-state-manager.js扩展:writeTasks/readTasks/writeVerify/readVerify/setRoutingHint/ fix-loop retries 计数$verify/$completebuiltin skills + 门控(verify.status 必须为 passed)$teamhandler 升级:按tasks.json的scopePaths拆分,不再平均分编号步骤workflow-session-ipc.js—— 4 个 read-only IPC 通道 (list/get/list-members/classify-intake)workflow-session.ts—— Pinia store 只读消费 session 状态CanonicalWorkflowPanel.vue—— Workflow Monitor 子面板(session 列表 + stage + task readiness + verify checks + routing hint)
ADR Phase E 新增 (2026-04-09):
desktop-app-vue/src/main/ai-engine/code-agent/intake-classifier.js—— 纯函数路由启发式- 输入:
{ request, scopePaths, fileHints, sessionId, tasks, concurrency } - 输出:
{ decision: "ralph"|"team", confidence, complexity, scopeCount, boundaries, testHeavy, signals, reason, recommendedConcurrency, suggestedRoles } - 规则:
scopeCount>=2→ team | cross-cutting 短语 → team | trivial 短语 → ralph | 单 scope → ralph | 默认 → ralph
- 输入:
deep-interview/handler.js集成:写完 intent.md 后调用分类器,通过setRoutingHint合并写入mode.json(非阻塞、失败降级为null)workflow-session-ipc.js+workflow-session:classify-intake通道(sessionId提供时自动富化 tasks.json)preload/index.js+workflowSession.classifyIntakeworkflow-session.tsPinia store +classifyIntakeaction +lastClassification状态CanonicalWorkflowPanel.vue新增 routing hint 展示行(decision tag / complexity / confidence / scopeCount / reason / suggested roles)- 跨阶段不变式:
_updateModemerge-write 天然保证routingHint跨 stage 迁移不丢失
文档:
docs-site/docs/chainlesschain/coding-workflow.md(用户文档,含 Phase 1–5 + ADR Phase A–E 全量示例)docs/design/modules/80_规范工作流系统.md(本文件)docs/design/modules/81_轻量多Agent编排系统.md(ADR 设计讨论)docs/implementation-plans/LIGHTWEIGHT_MULTI_AGENT_ORCHESTRATION_ADR.md(实施计划,已关闭)
测试总数:
- Phase 1–5: 94 tests
- ADR Phase A–D: +67 tests (state + IPC + store + UI + verify)
- ADR Phase E: +54 tests (classifier 20 + IPC 5 + store 3 + handler 4 + integration 5 + E2E 7 + deep-interview 集成 10)
- 合计 215+ tests 全绿,其中 Phase 5 含 2 个真 spawn 集成,Phase E 含 7 个 handler → fs → IPC → preload shim → Pinia store 全链路 E2E
维护者: 开发团队 相关文档: coding-workflow.md, 79_Coding_Agent系统.md
