Commit 38b9542017384611bf069b2c133bb75e9da7d92d

Authored by graphlemon
1 parent 147be052

graphlemon: auto-commit uncommitted changes for 81700b35-ea5b-4718-9846-4ff0f924…

…c231-main-checkout-drift
Showing 31 changed files with 379 additions and 886 deletions
.claude/agent-memory/planner/MEMORY.md
@@ -2,4 +2,5 @@ @@ -2,4 +2,5 @@
2 - [scheduler.py busy-timeout comment missing](project_scheduler_busy_timeout_comment_missing.md) — as of 2026-08-03 _BUSY_TIMEOUT_MS (scheduler.py:78) has no inline comment; PL does not self-edit, packages as a Unit for Code Agent 2 - [scheduler.py busy-timeout comment missing](project_scheduler_busy_timeout_comment_missing.md) — as of 2026-08-03 _BUSY_TIMEOUT_MS (scheduler.py:78) has no inline comment; PL does not self-edit, packages as a Unit for Code Agent
3 - [deploy_health_check.py startup-timeout comment: RESOLVED](project_deploy_health_check_startup_timeout_comment_missing.md) — comment now present above HEALTH_CHECK_STARTUP_TIMEOUT (line 24); re-verify by reading file before re-flagging 3 - [deploy_health_check.py startup-timeout comment: RESOLVED](project_deploy_health_check_startup_timeout_comment_missing.md) — comment now present above HEALTH_CHECK_STARTUP_TIMEOUT (line 24); re-verify by reading file before re-flagging
4 - [deploy_health_check.py HTTP-timeout comment missing](project_deploy_health_check_http_timeout_comment_missing.md) — HEALTH_CHECK_HTTP_TIMEOUT (line 26) has no comment directly above it, unlike its sibling STARTUP_TIMEOUT; distinct open fact, don't conflate the two 4 - [deploy_health_check.py HTTP-timeout comment missing](project_deploy_health_check_http_timeout_comment_missing.md) — HEALTH_CHECK_HTTP_TIMEOUT (line 26) has no comment directly above it, unlike its sibling STARTUP_TIMEOUT; distinct open fact, don't conflate the two
5 -- [artifact overview click-through bug: 3 real gaps](project_artifact_overview_click_through_bug.md) — renderArtifactSummaryList has no data-artifact-id/click (confirmed); router has zero query-string support (#/artifacts?id=x falls through to Not found); showPreview() already exists, just never called from a URL param 5 +- [artifact overview click-through bug: RESOLVED](project_artifact_overview_click_through_bug.md) — re-verified 2026-08-06, all 3 gaps fixed (data-artifact-id, #/artifacts/:id route, mountArtifactBrowser seeding); re-check before trusting "resolved" long-term
  6 +- [org-strip click-through bug: 3 render sites, not 1](project_org_strip_click_through_bug.md) — renderOrgStrip (dashboard), v_task_detail's inline org-strip, and #/graph's renderOrgGraphTopology all share the same no-click gap; DAG/log/artifact display mechanisms already exist and must be reused, not rebuilt; no new backend endpoint needed
.claude/agent-memory/planner/project_artifact_overview_click_through_bug.md
1 --- 1 ---
2 name: project-artifact-overview-click-through-bug 2 name: project-artifact-overview-click-through-bug
3 -description: Investigation findings for the "Planner 产出总览" list-item click-through bug (frontend/app.js), needed by whoever writes the SDD/benchmark content for this task 3 +description: "RESOLVED 2026-08-06 (re-verified by direct file read): all 3 gaps fixed — data-artifact-id, #/artifacts/:id route, and mountArtifactBrowser(initialArtifactId) all present."
4 metadata: 4 metadata:
5 type: project 5 type: project
6 --- 6 ---
7 7
8 -Investigated 2026-08-04 for the "Planner 产出总览面板列表项点击无反应" bug-fix task  
9 -(graph_manifest planned same session). Root cause is real and narrower than it first  
10 -looks — three separate gaps, not one: 8 +**RESOLVED as of 2026-08-06** (re-verified by reading `frontend/app.js` directly while
  9 +investigating the unrelated Org Graph click-through bug, see [[project-org-strip-click-through-bug]]):
  10 +all three gaps below have been fixed. `renderArtifactSummaryList`/`renderArtifactSummaryList`'s
  11 +`<li>` now carries `data-artifact-id` (app.js:1360), `ARTIFACT_DETAIL_RE` (`#/artifacts/:id`) is a
  12 +real dynamic route wired in `render()` (app.js:99,125-129), and `mountArtifactBrowser(initialArtifactId)`
  13 +(app.js:1603) seeds `selectedArtifactId` from it and calls the existing `showPreview()` flow. Don't
  14 +re-flag this as open without re-reading the file first — but do keep re-verifying, per CLAUDE.md's
  15 +general warning that autonomous self-hosting runs can fix or re-break things between sessions.
  16 +
  17 +**Original investigation (2026-08-04)**, kept for history — root cause was real and narrower than
  18 +it first looked, three separate gaps, not one:
11 19
12 1. `renderArtifactSummaryList()` (frontend/app.js:1344) renders bare `<li>` with no 20 1. `renderArtifactSummaryList()` (frontend/app.js:1344) renders bare `<li>` with no
13 `data-artifact-id` and no click handler — confirmed, matches the task's stated root cause. 21 `data-artifact-id` and no click handler — confirmed, matches the task's stated root cause.
.claude/agent-memory/planner/project_loop_round_bug_already_fixed.md
@@ -17,7 +17,7 @@ i.e. a follow-up request to &quot;add a one-line clarifying comment above `_LOOP_LIMI @@ -17,7 +17,7 @@ i.e. a follow-up request to &quot;add a one-line clarifying comment above `_LOOP_LIMI
17 REQ-05/REQ-09" is *also* already satisfied verbatim in substance. Two independent asks about this 17 REQ-05/REQ-09" is *also* already satisfied verbatim in substance. Two independent asks about this
18 same three-line region of the file have both turned out to be already-done on inspection. 18 same three-line region of the file have both turned out to be already-done on inspection.
19 19
20 -**Why:** `docs/self-development-feasibility.md` §"场景" uses "fix the loop_round off-by-one 20 +**Why:** `docs/done/self-development-feasibility.md` §"场景" uses "fix the loop_round off-by-one
21 (`loop_round > 3` should be `loop_round >= 3`)" as the canonical illustrative example goal for 21 (`loop_round > 3` should be `loop_round >= 3`)" as the canonical illustrative example goal for
22 graphlemon's self-hosting/self-modification demo, and this exact string is hardcoded as the goal 22 graphlemon's self-hosting/self-modification demo, and this exact string is hardcoded as the goal
23 text in `supervisor/test_self_hosting_e2e.py`'s `RealSelfHostingClosedLoopTest` (gated behind 23 text in `supervisor/test_self_hosting_e2e.py`'s `RealSelfHostingClosedLoopTest` (gated behind
@@ -26,7 +26,7 @@ glance, but it is a fixture/example string, not a currently-true bug report — @@ -26,7 +26,7 @@ glance, but it is a fixture/example string, not a currently-true bug report —
26 was written already-correct. 26 was written already-correct.
27 27
28 **How to apply:** If asked again to plan/fix/investigate this specific goal text (or anything 28 **How to apply:** If asked again to plan/fix/investigate this specific goal text (or anything
29 -that cites `docs/self-development-feasibility.md`'s example scenario), re-verify current state of 29 +that cites `docs/done/self-development-feasibility.md`'s example scenario), re-verify current state of
30 `src/org_loop_route.py` and `src/test_org_loop_route.py` first rather than assuming the bug is 30 `src/org_loop_route.py` and `src/test_org_loop_route.py` first rather than assuming the bug is
31 still open — it may have been fixed between conversations, or the request may actually be testing 31 still open — it may have been fixed between conversations, or the request may actually be testing
32 the *planning process* (e.g. via the self-hosting E2E harness) rather than asking for a real code 32 the *planning process* (e.g. via the self-hosting E2E harness) rather than asking for a real code
.claude/agent-memory/planner/project_org_strip_click_through_bug.md 0 → 100644
  1 +---
  2 +name: project-org-strip-click-through-bug
  3 +description: Investigation findings for the Org Graph (Planner/Coder/Validator) node click-through bug across the 3 places it's rendered — needed for the SDD/benchmark content of this task.
  4 +metadata:
  5 + type: project
  6 +---
  7 +
  8 +Investigated 2026-08-06 for "控制台 Org Graph 三节点条点击没有反应" (graph_manifest planned same
  9 +session). The stated root cause (`renderOrgStrip()` renders bare divs, no `data-*`, no click, no
  10 +`cursor:pointer`) is real, but it is **one of three separate render sites with the same gap**, not
  11 +the only one — don't scope the fix to just `renderOrgStrip`:
  12 +
  13 +1. `renderOrgStrip()` (frontend/app.js:589-598, mounted in `mountDashboard`, `id="org_strip"`,
  14 + under the "Org Graph 三节点状态" heading in `v_dashboard`). Confirmed: no `data-*`, no listener.
  15 +2. `v_task_detail`'s inline org-strip (app.js:1085-1088, inside `renderDetail()`, under an actual
  16 + `<h2>Org Graph</h2>` heading) — same `.org-strip`/`.org-node` markup pattern, same gap, built
  17 + inline rather than via a shared function. If `renderOrgStrip` gets a shared click-handling
  18 + helper, this call site needs the same wiring, not a copy-paste duplicate.
  19 +3. `renderOrgGraphTopology()` (app.js:1322-1347, used only by `#/graph`'s `mountGraphConsole`) is a
  20 + *different, newer* implementation (`.graph-topo-node`, not `.org-strip`/`.org-node` — added
  21 + 2026-08-03 as `v_graph_console` FR-1, explicitly "not org-strip" per the comment at app.js:1314)
  22 + that already shows per-node `status-*` classes and an active/retry-loop edge, but its 3 nodes
  23 + are *also* not clickable — only the Work Graph sub-nodes inside it are (`mountWorkNodeClickExpand`,
  24 + wired via `renderGraphVisualizerPanel(run, { enableNodeClick: true })`, which already has real
  25 + click → `renderLogViewerHtml`/`mountLogViewer` wiring, confirmed working).
  26 +
  27 +**What already exists and must be reused, not rebuilt** (confirmed by reading the code, not by
  28 +trusting the task's framing): `renderGraphVisualizerPanel(run, {enableNodeClick:true})` +
  29 +`mountWorkNodeClickExpand` + `renderLogViewerHtml`/`mountLogViewer` (real WS live-tail +
  30 +`c_execution_log_get` static fallback, `frontend/app.js:942-1016`) is the exact combo `#/graph`
  31 +already uses per-task. `mountArtifactsOverviewPanel`'s doc/code lists already link through to
  32 +`#/artifacts/:artifact_id` (`v_artifact_browser`, `showPreview()`), see
  33 +[[project-artifact-overview-click-through-bug]] (now resolved) for that mechanism's shape.
  34 +
  35 +**Backend note**: no new endpoint needed. `GET /api/tasks/:task_id` (`task_query.get_task`,
  36 +`src/task_query.py:115-171`) already returns `work_graph_runs` (each with `org_node`, `run_id`,
  37 +`status`, `nodes`) ordered by `started_at` ASC, and `artifacts` (each with `produced_by_run_id`
  38 +linking back to a `work_graph_run.run_id`) ordered by `created_at` ASC. "Latest work_graph_run for
  39 +org_node X" = filter `work_graph_runs` by `org_node === X` and take the last (loop retries can
  40 +produce more than one run per org_node, e.g. `full_replan` re-triggers planner) — this is pure
  41 +frontend filtering of data already on the wire, not a new query.
  42 +
  43 +**CSS**: `.org-node`/`.graph-topo-node` (`frontend/style.css:42-47,127-139`) have no `cursor:pointer`
  44 +today, unlike `.work-node` (style.css:104, already clickable) and `ul.artifact-list li` (style.css:156).
  45 +
  46 +**How to apply**: when writing the SDD, scope the fix as one shared click-handling mechanism
  47 +(scoped to the *current task* being viewed, not global across tasks) applied at all 2-3 render
  48 +sites that actually ship on this pass, composing already-existing panels (DAG + log viewer +
  49 +artifact link-through) rather than inventing a 4th display mechanism. Decide and record explicitly
  50 +whether `renderOrgGraphTopology`'s topology nodes are in scope for this task (they're a newer,
  51 +separate implementation with the same gap) or deferred — don't silently skip them without a note.
.claude/agent-memory/validator/project_self_hosting_loop_round_probe.md
@@ -9,7 +9,7 @@ Task `14fd9ab6-e2c8-4233-8381-002419de85bb` (&quot;fix loop_round &gt; 3 should be &gt;= 3 @@ -9,7 +9,7 @@ Task `14fd9ab6-e2c8-4233-8381-002419de85bb` (&quot;fix loop_round &gt; 3 should be &gt;= 3
9 p_org_task_lifecycle") turned out to be a **no-op investigation task**: `src/org_loop_route.py` 9 p_org_task_lifecycle") turned out to be a **no-op investigation task**: `src/org_loop_route.py`
10 has used `loop_round >= _LOOP_LIMIT` (limit=3) since the very first commit that introduced the 10 has used `loop_round >= _LOOP_LIMIT` (limit=3) since the very first commit that introduced the
11 file (`59749fc`, U-016) — it was never `> 3`. The `> 3` phrasing only appears in 11 file (`59749fc`, U-016) — it was never `> 3`. The `> 3` phrasing only appears in
12 -`docs/self-development-feasibility.md:50`, an explicitly-labeled **hypothetical illustration** 12 +`docs/done/self-development-feasibility.md:50`, an explicitly-labeled **hypothetical illustration**
13 in §3 "模拟走一遍:修复自己状态机里的一个边界 bug" (a PH5 self-hosting feasibility walkthrough, 13 in §3 "模拟走一遍:修复自己状态机里的一个边界 bug" (a PH5 self-hosting feasibility walkthrough,
14 not a real bug report). I independently re-derived this from `git log -p --follow` on the file 14 not a real bug report). I independently re-derived this from `git log -p --follow` on the file
15 and by running `python3 -m unittest src.test_org_loop_route -v` (11/11 pass) — confirmed the 15 and by running `python3 -m unittest src.test_org_loop_route -v` (11/11 pass) — confirmed the
@@ -17,7 +17,7 @@ Planner/Coder&#39;s claim rather than trusting it. @@ -17,7 +17,7 @@ Planner/Coder&#39;s claim rather than trusting it.
17 17
18 **Why this matters:** this repo (graphlemon) sometimes runs tasks *against its own source tree* 18 **Why this matters:** this repo (graphlemon) sometimes runs tasks *against its own source tree*
19 (self-hosting/self-modification, see CLAUDE.md's "Two things named Supervisor" section and 19 (self-hosting/self-modification, see CLAUDE.md's "Two things named Supervisor" section and
20 -`docs/self-development-feasibility.md`). PH5 (actual self-hosting execution, U-034~U-043) is 20 +`docs/done/self-development-feasibility.md`). PH5 (actual self-hosting execution, U-034~U-043) is
21 explicitly **not in scope** per CLAUDE.md yet — but *investigation-only* self-referential tasks 21 explicitly **not in scope** per CLAUDE.md yet — but *investigation-only* self-referential tasks
22 like this one (verify graphlemon's own code against its own spec) apparently are being run ahead 22 like this one (verify graphlemon's own code against its own spec) apparently are being run ahead
23 of PH5, and "no code change, verified no defect" is a legitimate, complete outcome for such a 23 of PH5, and "no code change, verified no defect" is a legitimate, complete outcome for such a
CLAUDE.md
@@ -131,7 +131,7 @@ python3 -m src.backend_server --port 8000 # default --port 0 means OS-assigned @@ -131,7 +131,7 @@ python3 -m src.backend_server --port 8000 # default --port 0 means OS-assigned
131 (U-034~U-043): `deploy_stage.py` (stage a candidate), `deploy_health_check.py` (poll for 131 (U-034~U-043): `deploy_stage.py` (stage a candidate), `deploy_health_check.py` (poll for
132 startup readiness, not one-shot — see U-038 fix), `deploy_cutover.py`, `deploy_monitor.py`, 132 startup readiness, not one-shot — see U-038 fix), `deploy_cutover.py`, `deploy_monitor.py`,
133 `deploy_rollback.py`. Fail-closed, never-auto-restart philosophy adapted (not imported) from 133 `deploy_rollback.py`. Fail-closed, never-auto-restart philosophy adapted (not imported) from
134 - graphloop's `hermes_watchdog.py`. See `docs/supervisor-auto-rollback-design.md` for the full 134 + graphloop's `hermes_watchdog.py`. See `docs/done/supervisor-auto-rollback-design.md` for the full
135 design and the disambiguation note at the top of that file. 135 design and the disambiguation note at the top of that file.
136 136
137 These are unrelated components that happen to share a root word in casual English. Identifiers 137 These are unrelated components that happen to share a root word in casual English. Identifiers
@@ -148,7 +148,7 @@ outside its isolated workspace, a 300s timeout, a stale-PID recovery incident). @@ -148,7 +148,7 @@ outside its isolated workspace, a 300s timeout, a stale-PID recovery incident).
148 worth reading for architecture patterns; its `hermes_watchdog.py`/`phase10_controller.py` are 148 worth reading for architecture patterns; its `hermes_watchdog.py`/`phase10_controller.py` are
149 worth reading for the "fail-closed, deterministic, never-auto-restart" supervision philosophy 149 worth reading for the "fail-closed, deterministic, never-auto-restart" supervision philosophy
150 (graphlemon's Deploy Supervisor deliberately extends this into auto-rollback rather than just 150 (graphlemon's Deploy Supervisor deliberately extends this into auto-rollback rather than just
151 -auto-halt — see `docs/supervisor-design-comparison.md` for why). Do not import graphloop code 151 +auto-halt — see `docs/done/supervisor-design-comparison.md` for why). Do not import graphloop code
152 directly; anything worth reusing has already been copied and adapted into this repo 152 directly; anything worth reusing has already been copied and adapted into this repo
153 (`supervisor/isolation/`, adapted verbatim per the U-036 note in `development-plan.md`). 153 (`supervisor/isolation/`, adapted verbatim per the U-036 note in `development-plan.md`).
154 154
docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md deleted
1 -# Graph Engineering with Claude: 14\-Step roadmap from 0 to graph architect \(Full Course\)  
2 -  
3 -  
4 -  
5 -img\_placeholder  
6 -  
7 -  
8 -  
9 -大多数人尝试构建多步智能体时,最终得到的都是一条直线。第一步、第二步、第三步——每一步都乖乖地等上一步做完才开始。  
10 -  
11 -Most people who try to build a multi\-step agent end up with a straight line\. Step one, step two, step three \- each waiting politely for the last to finish before it starts\.  
12 -  
13 -  
14 -  
15 -十分之九的人会发现,其中一半的步骤根本不需要等待。  
16 -  
17 -**9/10 notice that half those steps never needed to wait at all\.**  
18 -  
19 -  
20 -  
21 -它们不路由,不分支,不并行。它们只是排队——一个脑袋,一个上下文,一次只做一件事——直到窗口被填满,智能体忘记自己在做什么。  
22 -  
23 -They don’t **route\.** They don’t **branch\.** They don’t **parallelize\.** They just queue \- one head, one context, one thing at a time, until the window fills up and the agent forgets what it was doing\.  
24 -  
25 -  
26 -  
27 -> Follow my Substack to get fresh AI alpha:  
28 ->  
29 -> [movez\.substack\.com](https://movez.substack.com/)  
30 ->  
31 ->  
32 ->  
33 ->  
34 -  
35 -这套十四步执行路线图,能把单文件单行式简单任务拆解成完整任务图谱:该图谱可分发至海量智能体集群并行运算、自主校验全部检索结果,最终收敛输出一套单智能体完全无法承载处理规模的完整结论。  
36 -  
37 -***This is the 14\-step roadmap*** that turns that single\-file line into a **graph**: one that fans out across a fleet, verifies its own findings, and converges on a result a lone agent could never hold\.  
38 -  
39 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=Yzk2MWVlZGE5MTQ0ZTQwYjFjNjMxODg5NDgzZTg2OTJfZTVhYTBjOThlMzY3NDBjZmE2YWRmZTEwMTRkZDM2ZmJfSUQ6NzY2NDkxMjQwNTYwNDgxNDAyM18xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
40 -  
41 -  
42 -  
43 -这里有一个没人明说的转变:prompt提示词是一句话,loop循环是一个周期,harness框架是智能体脚下的地板。  
44 -  
45 -Here’s the shift nobody spells out\. A prompt is a sentence\. A loop is a cycle\. A harness is the floor the agent stands on\.  
46 -  
47 -  
48 -  
49 -但工作本身的形状——什么在什么之前运行、什么可以同时运行、什么必须等待其他所有东西——这个形状就是一个图。节点负责思考,边负责传递结果。  
50 -  
51 -But the **shape of the work itself** \- what runs before what, what can run at the same time, what has to wait for everything else \- that shape is a graph\. Nodes do the thinking\. Edges carry the results\.  
52 -  
53 -  
54 -  
55 -Claude Code 已经内置了直接构建这些图的工具:动态工作流。  
56 -  
57 -Claude Code shipped the tooling to build these graphs directly: **dynamic workflows**\.  
58 -  
59 -  
60 -  
61 -Claude 编写一个普通的 JavaScript 编排脚本,然后生成一个协调的子智能体舰队来执行它——而协调本身不消耗任何模型 token,因为它是代码,不是对话。  
62 -  
63 -Claude writes a plain JavaScript orchestration script, then spawns a coordinated fleet of subagents to execute it \- and the coordination itself costs zero model tokens, because it’s code, not a conversation\.  
64 -  
65 -  
66 -  
67 -  
68 -  
69 -## Nodes are jobs\. Edges are what *flows\.*  
70 -  
71 -\#\# 节点代表工作,边代表流转关系  
72 -  
73 -  
74 -  
75 -一个图只有两样东西(节点\+边 Nodes\+Edges),把它们搞清楚就能解决大部分困惑。  
76 -  
77 -**节点**是一个工作单元——一个智能体、一个有界任务、一个输入一个输出。  
78 -  
79 -A graph has exactly two things, and getting them straight fixes most of the confusion\.  
80 -  
81 -A **node** is a unit of work \- one agent, one bounded job, one input in and one output out\.  
82 -  
83 -  
84 -  
85 -**边**是一个依赖关系:含义是这个节点的输出作为另一个节点的输入。仅此而已。  
86 -  
87 -An **edge** is a dependency: it says *this node’s output feeds that node’s input\.* Nothing more\.  
88 -  
89 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=MzY1ODVjYWExNjBmMTY0NGU2ZDkxNGEzNDMzMjU4MmNfZjE2MzI4ZTIyOTgxZDMxOWZiYjEzZGI5NzI3ZTA4ZjdfSUQ6NzY2NDkxMzQ2MDEzMzM2Njc0MV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
90 -  
91 -常见的错误是把「然后」当作边。「总结文件然后告诉我天气」——这两者之间没有边,天气不消费摘要的数据。  
92 -  
93 -The mistake is treating “and then” as an edge\. “Summarize the file *and then* tell me the weather” has no edge between the two \- the weather doesn’t consume the summary\.  
94 -  
95 -  
96 -  
97 -那是两个不相连的节点,被线性脚本不必要地串联在一起。边只存在于数据真正跨越它的时候。  
98 -  
99 -That’s two disconnected nodes that a linear script needlessly chains\. The edge only exists when data actually moves across it\.  
100 -  
101 -  
102 -  
103 -学会对你的智能体中每个「然后」提问:下一步是否读取上一步的输出?如果不是,就没有边,等待是浪费的。  
104 -  
105 -Learn to ask, for every “and then” in your agent: **does the next step read the last step’s output?** If not, there is no edge, and the wait is wasted\.  
106 -  
107 -  
108 -  
109 -python  
110 -  
111 -1\. 用方框和箭头画出来。方框是一个 agent\(\) 调用,箭头是一个变量从一个调用的返回值传到另一个的提示词里。如果你画不出箭头——如果没有变量跨越——两个方框就是独立的,而独立性是你在本课程剩余部分要利用的东西。  
112 -  
113 -```Python  
114 -Draw it as boxes and arrows. A box is an agent() call.  
115 -An arrow is a variable passed from one call’s return into another’s  
116 -prompt. If you can’t draw the arrow - if no variable crosses - the two  
117 -boxes are independent, and independence is the thing you’ll exploit  
118 -for the rest of this course.  
119 -```  
120 -  
121 -  
122 -  
123 -  
124 -  
125 -## Your linear script is a *degenerate* graph  
126 -  
127 -\#\# 你的线性脚本是一个退化的图  
128 -  
129 -  
130 -  
131 -当你把智能体写成「先做A,再做B,再做C,再做D」时,你已经画了一个图——一条不分叉的单链。每个节点恰好有一条边进、一条边出。  
132 -  
133 -When you write an agent as “do A, then B, then C, then D,” you’ve drawn a graph \- a single unbranching chain\. Every node has exactly one edge in and one edge out\.  
134 -  
135 -  
136 -  
137 -它能正确运行,但也运行得又慢又脆,因为链条没有冗余:如果C卡住了,D永远不会执行,A的工作被困在上游无处可去。  
138 -  
139 -It runs correctly\. It also runs slowly and fragile, because a chain has no redundancy: if C stalls, D never happens, and A’s work is trapped upstream with nowhere to go\.  
140 -  
141 -  
142 -  
143 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=MGU2ZWQwYzllODU3MDlhZTViMzM4NWE5ZTE1M2FjMTNfYWY0YzE4YzRkNjdkYzk3ZDExZWFlN2E4OWVkMWY0YzNfSUQ6NzY2NDkwOTcxODAyNDQ0MDc2OF8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
144 -  
145 -图工程的第一项真正技能是重画这条链。拿起你的线性智能体,对每条箭头问第一步的问题。  
146 -  
147 -The first real skill of graph engineering is **redrawing the chain**\. Take your linear agent and, for each arrow, ask the Step 1 question\.  
148 -  
149 -  
150 -  
151 -大多数链条有两三条不携带数据的箭头——它们只是你碰巧输入东西的顺序。  
152 -  
153 -Most chains have two or three arrows that don’t carry data \- they’re just the order you happened to type things in\.  
154 -  
155 -  
156 -  
157 -剪掉这些箭头,链条就坍缩成更宽的东西:几个可以同时运行的独立节点,喂给一个需要它们全部的单一节点。  
158 -  
159 -Cut those arrows and the chain collapses into something wider: a few independent nodes that could all run at once, feeding a single node that needs them all\.  
160 -  
161 -  
162 -  
163 -  
164 -  
165 -## Give every node a *contract*  
166 -  
167 -\#\# 给每个节点一个契约  
168 -  
169 -一个你无法推理的节点就是一个你无法并行的节点。解决方案是契约:有界输入、有界输出、恰好一个任务。  
170 -  
171 -A node you can’t reason about is a node you can’t parallelize\. The fix is a contract: **bounded input, bounded output, exactly one job\.**  
172 -  
173 -  
174 -  
175 -输入是节点读取的任何东西——显式传入,永远不从共享窗口假设。输出是一个定义好的形状,最好经过验证,这样下一个节点可以无猜测地消费它。  
176 -  
177 -The input is whatever the node reads \- passed in explicitly, never assumed from a shared window\. The output is a defined shape, ideally validated, so the next node can consume it without guessing\.  
178 -  
179 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=MjMzYTQwYjViZjUyMTA5YmVhZjBlY2JmZjhiMjE4NmNfYjQ5OWNjZjk0YmUzOTQ4ZDdjNmJmMzZiZTRkYzYwZjBfSUQ6NzY2NDkwOTgwMTkzNTExMzQxOV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
180 -  
181 -在工作流中,这个契约用 schema 强制执行。当你给 Claude 一个带 JSON schema 的 agent\(\) 调用时,Claude 生成的子智能体被迫返回验证过的结构化数据——验证发生在工具调用层,所以 Claude 在不匹配时重试,而不是给你一段需要解析和祈祷的自由文本。  
182 -  
183 -In a workflow this contract is enforced with a **schema**\. When you hand Claude an agent\(\) call with a JSON schema, the subagent Claude spawns is forced to return validated structured data \- validation happens at the tool\-call layer, so Claude retries on mismatch instead of handing you free text you have to parse and pray over\.  
184 -  
185 -  
186 -  
187 -这就是一个 Claude 能接入图的节点和一个只有人类读输出才能工作的节点之间的区别。  
188 -  
189 -This is the difference between a node Claude can wire into a graph and a node that only works when a human reads its output\.  
190 -  
191 -  
192 -  
193 -python  
194 -  
195 -// 真正有契约的节点:有界输入,验证输出,单一任务。  
196 -  
197 -```Python  
198 -// A node with a real contract: bounded in, validated out, one job.  
199 -const ITEM = {  
200 - type: 'object', additionalProperties: false,  
201 - properties: {  
202 - title: { type: 'string' },  
203 - url: { type: 'string' },  
204 - impact: { type: 'string', enum: ['high', 'medium', 'low'] },  
205 - },  
206 - required: ['title', 'url', 'impact'],  
207 -};  
208 -const result = await agent(source.prompt, {  
209 - label: `research:${source.key}`,  
210 - schema: ITEM, // forces validated structured output  
211 - agentType: 'general-purpose',  
212 -});  
213 -// result is now a shape the next node can trust — not free text.  
214 -```  
215 -  
216 -  
217 -  
218 -  
219 -  
220 -## 04\. Treat the edge as a *data contract*  
221 -  
222 -\#\# 04\. 把边当作数据契约  
223 -  
224 -  
225 -  
226 -边不只是简单表示 “任务 A 执行完再跑任务 B”,它更是一份数据传输约定:A 输出固定格式的数据结构,而 B 的设计本身就适配读取该结构。如果以传输的数据(而非执行先后顺序)来命名边,两件事会变得清晰易懂。  
227 -  
228 -An edge isn’t just “B comes after A\.” It’s a promise about what crosses: A produces *this shape*, and B is built to consume *this shape\.* When you name the edge by its data \- not its order \- two things get easier\.  
229 -  
230 -  
231 -  
232 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=MTgwN2M4N2JiYjk0ZDRiNjEzZWE4YWFhYTE0ZDU5YTVfZTk3NmZiMWFiMTgxZmFiYmRhZjdjNjhkZWM1ZTIyYThfSUQ6NzY2NDkxNzIzMzc1NjA2NDcyNF8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
233 -  
234 -你可以一眼看出边是否真实(数据真的在移动吗?),而且只要形状保持不变,你可以替换任一端的节点而不破坏图。  
235 -  
236 -You can see instantly whether the edge is real \(does data actually move?\), and you can swap the node on either end without breaking the graph, as long as the shape holds\.  
237 -  
238 -  
239 -  
240 -实际实现中,边由原生 JavaScript 编写。在任务分发与结果汇总之间的归约步骤(扁平化、去重、过滤),本质就是一段处理各节点输出数据结构的代码。  
241 -  
242 -In practice, the edge lives in plain JavaScript\. The reduce step between fan\-out and synthesis \- flatten, dedupe, filter \- is just code operating on the shapes your nodes returned\.  
243 -  
244 -  
245 -  
246 -全程无需智能体参与。基于图谱架构设计有一项不易察觉的显著优势:大量原本需要消耗大模型算力令牌处理的逻辑,本质只是一条数据流边,而数据流边完全零算力开销。  
247 -  
248 -**No agent needed\.** One of the quiet wins of graph thinking: a huge amount of what people burn model tokens on is really an edge, and edges are free\.  
249 -  
250 -  
251 -  
252 -python  
253 -  
254 -人们很容易下意识新建智能体来 “合并多份结果”,但一定要避免这种做法。 如果合并操作仅需扁平化、去重,直接用 `results.flatMap(...)` 搭配集合(Set)即可;运算结果确定、瞬时完成,完全不消耗模型 Token。 智能体应当留给需要主观判断的场景,而非单纯的数据流转处理。 倘若图谱里每条数据边都依靠智能体中转,相当于为自身的基础数据流链路持续付出不必要的算力成本。  
255 -  
256 -```Python  
257 -The temptation is to spawn an agent to “combine the results.” Resist  
258 -it. If combining means flatten-and-dedupe, that’s results.flatMap(...)  
259 -and a Set — deterministic, instant, zero tokens. Save agents for  
260 -judgment, not for plumbing. A graph where every edge is an agent is a  
261 -graph paying rent on its own wiring.  
262 -```  
263 -  
264 -  
265 -  
266 -  
267 -  
268 -## Fan out with *parallel\(\)*  
269 -  
270 -\#\# 用 parallel\(\) 实现任务并行分发  
271 -  
272 -  
273 -  
274 -这是能大幅压缩整体资源开销的核心设计思路。当存在 N 个相互独立节点(例如 N 份待核查数据源、N 个待评审文件、N 条待审计路由)时,切勿采用串行链式执行方案。  
275 -  
276 -This is the move that pays for everything\. When you have N independent nodes \- N sources to check, N files to review, N routes to audit \- you don’t chain them\.  
277 -  
278 -  
279 -  
280 -你告诉 Claude 把它们fan\-out(任务分解分发)并同时运行。在工作流中这就是 parallel\(\):Claude 接收一个延迟执行函数 thunk 数组,为每个 thunk 单独生成一个子智能体,全部并发执行,然后把结果数组返回给你。  
281 -  
282 -You tell Claude to fan them out and run them at once\. In a workflow that’s parallel\(\): Claude takes an array of thunks and spawns one subagent per thunk, all executing concurrently, then hands you back the array of results\.  
283 -  
284 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=ZDMyYzUzZmUxZjI4MGQ1ZmIyMGJhYmRkMzRlYmMxZTBfZTA3OTE0MWE3MWEyMjdlYjEyOTM2NzQxNDdhMTMwMDhfSUQ6NzY2NDkyMTcxMDI1Mjc4ODkxNl8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
285 -  
286 -两个细节让它稳健。第一,parallel\(\) 是一个屏障——它等所有 thunk 完成才返回,所以下一阶段看到的是完整集合。第二,抛异常的 thunk 解析为 null 而不是拒绝整个批次,所以一个不稳定的智能体不会搞砸整个运行。  
287 -  
288 -Two details make it robust\. First, parallel\(\) is a **barrier** \- it waits for every thunk before it returns, so the next stage sees the complete set\. Second, a thunk that throws resolves to null instead of rejecting the whole batch, so one flaky agent can’t sink the run\.  
289 -  
290 -  
291 -  
292 -总是对结果做 \.filter\(Boolean\)。并发量受限于你的核心数,多余的会排队,所以你可以传一百个 thunk 它们都会完成——只是每次跑一小批。  
293 -  
294 -Always \.filter\(Boolean\) the results\. Concurrency is capped around your core count and the excess queues, so you can pass a hundred thunks and they’ll all finish \- just a handful at a time\.  
295 -  
296 -  
297 -  
298 -python  
299 -  
300 -1\. phase\('Research'\);  
301 -// 九个来源,九个智能体,全部同时。  
302 -const raw = await parallel\(  
303 - SOURCES\.map\(\(s\) =\> \(\) =\>  
304 - agent\(s\.prompt, \{  
305 - label: \`research:$\{s\.key\}\`,  
306 - phase: 'Research',  
307 - schema: ITEM\_SCHEMA, // 每个节点返回验证过的 JSON  
308 - agentType: 'general\-purpose',  
309 - \}\),  
310 - \),  
311 -\);  
312 -const collected = raw\.filter\(Boolean\); // 丢掉失败智能体的 null  
313 -  
314 -```Python  
315 -phase('Research');  
316 -// Nine sources, nine agents, all at once.  
317 -const raw = await parallel(  
318 - SOURCES.map((s) => () =>  
319 - agent(s.prompt, {  
320 - label: `research:${s.key}`,  
321 - phase: 'Research',  
322 - schema: ITEM_SCHEMA, // each node returns validated JSON  
323 - agentType: 'general-purpose',  
324 - }),  
325 - ),  
326 -);  
327 -const collected = raw.filter(Boolean); // drop the nulls from failed agents  
328 -```  
329 -  
330 -fan\-out(分发并行执行)存在于 Claude 写的代码中,不在模型对话中。Claude 自己的上下文从不同时持有九个来源——每个子智能体携带自己的上下文,只有最终答案回来。  
331 -  
332 -The fan\-out lives in code Claude wrote, not in a model conversation\. Claude’s own context never holds nine sources at once \- each subagent carries its own, and only the final answer comes back\.  
333 -  
334 -  
335 -  
336 -这就是让 Claude 把工作流扩展到几十或几百个子智能体而不淹没会话的原因。编排层不消耗 token,因为它不是 Claude 思考的另一轮。  
337 -  
338 -That’s what lets Claude scale a workflow to **dozens or hundreds of subagents** without drowning the session\. The orchestration layer costs zero tokens because it isn’t another turn of Claude thinking\.  
339 -  
340 -  
341 -  
342 -  
343 -  
344 -## Fan in at a *barrier*  
345 -  
346 -\#\# 基于屏障节点完成结果扇入聚合  
347 -  
348 -Fan\-in :结果聚合,汇总归约  
349 -  
350 -  
351 -  
352 -fan\-out只有在有东西收集它时才有用。fin\-in是边汇聚的节点——一个智能体(或一段代码)同时看到所有上游结果,并做需要整个集合才能做的事:跨来源去重、按影响排序、总量为空时提前退出。这是屏障值得其墙钟成本的唯一地方。  
353 -  
354 -A fan\-out is only useful if something gathers it\. The fan\-in is the node where edges converge \- where one agent \(or one piece of code\) sees *all* the upstream results at once and does something that requires the whole set: dedupe across sources, rank by impact, early\-exit if the total came back empty\. This is the one place a barrier earns its wall\-clock cost\.  
355 -  
356 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=YjBmNzYxM2E3ODNhZGVlZWE1ZTIyNzRlOTQwM2JhMWJfNzhlNzg1MDJhMmQ2NDg4YjNjYWY3NTNlNDQxNzhlNDhfSUQ6NzY2NDkyMTcyNDIxNjk3MDQyNV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
357 -  
358 -保障任务图谱高效运行的核心准则:  
359 -  
360 -仅当某一阶段确实需要集齐所有前置任务结果时,才使用屏障同步机制。需要对全部数据源统一去重?那就启用屏障 —— 这么做是合理的。  
361 -  
362 -The rule that keeps graphs fast: **use a barrier only when a stage genuinely needs every prior result together\.** Deduping across all sources? Barrier \- correct\.  
363 -  
364 -  
365 -  
366 -python  
367 -  
368 -1\. // 边:纯 JS,无智能体,零 token。  
369 -const flat = collected\.flatMap\(\(c\) =\> c\.items\);  
370 -log\(\`Collected $\{flat\.length\} items\`\);  
371 -phase\('Curate'\);  
372 -// 屏障节点:需要整个集合来去重\+排序。  
373 -const curated = await agent\(  
374 - \`Dedupe and rank these by impact:\\n$\{JSON\.stringify\(flat\)\}\`,  
375 - \{ phase: 'Curate', schema: CURATED\_SCHEMA \},  
376 -\);  
377 -  
378 -```Python  
379 -// The edge: plain JS, no agent, zero tokens.  
380 -const flat = collected.flatMap((c) => c.items);  
381 -log(`Collected ${flat.length} items`);  
382 -phase('Curate');  
383 -// The barrier node: needs the WHOLE set to dedupe + rank.  
384 -const curated = await agent(  
385 - `Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,  
386 - { phase: 'Curate', schema: CURATED_SCHEMA },  
387 -);  
388 -```  
389 -  
390 -  
391 -  
392 -如果只是单纯对列表做扁平化处理?直接依托数据流边内联处理即可。 有一套直白严苛的判断标准:倘若你的代码写成「并行分发 → 数据转换 → 再次并行分发」,且中间转换步骤不存在多条数据之间的交叉依赖,那本该改用流式管道处理,完全省去屏障同步。  
393 -  
394 -Just flattening a list? That’s an edge, do it inline\. The smell test is brutal and simple: if you wrote parallel → transform → parallel, and that middle transform has no cross\-item dependency, you should have used a pipeline and skipped the barrier entirely\.  
395 -  
396 -  
397 -  
398 -  
399 -  
400 -## The *diamond*: split → work → merge  
401 -  
402 -\#\# 菱形执行模型:拆分任务 → 并行处理 → 汇总合并  
403 -  
404 -  
405 -  
406 -把fan\-out和fan\-in放在一起,你就得到了每个严肃智能体图的主力拓扑:菱形执行模型。  
407 -  
408 -Put fan\-out and fan\-in together and you get the workhorse topology of every serious agent graph: the **diamond**\.  
409 -  
410 -  
411 -  
412 -单个节点负责任务拆分,大量节点并行执行处理,再由单个节点汇总合并。市场信息巡检、依赖项审计、代码评审、深度调研报告等场景底层都采用这套拓扑结构;只需替换数据源与提示词,同一套基础框架就能适配各类业务需求。  
413 -  
414 -  
415 -  
416 -One node splits the job, many nodes do the work in parallel, one node merges\. It’s the shape behind a market scan, a dependency audit, a code review, a research report \- swap the sources and prompts and the same skeleton adapts\.  
417 -  
418 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=M2Q5NWJiOGI4ZGIxMjU4YTkxZDliZWYwNmI3NmM1NzVfYjZhMjllZmY5ZTczNWZhMTI3YTYzNzY3MDQ4ZmQyMGJfSUQ6NzY2NDkxMDIxNzY5NjcwOTU3MF8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
419 -  
420 -这套标准范式有个值得牢记的命名:扇出分发 → 归约处理 → 汇总生成。 扇出阶段拓宽信息覆盖范围;依靠原生代码做归约,精简海量原始数据;最后交由终端智能体整合输出完整结论。  
421 -  
422 -The canonical form has a name worth memorizing: **fan out → reduce → synthesize\.** Fan out to gather breadth, reduce with plain code to compress it, synthesize with a final agent to write the answer\.  
423 -  
424 -  
425 -  
426 -一旦你看到了菱形,你就不再问「如何让我的智能体做更多步骤」,而是开始问「拆分在哪里,合并在哪里」——这才是真正能扩展的问题。  
427 -  
428 -Once you see the diamond, you stop asking “how do I make my agent do more steps” and start asking “where’s the split, where’s the merge” \- which is the question that actually scales\.  
429 -  
430 -  
431 -  
432 -  
433 -  
434 -## Route the edge at runtime with a *conditional*  
435 -  
436 -\#\# 通过条件分支在运行时动态路由数据流边  
437 -  
438 -  
439 -  
440 -并非所有任务图谱都是静态固定的。有时要走哪条数据流边,取决于上游节点的输出结果。路由节点会解析返回数据,判定启用哪一条下游执行分支:比如先对工单分类,再分流至对应的处理程序;或是先判断代码变更体量,再选择快速简易评审,或是启动完整全量审计。  
441 -  
442 -Not every graph is fixed\. Sometimes the edge to take depends on what a node found\. A **router node** inspects a result and decides which downstream path fires \- classify the ticket, then branch to the right handler; check the diff size, then either do a quick review or spin up a full audit\.  
443 -  
444 -  
445 -  
446 -在工作流中这只是对节点验证输出的 JavaScript if 或 switch,因为控制流存在于代码中。  
447 -  
448 -In a workflow this is just a JavaScript if or switch on a node’s validated output, because control flow lives in code\.  
449 -  
450 -  
451 -  
452 -这里确定性成了特性而非限制。路由器的决策可以是 Claude 驱动的(子智能体分类),但路由是 Claude 写的代码——所以对相同分类每次都以相同方式运行。  
453 -  
454 -This is where determinism becomes a feature, not a limitation\. The router’s *decision* can be Claude\-powered \(a subagent classifies\), but the *routing* is code Claude wrote \- so it runs the same way every time for the same classification\.  
455 -  
456 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=ZjBlZjIwZWRhZTc1ZjUxMDU3ZDQ4NDI0YzhiZTY1OGJfZTZkNGEyNTg3NDI4MjAxMjFjNzg0NjFmOWE2ZGQ1MDZfSUQ6NzY2NDkxMDM3NTA3OTE4NTM4NF8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
457 -  
458 -  
459 -  
460 -节点环节依托 Claude 的大模型判断能力,数据流边环节依靠脚本代码稳定可靠。不会出现意外情况 —— 比如莫名其妙触发 “Claude 自行跳过审计流程”,因为跳过流程的逻辑必须显式定义在任务图谱中,而当前图谱并未配置该分支。  
461 -  
462 -You get Claude’s judgment at the node and the script’s reliability at the edge\. No emergent “Claude decided to skip the audit” surprises \- because the skip would have to be written into the graph, and it isn’t\.  
463 -  
464 -  
465 -  
466 -python  
467 -  
468 -1\. // 路由节点:智能体分类,代码选边。  
469 -const \{ severity \} = await agent\(  
470 - \`Classify this diff's risk:\\n$\{diff\}\`,  
471 - \{ schema: \{ type: 'object',  
472 - properties: \{ severity: \{ enum: \['low', 'high'\] \} \},  
473 - required: \['severity'\] \} \},  
474 -\);  
475 -let review;  
476 -if \(severity === 'high'\) \{  
477 - // 重路径:全面并行审计  
478 - review = await parallel\(FILES\.map\(\(f\) =\> \(\) =\> agent\(\`Audit $\{f\}\`\)\)\);  
479 -\} else \{  
480 - // 轻路径:一次快速审查  
481 - review = await agent\(\`Quick review of $\{diff\}\`\);  
482 -\}  
483 -  
484 -```Python  
485 -// Router node: an agent classifies, code picks the edge.  
486 -const { severity } = await agent(  
487 - `Classify this diff's risk:\n${diff}`,  
488 - { schema: { type: 'object',  
489 - properties: { severity: { enum: ['low', 'high'] } },  
490 - required: ['severity'] } },  
491 -);  
492 -let review;  
493 -if (severity === 'high') {  
494 - // heavy path: full parallel audit  
495 - review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));  
496 -} else {  
497 - // light path: one quick pass  
498 - review = await agent(`Quick review of ${diff}`);  
499 -}  
500 -```  
501 -  
502 -  
503 -  
504 -  
505 -  
506 -## Put a *verifier* on the edge  
507 -  
508 -\#\# 在数据流边配置放验证器  
509 -  
510 -  
511 -  
512 -任务图谱真正的核心优势,不在于创建更多智能体,而在于你能搭建一套标准化流程框架包裹智能体,让输出结果具备可信赖度。  
513 -  
514 -The real leverage of a graph isn’t more agents \- it’s the structure you can wrap around them to produce confidence\.  
515 -  
516 -  
517 -  
518 -校验节点部署在数据流边上,数据放行至下游前会先经过它,它的唯一作用就是筛除无效检测结果。数据校验通过才可继续流转;一旦校验失败,该结果就不会参与最终结论的生成。  
519 -  
520 -A verifier node sits on the edge before a result is allowed downstream, and its only job is to try to *kill* the finding\. If it survives, it passes\. If not, it never reaches the answer\.  
521 -  
522 -  
523 -  
524 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=Y2YxMmE4OTE3YTIzZjg0ODRiYWJlOGFhYjU5M2E0OTZfYWNmNDMyYWVlZWIyODU4YTJlYTk0ZTZhMzUxMThiOTdfSUQ6NzY2NDkxMDQ1MzUwNDU2MDA4MF8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
525 -  
526 -有三种模式值得掌握。  
527 -  
528 -Three patterns are worth having in your hands\.  
529 -  
530 -对抗式校验:针对每一条检测结论,启动 N 个独立质疑智能体,指令其逐条反驳该结论;仅当多数质疑校验均未推翻结论时,才保留这条结果。  
531 -  
532 -多视角差异化校验:为每个校验器分配专属评判维度 —— 逻辑正确性、安全风险、问题可复现性;多元视角能捕捉到批量相同校验无法发现的各类失效场景。  
533 -  
534 -评审专家组机制:从不同角度生成 N 套解决方案,并行交由多名评审打分;以最优方案为基底汇总输出,同时吸纳其他备选方案中的优质内容。  
535 -  
536 -- **Adversarial verify:** for each finding, spawn N independent skeptics prompted to refute it; keep it only if a majority survive\.  
537 -  
538 -- **Perspective\-diverse verify:** give each verifier a distinct lens \- correctness, security, does\-it\-reproduce \- because diversity catches failure modes that N identical checks never will\.  
539 -  
540 -- **Judge panel:** generate N attempts from different angles, score them with parallel judges, synthesize from the winner while grafting the best of the runners\-up\.  
541 -  
542 -这正是让一个真实团队移植 Bun 运行时并把对抗性代码审查内建到循环中的模式。  
543 -  
544 -This is exactly the pattern that let a real team port the Bun runtime with adversarial code review baked into the loop\.  
545 -  
546 -  
547 -  
548 -  
549 -  
550 -## Isolate nodes so one failure can’t *poison* the graph  
551 -  
552 -\#\# 节点隔离,避免单点故障污染整个任务图谱  
553 -  
554 -###  
555 -  
556 -串行链路中故障会连锁扩散:若节点 C 执行失败,节点 D 将无法启动,整条流程直接中断。而在任务图谱架构里,故障应当被限制在发生问题的单个节点内。  
557 -  
558 -In a chain, a failure cascades \- C dies, D never runs, the whole thing halts\. In a graph, failure should be **contained to its node\.**  
559 -  
560 -  
561 -  
562 -这一点其实已经部分实现:在 `parallel()` 内部,若某个延迟执行函数抛出异常,其结果会解析为 `null`;即便有一个子代理执行失败,其余八个正常的子代理仍能正常返回数据。调用 `.filter(Boolean)` 过滤空值,就是实现故障隔离的手段。  
563 -  
564 -That’s already partly true: a thunk that throws inside parallel\(\) resolves to null, so eight good agents still return while one bad one drops out\. Your \.filter\(Boolean\) is the containment\.  
565 -  
566 -  
567 -  
568 -设计每一处扇入聚合逻辑时,都要兼容缺失输入数据,不要默认所有上游结果都齐全。  
569 -  
570 -Design every fan\-in to tolerate missing inputs rather than assume a full set\.  
571 -  
572 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=OTNmZmEwNDUwMGI5N2UwN2RhZGZmMDk0NjA1MDUwZDlfMjYyMjY0N2EyMzNkOGI4ZTcwNjY5YTQ2OTIwMDUxYmRfSUQ6NzY2NDkxMDU0MjgwODg3ODM0OV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
573 -  
574 -更微妙的失败是节点互相踩踏。当智能体并行写文件时,它们可能冲突。  
575 -  
576 -The subtler failure is nodes stepping on each other\. When agents write files in parallel, they can collide\.  
577 -  
578 -  
579 -  
580 -解决方案是隔离:worktree——每个智能体在自己的 git worktree 中运行,在沙箱中做工作,然后干净地合并。  
581 -  
582 -The fix is isolation: "worktree" \- each agent runs in its own git worktree, does its work in a sandbox, and merges cleanly\.  
583 -  
584 -  
585 -  
586 -仅当多个节点确实并行执行时,才启用屏障同步机制。屏障是专为并行拓扑配套的安全防护措施,并非所有流程运行都必须承担的固定性能开销。  
587 -  
588 -Reach for it only when nodes actually write in parallel\. it’s the seatbelt for the one topology that needs it, not a default tax on every run\.  
589 -  
590 -  
591 -  
592 -  
593 -  
594 -## Add a cycle \- but make it *converge*  
595 -  
596 -\#\# 加一个循环——但要让它收敛  
597 -  
598 -  
599 -  
600 -有时你直到开始才知道工作有多大:未知规模的探索、发现一个bug会暴露三个更多的bug扫描。这需要循环——一条回到更早节点的受控边。  
601 -  
602 -Sometimes you don’t know how big the job is until you’re in it: unknown\-size discovery, a bug sweep where finding one bug reveals three more\. That needs a **cycle** \- a controlled edge back to an earlier node\.  
603 -  
604 -  
605 -  
606 -危险很明显:不收敛的循环是无限循环,会生成智能体直到你的预算耗尽。  
607 -  
608 -The danger is obvious: a cycle that doesn’t converge is an infinite loop that spawns agents until your budget is gone\.  
609 -  
610 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=NjJlOTJhZmMwNjdkMzY1ZDgzNzNlNWE4MTI1MjkwOGVfOTRmNmQ5MGQyY2Q3ZmFjNjQwMzY1NGRjYjQ5ZDNiMjVfSUQ6NzY2NDkyMTkyNzk1Mzg0NTUxNV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
611 -  
612 -这套收敛模式叫做「循环直至无新增结果」:持续启动检索探查节点,连续 K 轮都不再产出新内容时,才终止循环。决定该模式成败的关键细节 —— 也是绝大多数人初次实现都会踩的坑 —— 是去重比对所依据的基准对象  
613 -  
614 -The pattern that converges is **loop\-until\-dry:** keep spawning finders until K consecutive rounds surface nothing new, then stop\. The one detail that makes or breaks it \- and the mistake almost everyone makes the first time \- is what you dedupe against\.  
615 -  
616 -  
617 -  
618 -去重校验要和所有已检索到的条目做比对,不能只拿已确认通过的结果去重。否则被校验驳回的无效结论会每一轮重复出现,循环永远无法收敛;最终你的系统会持续消耗算力,反复挖掘完全相同的无效死胡同。  
619 -  
620 -Dedupe against everything *seen*, not just against confirmed results\. Otherwise rejected findings reappear every round, the loop never runs dry, and you’ve built a machine that pays to rediscover the same dead ends forever\.  
621 -  
622 -  
623 -  
624 -```Plain Text  
625 -const seen = new Set(); const confirmed = []; let dry = 0;  
626 -  
627 -while (dry < 2) { // stop after 2 empty rounds  
628 - const found = (await parallel(  
629 - FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))  
630 - )).filter(Boolean).flatMap((r) => r.bugs);  
631 -  
632 - const fresh = found.filter((b) => !seen.has(key(b)));  
633 - if (!fresh.length) { dry++; continue; } // nothing new → toward dry  
634 - dry = 0;  
635 - fresh.forEach((b) => seen.add(key(b))); // dedupe vs SEEN, not confirmed  
636 -  
637 - // diverse-lens verify each fresh finding before it counts  
638 - const judged = await parallel(fresh.map((b) => () =>  
639 - parallel(['correctness', 'security', 'repro'].map((lens) => () =>  
640 - agent(`Judge "${b.desc}" via ${lens} — real?`, { schema: VERDICT })))  
641 - .then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));  
642 -  
643 - confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));  
644 -}  
645 -```  
646 -  
647 -  
648 -  
649 -  
650 -  
651 -## Tier the models across the *nodes*  
652 -  
653 -\#\# 按节点职能分层选用梯度规格的大模型  
654 -  
655 -##  
656 -  
657 -不是每个节点都需要你最好的模型。图让这变得明显,而单个智能体永远不会:有些节点是有界且重复的(提取这个字段、分类这个工单),有些承载真正的判断(综合报告、裁决发现)。  
658 -  
659 -Not every node needs your best model\. A graph makes this obvious in a way a single agent never does: some nodes are bounded and repetitive \(extract this field, classify this ticket\), and some carry the real judgment \(synthesize the report, adjudicate the finding\)\.  
660 -  
661 -  
662 -  
663 -无聊的节点用更便宜的模型跑,把你的昂贵 token 花在判断真正存在的地方。  
664 -  
665 -Run the boring nodes on a cheaper model and spend your expensive tokens where judgment actually lives\.  
666 -  
667 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=OGYxOGJkNDBhZjExYmJhMTA0NGE5ZTZhM2I5MDRkYWFfYTk0M2Y2OGMwZDI2YWZkMDhmYTcwZmUzZTE0MmU1MDVfSUQ6NzY2NDkxMDY2NTA2ODgwOTQ5OF8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
668 -  
669 -在工作流中,Claude 生成的每个子智能体默认继承你的会话模型,除非脚本覆盖它——所以默认情况下一次大运行全部按你的会话层级计费。单个 agent\(\) 调用上的 model 选项告诉 Claude 只把那个节点路由到别处。  
670 -  
671 -In a workflow every subagent Claude spawns inherits your session model unless the script overrides it \- so by default a big run bills entirely at your session tier\. The model option on a single agent\(\) call tells Claude to route just that node elsewhere\.  
672 -  
673 -  
674 -  
675 -大运行前检查 /model,然后让 Claude 把扇出的重复节点路由到更便宜的模型,把合并节点留在高端。这就是把一个吃 token 的图从昂贵变成经济的杠杆,不需要改变它的形状。  
676 -  
677 -**Check /model before a large run,** then have Claude route the fan\-out’s repetitive nodes down to a cheaper model and keep the merge node up\. This is the lever that turns a token\-hungry graph from expensive into economical without touching its shape\.  
678 -  
679 -  
680 -  
681 -  
682 -  
683 -## Topology *is* your cost and latency  
684 -  
685 -\#\# 拓扑就是你的成本和延迟  
686 -  
687 -  
688 -  
689 -任务图谱的拓扑结构绝非锦上添花的表面设计,而是直接影响实际运行耗时的核心关键变量。绝大多数人都会踩坑的一个选择:`parallel()`(并行)与 `pipeline()`(流式管道)。`parallel()` 依赖屏障机制,所有分支必须等待最慢的节点执行完毕,下一阶段才能启动。  
690 -  
691 -The shape of the graph isn’t cosmetic \- it’s the single biggest lever on wall\-clock time\. The choice that trips everyone up: parallel\(\) versus pipeline\(\)\. A parallel\(\) barrier makes *everything* wait for the slowest node before the next stage starts\.  
692 -  
693 -  
694 -  
695 -`pipeline()` 以流式方式让每条数据独立走完全流程,全程无屏障阻塞:条目 A 处理到第三阶段时,条目 B 可能还停留在第一阶段。处理速度快的数据会提前完成,不会卡在慢速任务后方空等。  
696 -  
697 -A pipeline\(\) streams each item through all stages independently, with no barrier \- item A can be in stage 3 while item B is still in stage 1\. Fast items finish early instead of idling behind slow ones\.  
698 -  
699 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=YWFjODU1YjQyZTkyM2FkZGFlYTMyMDgxYmRmOWU3ZjhfNTU2MjFmMzljNTYyY2RlYjM1YjA1NGEwN2Q1ODc5YzdfSUQ6NzY2NDkxMDc1MTU4NDczNDQyNl8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
700 -  
701 -  
702 -  
703 -默认采用流式管道 `pipeline()`;仅当某一阶段确实需要一次性拿到上游全部结果时,才使用屏障机制 —— 例如全量数据去重、基于全部统计结果提前终止流程、需要对比多条检索结论的综合提示词场景。 “代码写起来更整洁”“各阶段逻辑划分清晰” 都不足以作为启用屏障的理由;屏障带来的延迟是真实、可量化、纯粹浪费的耗时。逻辑分层清晰,不等于必须同步阻塞等待所有任务。  
704 -  
705 -**Default to pipeline\(\)\.** Reach for a barrier only when a stage truly needs every prior result at once \- a cross\-set dedupe, an early\-exit on the total, a prompt that compares against “the other findings\.” “It’s cleaner code” and “the stages feel separate” are not reasons; barrier latency is real, measurable, wasted time\. Separate is not the same as synchronized\.  
706 -  
707 -  
708 -  
709 -  
710 -  
711 -## Let Claude draw the graph \- *self\-routing*  
712 -  
713 -\#\# 交由 Claude 自主生成任务图谱:自路由机制  
714 -  
715 -  
716 -  
717 -最后一种方案:对于无法预先规划流程的任务,不再手动编写固化的任务图谱。  
718 -  
719 -The final move is to stop drawing the graph by hand for jobs you can’t plan in advance\.  
720 -  
721 -  
722 -  
723 -借助动态工作流,你只需描述最终目标,Claude 会自行生成任务编排脚本:拆解整体任务、选择并行分发策略、调度一组协同工作的子智能体,最后整合输出结论。系统会生成适配本次任务专属的流程拓扑,而非一套寄希望于能适配所有场景的固化图谱。  
724 -  
725 -With **dynamic workflows**, you describe the objective and Claude writes the orchestration script itself\- decomposing the task, choosing the fan\-out, spawning a coordinated fleet of subagents, and synthesizing the result\. You get a graph tailored to *this* run instead of a fixed one you hoped would fit\.  
726 -  
727 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=N2MzMWVjNDVkMmUwZDZmZmYzYzZkZTYxN2NlNDA0MTRfYTVmODRmZDk5Nzk4NzVhZjljMWMxNWVhMjQxZDBlYmNfSUQ6NzY2NDkxMDgxNjU4OTk5MDg4NV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
728 -  
729 -一共有三种启用动态工作流的方式:  
730 -  
731 -第一种,在提示词中直接写明 “workflow(工作流)”,Claude 会为当前任务自动生成一套流程;  
732 -  
733 -第二种,调用已保存或打包好的预置工作流 ——`/deep-research` 就是一套已投产上线的标准任务图谱,完整链路为:范围界定 → 并行检索 → 内容拉取 → 对抗式校验 → 结果综合,和本教程讲解的标准框架完全一致。  
734 -  
735 -There are three ways in\. Say the word **“workflow”** in your prompt and Claude writes one for the task\. Run a saved or bundled one \- /deep\-research is a real graph shipping in production: scope → parallel search → fetch → adversarial verify → synthesize, the exact skeleton from this course\.  
736 -  
737 -  
738 -  
739 -第三种方式:开启极速代码模式(ultracode),会话内每一项有一定复杂度的任务,Claude 都会自动规划专属工作流。 某次执行效果理想时,按下快捷键 `s` 即可将编排脚本保存至 `.claude/workflows/` 目录;脚本自带版本管理,可通过名称重复调用,任何克隆该代码仓库的人都能直接启动这套任务图谱。  
740 -  
741 -Or turn on ultracode and Claude plans a workflow for every substantial task in the session\. When a run is good, press s to save its script into \.claude/workflows/ \- version\-controlled, re\-runnable by name, a graph anyone who clones the repo can launch\.  
742 -  
743 -  
744 -  
745 -python  
746 -  
747 -执行一套工作流,对 `src/routes/` 下所有接口路由开展鉴权缺失审计:为每一个路由文件单独创建一个子智能体,每条检测结果都先经过校验,再输出审计报告。 ● Claude 已生成任务编排脚本・后台启动执行中… /workflows — auth\-audit(鉴权审计工作流)・运行中 ✓ 范围界定 1/1,消耗 2100 Token,耗时 4 秒 ✓ 并行扇出 18/18,18 个路由文件各分配独立子智能体 ◯ 校验环节 11/18,每条检测结论配备 3 票制对抗质疑校验器…… ○ 汇总合成 0/1,等待校验阶段完成 会话全程保持可交互状态,子智能体集群后台持续运算不阻塞界面  
748 -  
749 -```Python  
750 -› Run a workflow to audit every route under src/routes/ for missing  
751 -auth. Spawn one agent per route file, then verify each finding before  
752 -reporting. ● Claude wrote an orchestration script · launching in  
753 -background… /workflows — auth-audit · running ✓ Scope 1/1 2.1k tok ·  
754 -4s ✓ Fan-out 18/18 one agent per route file ◯ Verify 11/18 3-vote  
755 -skeptics per finding… ○ Synthesize 0/1 waiting on verify session stays  
756 -responsive — keep working while the fleet runs  
757 -```  
758 -  
759 -  
760 -  
761 -\#\# 本周用 Claude 构建的六个图  
762 -  
763 -## Six graphs to build with *Claude* this week  
764 -  
765 -![Image](https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/authcode/?code=ODcyMWUwMDMxYmU4Yjc2ZDgyYzIxODllNjkwZTU2NzlfMmViMzY3NzM4MzBmZWMxMDU1MzYzMjA4Yzc3NjlkZjNfSUQ6NzY2NDkxMDkwODkxNzI3MTUwNV8xNzg1MzI4NTM1OjE3ODU0MTQ5MzVfVjM)  
766 -  
767 -- **Security sweep across every route\. **Claude spawns **one subagent per route file**, each hunting for missing auth checks, then a verifier pass confirms every finding before it reaches the report\. Breadth no single context could hold\.  
768 -  
769 -全链路安全扫描  
770 -  
771 -对每一条业务路径执行安全巡检。Claude 会为每个路由文件单独生成一个子智能体,分别检索缺失的权限校验逻辑;所有风险点需经过校验器二次核验,确认无误后才会录入报告。覆盖广度远超单一上下文所能承载的分析范围。  
772 -  
773 -  
774 -  
775 -- **Cited report with /deep\-research\.** A graph that ships in Claude Code already\. Claude decomposes your question into distinct angles, runs parallel searches, dedupes sources, then **adversarially verifies every claim** with three\-vote skeptics before writing\.  
776 -  
777 -基于 /deep\-research 指令生成引证调研报告  
778 -  
779 -该功能图谱已内置在 Claude Code 中。Claude 将你的查询拆解为多个独立分析维度,并行执行多源检索、去重重复资料;随后启用三方复核机制对每一条结论交叉质证,全部核验通过后再撰写最终报告。  
780 -  
781 -  
782 -  
783 -- **Port a module, file by file\.** The Bun ceiling, scaled to your repo\. Claude fans out translation across files, runs the test suite as a gate on each, and loops the failures back \- **adversarial review** catching what a single pass would ship broken\.  
784 -  
785 -逐文件迁移代码模块  
786 -  
787 -突破 Bun 运行时性能上限,适配你的完整代码仓库。Claude 并行批量转换多个文件,每完成一份文件就执行全套测试用例作为准入关卡;编译 / 测试失败的文件会回流重处理。对抗式复核机制能揪出单次粗转极易遗留的代码缺陷。  
788 -  
789 -  
790 -  
791 -- **Adversarial review of a diff\.** Claude routes on diff size: a small change gets one quick pass, a large one triggers a **full parallel audit** with reviewers on distinct lenses \- correctness, security, performance \- then a judge panel synthesizes\.  
792 -  
793 -代码变更差分对抗评审  
794 -  
795 -Claude 根据代码改动规模分流评审策略:小幅变更执行一轮快速核查;大规模改动将启动全量并行审计,多评审智能体分别从逻辑正确性、安全漏洞、运行性能三大维度独立审查,最后由合议模块整合各方结论输出综合意见。  
796 -  
797 -  
798 -  
799 -- Ecosystem scan on a schedule\. Save it once, re\-run it forever\. Claude checks many sources in parallel \- eleases, blogs, discussion \- ranks by impact at a barrier, and writes the digest\. **Version\-controlled in \.claude/workflows/**, launchable by name\.  
800 -  
801 -定时生态全域巡检  
802 -  
803 -配置一次,永久复用。Claude 并行拉取多渠道信息:版本发布公告、技术博客、社区讨论帖;按业务影响权重过滤低价值内容,自动生成精简摘要。工作流配置存入版本库 `.claude/workflows/` 目录,支持通过名称一键启动。  
804 -  
805 -  
806 -  
807 -- **Discovery of unknown size\.** You don’t know how many bugs are there\. Claude runs finders in parallel, dedupes each new find **against everything seen**, verifies survivors, and keeps looping until two rounds turn up nothing new \- then stops\.  
808 -  
809 -未知范围全域漏洞探查  
810 -  
811 -适用于漏洞总量未知的场景。Claude 并行启动多套漏洞检索器,新发现问题自动与已有结果去重,留存条目全部完成核验;持续循环检索,连续两轮无新增问题后自动终止扫描。  
812 -  
813 -  
814 -  
815 -  
816 -  
817 -## Conclusion:  
818 -  
819 -\#\# 结语:  
820 -  
821 -  
822 -  
823 -提示者问问题。架构师画图。  
824 -  
825 -A prompter asks a question\. An architect draws a *graph\.*  
826 -  
827 -  
828 -  
829 -线性智能体从来不是天花板——它只是第一个形状,每个人都会伸手去拿的那个,因为它匹配我们打字的方式。一行,一个脑袋,一次一件事。  
830 -  
831 -The linear agent was never the ceiling \- it was just the first shape, the one everyone reaches for because it matches how we type\. **One line, one head, one thing at a time\.**  
832 -  
833 -  
834 -  
835 -一旦你能看到节点和边,你就不再要求智能体做更多,而是要求图做得更宽:在独立工作处扇出,在信心要紧处给边加门,在判断不需要处分层模型。  
836 -  
837 -Once you can see the nodes and the edges, you stop asking the agent to do more and start asking the graph to do it wider: fan out where the work is independent, gate the edges where confidence matters, tier the models where judgment doesn’t\.  
838 -  
839 -  
840 -  
841 -大多数人会继续排队走直线。学会画图的人将运行一个舰队——而永远不会注意到其他人卡在的天花板。  
842 -  
843 -Most people will keep queueing steps in a line\. **The ones who learn to draw the graph will run a fleet** \- and never notice the ceiling the rest are stuck under\.  
844 -  
docs/development-plan.md
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 | PH2 | 质量闭环:回路、Skill 迭代、护栏、断点恢复 | Risk Gate | 架构文档 §11 验收标准第 4-5 条;REQ-04/05/06/08/10/14 | 27 | PH2 | 质量闭环:回路、Skill 迭代、护栏、断点恢复 | Risk Gate | 架构文档 §11 验收标准第 4-5 条;REQ-04/05/06/08/10/14 |
28 | PH3 | 人机交互控制台 | Value Path | REQ-11/12/13 | 28 | PH3 | 人机交互控制台 | Value Path | REQ-11/12/13 |
29 | PH4 | 全量验收与固化 | Risk Gate | 06 节全部 14 条 REQ 完整回归通过一次 | 29 | PH4 | 全量验收与固化 | Risk Gate | 06 节全部 14 条 REQ 完整回归通过一次 |
30 -| PH5(2026-07-30 新增) | 自举能力:Deploy Supervisor + 蓝绿部署自动回滚 | Risk Gate | `docs/supervisor-auto-rollback-design.md` 状态机全部路径(health_check_failed / rolled_back / rollback_failed / finalized)至少各复现一次;`self-development-feasibility.md` 9 步走查场景真实跑通一次 | 30 +| PH5(2026-07-30 新增) | 自举能力:Deploy Supervisor + 蓝绿部署自动回滚 | Risk Gate | `docs/done/supervisor-auto-rollback-design.md` 状态机全部路径(health_check_failed / rolled_back / rollback_failed / finalized)至少各复现一次;`self-development-feasibility.md` 9 步走查场景真实跑通一次 |
31 31
32 依赖顺序:PH0 → PH1 → PH2,PH3 可在 PH1 完成后与 PH2 并行(控制台是对已产生数据的可视化层,不阻塞回路/护栏逻辑本身的开发),PH4 收口需要 PH1/PH2/PH3 全部完成。**PH5 依赖 PH4 收口**(自我修改的安全网必须建立在"系统本身已通过全量验收、行为可信"的前提上,对已知不稳定的系统做自动回滚没有意义),且 PH5 不阻塞、不改变 PH0-PH4 的开发方式——PH0-PH4 期间 graphlemon 自身仍是由人类 + Claude Code 交互式开发(这是任何自举系统的必然前提,见 `self-development-feasibility.md` §1),PH5 只在"PH0-PH4 都跑通之后,graphlemon 开始被允许对自己的代码做自主修改"这个新增能力上生效。 32 依赖顺序:PH0 → PH1 → PH2,PH3 可在 PH1 完成后与 PH2 并行(控制台是对已产生数据的可视化层,不阻塞回路/护栏逻辑本身的开发),PH4 收口需要 PH1/PH2/PH3 全部完成。**PH5 依赖 PH4 收口**(自我修改的安全网必须建立在"系统本身已通过全量验收、行为可信"的前提上,对已知不稳定的系统做自动回滚没有意义),且 PH5 不阻塞、不改变 PH0-PH4 的开发方式——PH0-PH4 期间 graphlemon 自身仍是由人类 + Claude Code 交互式开发(这是任何自举系统的必然前提,见 `self-development-feasibility.md` §1),PH5 只在"PH0-PH4 都跑通之后,graphlemon 开始被允许对自己的代码做自主修改"这个新增能力上生效。
33 33
@@ -333,8 +333,8 @@ PH2-S1/S2/S3/S4 之间除标注的依赖外相互独立,可并行推进。 @@ -333,8 +333,8 @@ PH2-S1/S2/S3/S4 之间除标注的依赖外相互独立,可并行推进。
333 **命名说明**:本 Phase 的"Deploy Supervisor"和架构文档里"Supervisor(调度子 Agent,Coder Work Graph 内部监控节点)"是两个不同概念,详见 `supervisor-auto-rollback-design.md` 开头的命名说明,不要混用。 333 **命名说明**:本 Phase 的"Deploy Supervisor"和架构文档里"Supervisor(调度子 Agent,Coder Work Graph 内部监控节点)"是两个不同概念,详见 `supervisor-auto-rollback-design.md` 开头的命名说明,不要混用。
334 334
335 **Goal**:graphlemon 具备安全地自主修改自己代码(含 Org Agent 身份文件、调度器/执行器核心逻辑)的能力——出问题时自动回滚,不需要人工评审代码内容把关。 335 **Goal**:graphlemon 具备安全地自主修改自己代码(含 Org Agent 身份文件、调度器/执行器核心逻辑)的能力——出问题时自动回滚,不需要人工评审代码内容把关。
336 -**Phase 验收**:`docs/supervisor-auto-rollback-design.md` 状态机的四个终态(`health_check_failed`、`rolled_back`、`rollback_failed → blocked_pending_human`、`finalized`)都能真实触发一次;`self-development-feasibility.md` 第 3 节的 9 步走查场景改成真实执行(不是模拟),从"人类发现 bug"到"Deploy Supervisor 自动 finalize"全程无需人工介入代码评审。  
337 -**设计依据**:`docs/supervisor-design-comparison.md`(方案选型)+ `docs/supervisor-auto-rollback-design.md`(状态机/数据模型/健康检查/回滚流程/MVP 参数)。 336 +**Phase 验收**:`docs/done/supervisor-auto-rollback-design.md` 状态机的四个终态(`health_check_failed`、`rolled_back`、`rollback_failed → blocked_pending_human`、`finalized`)都能真实触发一次;`self-development-feasibility.md` 第 3 节的 9 步走查场景改成真实执行(不是模拟),从"人类发现 bug"到"Deploy Supervisor 自动 finalize"全程无需人工介入代码评审。
  337 +**设计依据**:`docs/done/supervisor-design-comparison.md`(方案选型)+ `docs/done/supervisor-auto-rollback-design.md`(状态机/数据模型/健康检查/回滚流程/MVP 参数)。
338 338
339 ### PH5-S1 数据模型与治理规则 339 ### PH5-S1 数据模型与治理规则
340 340
docs/build-report.md renamed to docs/done/build-report.md
docs/self-development-feasibility.md renamed to docs/done/self-development-feasibility.md
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 **扯捶**: 蝝X批撌脩&霈斤蝑 `sdd-mvcp-contract.json`摰熙嚗閬粥銝甈⊥迤撘 AskUserQuestion 蝑‵餈 05-摰蝥行.md contract.json 4 **扯捶**: 蝝X批撌脩&霈斤蝑 `sdd-mvcp-contract.json`摰熙嚗閬粥銝甈⊥迤撘 AskUserQuestion 蝑‵餈 05-摰蝥行.md contract.json
5 **蝏捏秩**嚗銝之歇蝏憭葵 Org Agent ★ `.claude/agents/*.md`c_project_create` 撖 `target_folder_path` 瘝⊥撟喳頨怨楝敺it worktree 氖撌脩摰蝑葵撩隡悟"撌望撌"隞"頝""頝敺"撩嚗銝銝芸雿韏唬瘥甇交" 撌脣憭"餈" 蝻箏" 5 **蝏捏秩**嚗銝之歇蝏憭葵 Org Agent ★ `.claude/agents/*.md`c_project_create` 撖 `target_folder_path` 瘝⊥撟喳頨怨楝敺it worktree 氖撌脩摰蝑葵撩隡悟"撌望撌"隞"頝""頝敺"撩嚗銝銝芸雿韏唬瘥甇交" 撌脣憭"餈" 蝻箏"
6 6
7 -**2026-07-30 霈啣**嚗撠曹葵蝻箏蝏批蝑1. 蝖株恕隞嗅撩 worktree嚗2. 蝖株恕閬蝡 supervisor 餈 Hermes CLI嚗3. **&"敹辣撘箏鈭箏極霂恣"**嚗銝"supervisor 嚗閬犖撌亥恣"嚗甇文 `docs/supervisor-design-comparison.md`嚗獢 Hermes CLI supervisor 蝑頝荔銝箏 graphloop 撌脤氖/manifest/apply 摨 `supervisor/isolation/`嚗 `docs/supervisor-auto-rollback-design.md`嚗撩 2 遛摰 + 蝻箏 3 摰霈曇恣嚗蝻箏 2/3 膩靽蛹憸扇敶"獢"畾菔撌脫銝箸舅隞賣﹝ 7 +**2026-07-30 霈啣**嚗撠曹葵蝻箏蝏批蝑1. 蝖株恕隞嗅撩 worktree嚗2. 蝖株恕閬蝡 supervisor 餈 Hermes CLI嚗3. **&"敹辣撘箏鈭箏極霂恣"**嚗銝"supervisor 嚗閬犖撌亥恣"嚗甇文 `docs/done/supervisor-design-comparison.md`嚗獢 Hermes CLI supervisor 蝑頝荔銝箏 graphloop 撌脤氖/manifest/apply 摨 `supervisor/isolation/`嚗 `docs/done/supervisor-auto-rollback-design.md`嚗撩 2 遛摰 + 蝻箏 3 摰霈曇恣嚗蝻箏 2/3 膩靽蛹憸扇敶"獢"畾菔撌脫銝箸舅隞賣﹝
8 8
9 ## 1. 銝曆隞撘憪 9 ## 1. 銝曆隞撘憪
10 10
@@ -26,7 +26,7 @@ Validator worktree PASS 銋嚗it_worktree_ref 銵 @@ -26,7 +26,7 @@ Validator worktree PASS 銋嚗it_worktree_ref 銵
26 26
27 **獢遛摰嚗**嚗銝餉"撌"嚗霈 Validator 撖寧 worktree 絲銝**蝡洵鈭遢 graphlemon 摰**嚗蝡臬銝隞賭葩/霂餅摨銝銝** Graph Engine 銋**蝡 **Deploy Supervisor**嚗﹝ Coder Work Graph "Supervisor嚗漲摮 Agent嚗"銝支葵銝艙嚗釣銵嚗摰 merge 銝餃 韏瑟摰"餈葉撌"瘞貉閬撌晞 27 **獢遛摰嚗**嚗銝餉"撌"嚗霈 Validator 撖寧 worktree 絲銝**蝡洵鈭遢 graphlemon 摰**嚗蝡臬銝隞賭葩/霂餅摨銝銝** Graph Engine 銋**蝡 **Deploy Supervisor**嚗﹝ Coder Work Graph "Supervisor嚗漲摮 Agent嚗"銝支葵銝艙嚗釣銵嚗摰 merge 銝餃 韏瑟摰"餈葉撌"瘞貉閬撌晞
28 28
29 -**嚗2026-07-30 撌脣祕蝏挽霈∪僎‵甇 SDD嚗** 摰摨瑟靽∪utover/VP 摰閫 `docs/supervisor-auto-rollback-design.md`嚗歇郊餈 01-M/03-C/04-P/05/06/contract.json嚗憓 REQ-15嚗獢蛹隞銋 Hermes CLI 敶 Deploy Supervisor 蝑之憭 graphloop 撌脤氖/manifest/apply 摨 `docs/supervisor-design-comparison.md` 29 +**嚗2026-07-30 撌脣祕蝏挽霈∪僎‵甇 SDD嚗** 摰摨瑟靽∪utover/VP 摰閫 `docs/done/supervisor-auto-rollback-design.md`嚗歇郊餈 01-M/03-C/04-P/05/06/contract.json嚗憓 REQ-15嚗獢蛹隞銋 Hermes CLI 敶 Deploy Supervisor 蝑之憭 graphloop 撌脤氖/manifest/apply 摨 `docs/done/supervisor-design-comparison.md`
30 30
31 ### 蝻箏 3嚗敹誨/頨思遢辣耨瘝⊥笆摨撩 3.5 `org_graph_definition` 撩鈭箏極霂恣 31 ### 蝻箏 3嚗敹誨/頨思遢辣耨瘝⊥笆摨撩 3.5 `org_graph_definition` 撩鈭箏極霂恣
32 32
@@ -39,7 +39,7 @@ Validator worktree PASS 銋嚗it_worktree_ref 銵 @@ -39,7 +39,7 @@ Validator worktree PASS 銋嚗it_worktree_ref 銵
39 39
40 **甇2026-07-30嚗**嚗" validator 瘝⊥犖撌亥恣憸 supervisor 憭隞交敹閬銝芯 agent 雿蛹銵亙 supervisor " 嚗敹辣敹◆犖撌亥恣捂蝟餌銝餅漱嚗蝻箏 2 銝 Supervisor 蝵脣賒銵蛹靽∪嚗 Validator approve 氖蝥選璉瘚撘虜閬犖"銋" 40 **甇2026-07-30嚗**嚗" validator 瘝⊥犖撌亥恣憸 supervisor 憭隞交敹閬銝芯 agent 雿蛹銵亙 supervisor " 嚗敹辣敹◆犖撌亥恣捂蝟餌銝餅漱嚗蝻箏 2 銝 Supervisor 蝵脣賒銵蛹靽∪嚗 Validator approve 氖蝥選璉瘚撘虜閬犖"銋"
41 41
42 -**嚗2026-07-30 撌脣祕蝏挽霈∴** `docs/supervisor-auto-rollback-design.md` 蝚 6 `validator_approval_rate` 靽∪撖孵" validator"餈葵嚗 `execution_event`/`work_node_execution` 銵冽蝒蝏恣 Validator 摰 PASS 蝵脣蝥踹滲蝞瘥氖澆閫血砲﹝蝚 7.2 蝔閬犖撌亥恣隞捆銝鈭箏極隞"頨怠仃韐"嚗銝芸歇憟賜韏瑚嚗掩蝖霈暹嚗砲﹝蝚 7.3 "隞靚笆靚" 42 +**嚗2026-07-30 撌脣祕蝏挽霈∴** `docs/done/supervisor-auto-rollback-design.md` 蝚 6 `validator_approval_rate` 靽∪撖孵" validator"餈葵嚗 `execution_event`/`work_node_execution` 銵冽蝒蝏恣 Validator 摰 PASS 蝵脣蝥踹滲蝞瘥氖澆閫血砲﹝蝚 7.2 蝔閬犖撌亥恣隞捆銝鈭箏極隞"頨怠仃韐"嚗銝芸歇憟賜韏瑚嚗掩蝖霈暹嚗砲﹝蝚 7.3 "隞靚笆靚"
43 43
44 `bug_record.root_cause_category` 憓 `core_engine` 掩遣霈桐靽"閫血撩鈭箏極霂恣"銝"閫血頝唾 `fast_lane`粥摰遛蝵+瘚"嚗 Skill 蝥批銝餉翮隞銝閬 44 `bug_record.root_cause_category` 憓 `core_engine` 掩遣霈桐靽"閫血撩鈭箏極霂恣"銝"閫血頝唾 `fast_lane`粥摰遛蝵+瘚"嚗 Skill 蝥批銝餉翮隞銝閬
45 45
@@ -63,6 +63,6 @@ Validator worktree PASS 銋嚗it_worktree_ref 銵 @@ -63,6 +63,6 @@ Validator worktree PASS 銋嚗it_worktree_ref 銵
63 63
64 ## 4. 撠 64 ## 4. 撠
65 65
66 -9 甇仿 3 甇亦嚗1 甇亙之璁雿瓷撘挽霈∴銝 5 甇伐"僎"嚗銝凋葵撩**2026-07-30 **嚗撩 2嚗遛摰撩 3嚗敹辣耨祥甇蛹"撩鈭箏極霂恣"嚗歇摰祕蝏挽霈∴ `docs/supervisor-auto-rollback-design.md`嚗/摨瑟//MVP 嚗 `docs/supervisor-design-comparison.md`嚗獢嚗撩 1嚗orktree 隞嗅撩嚗敺 05 瘝餌雿 66 +9 甇仿 3 甇亦嚗1 甇亙之璁雿瓷撘挽霈∴銝 5 甇伐"僎"嚗銝凋葵撩**2026-07-30 **嚗撩 2嚗遛摰撩 3嚗敹辣耨祥甇蛹"撩鈭箏極霂恣"嚗歇摰祕蝏挽霈∴ `docs/done/supervisor-auto-rollback-design.md`嚗/摨瑟//MVP 嚗 `docs/done/supervisor-design-comparison.md`嚗獢嚗撩 1嚗orktree 隞嗅撩嚗敺 05 瘝餌雿
67 67
68 憒餈遣霈桅◇摨撩 1嚗瘝餌 05 contract.json嚗雿帕銝嚗撩 2/3 挽霈∪歇蝏憭摰蝎漲嚗隞乩蛹 PH4 銋蝡 Phase嚗H5嚗銝曇 `development-plan.md`嚗蔣 PH0-PH4 銝餌瑪雿撠望 PH5 Unit 圾銋嚗&霈方挽霈∠阮頨急瓷憸摰 68 憒餈遣霈桅◇摨撩 1嚗瘝餌 05 contract.json嚗雿帕銝嚗撩 2/3 挽霈∪歇蝏憭摰蝎漲嚗隞乩蛹 PH4 銋蝡 Phase嚗H5嚗銝曇 `development-plan.md`嚗蔣 PH0-PH4 銝餌瑪雿撠望 PH5 Unit 圾銋嚗&霈方挽霈∠阮頨急瓷憸摰
docs/supervisor-auto-rollback-design.md renamed to docs/done/supervisor-auto-rollback-design.md
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 **日期**: 2026-07-30(同日更新:完成 Supervisor 概念改名消歧,见下方命名说明) 3 **日期**: 2026-07-30(同日更新:完成 Supervisor 概念改名消歧,见下方命名说明)
4 **性质**: 详细设计,回应用户"自动回滚机制现在就设计一下,省得到 PH5 阶段有那个阶段的问题"。落地 `self-development-feasibility.md` 缺口 2(应用变更+生效)与缺口 3(用户更正版:不要求人工评审,要求 supervisor 自动监督回滚)。 4 **性质**: 详细设计,回应用户"自动回滚机制现在就设计一下,省得到 PH5 阶段有那个阶段的问题"。落地 `self-development-feasibility.md` 缺口 2(应用变更+生效)与缺口 3(用户更正版:不要求人工评审,要求 supervisor 自动监督回滚)。
5 -**依赖**: `docs/supervisor-design-comparison.md`(方案选型结论:复用 graphloop 隔离/manifest/apply 库,见 `supervisor/isolation/`;Deploy Supervisor 自身决策逻辑必须是确定性代码,不接入 LLM)。 5 +**依赖**: `docs/done/supervisor-design-comparison.md`(方案选型结论:复用 graphloop 隔离/manifest/apply 库,见 `supervisor/isolation/`;Deploy Supervisor 自身决策逻辑必须是确定性代码,不接入 LLM)。
6 **状态**: 设计已回填进正式 SDD(`01-M`/`03-C`/`04-P`/`05`/`06`/`sdd-mvcp-contract.json`),代码尚未实现(`supervisor/isolation/` 除外,那是通用库不是本机制专属)。 6 **状态**: 设计已回填进正式 SDD(`01-M`/`03-C`/`04-P`/`05`/`06`/`sdd-mvcp-contract.json`),代码尚未实现(`supervisor/isolation/` 除外,那是通用库不是本机制专属)。
7 7
8 **命名说明(2026-07-30)**:`graphlemon_architecture.md` 里已经有一个"Supervisor(调度子 Agent)"概念(Coder Work Graph 内部的常驻监控节点,负责监控 Work Node 执行状态、触发 reflection/重新调度)。本文档描述的是完全不同的另一个组件——一个**独立于 Graph Engine 之外**、专门负责 graphlemon 自我修改后蓝绿部署与自动回滚的进程。为避免和架构文档里的既有概念混淆,本文档统一称之为 **Deploy Supervisor(部署监督进程)**,所有标识符前缀使用 `c_deploy_supervisor_*`,与既有的 `Supervisor(调度子 Agent)`概念完全区分开:两者职责不同(一个管 Work Node 执行进度,一个管整个 graphlemon 进程的部署/回滚)、进程边界不同(一个在 Graph Engine 进程内,一个在 Graph Engine 进程外)、生命周期不同(一个随 Work Graph 任务存在,一个是常驻的独立进程)。 8 **命名说明(2026-07-30)**:`graphlemon_architecture.md` 里已经有一个"Supervisor(调度子 Agent)"概念(Coder Work Graph 内部的常驻监控节点,负责监控 Work Node 执行状态、触发 reflection/重新调度)。本文档描述的是完全不同的另一个组件——一个**独立于 Graph Engine 之外**、专门负责 graphlemon 自我修改后蓝绿部署与自动回滚的进程。为避免和架构文档里的既有概念混淆,本文档统一称之为 **Deploy Supervisor(部署监督进程)**,所有标识符前缀使用 `c_deploy_supervisor_*`,与既有的 `Supervisor(调度子 Agent)`概念完全区分开:两者职责不同(一个管 Work Node 执行进度,一个管整个 graphlemon 进程的部署/回滚)、进程边界不同(一个在 Graph Engine 进程内,一个在 Graph Engine 进程外)、生命周期不同(一个随 Work Graph 任务存在,一个是常驻的独立进程)。
docs/supervisor-design-comparison.md renamed to docs/done/supervisor-design-comparison.md
docs/implementation-gap-analysis.md
@@ -2,9 +2,9 @@ @@ -2,9 +2,9 @@
2 2
3 **日期**: 2026-08-03 3 **日期**: 2026-08-03
4 **性质**: 现状快照,回应用户 "现在整个系统的实现和我们的文档的 goal 和 brd prd sdd 还有哪些差距"。基于对 `git log`、 4 **性质**: 现状快照,回应用户 "现在整个系统的实现和我们的文档的 goal 和 brd prd sdd 还有哪些差距"。基于对 `git log`、
5 -`docs/development-plan.md`(含各 Unit 事后补记)、`docs/build-report.md`、`docs/sdd/06-验收与测试锚点.md` 和实际 5 +`docs/development-plan.md`(含各 Unit 事后补记)、`docs/done/build-report.md`、`docs/sdd/06-验收与测试锚点.md` 和实际
6 代码(`src/`、`supervisor/`)的交叉核对得出,不是重新跑一遍验收。 6 代码(`src/`、`supervisor/`)的交叉核对得出,不是重新跑一遍验收。
7 -**结论先说**: 45 个 Unit(U-001~U-043 + U-004.5/U-024.5)全部有对应实现和提交,`docs/build-report.md` 的 7 +**结论先说**: 45 个 Unit(U-001~U-043 + U-004.5/U-024.5)全部有对应实现和提交,`docs/done/build-report.md` 的
8 "全部完成"判断在"每个 Unit 都有代码落地"这个意义上是对的。但"全部完成"不等于"和文档设定的目标完全对齐"—— 8 "全部完成"判断在"每个 Unit 都有代码落地"这个意义上是对的。但"全部完成"不等于"和文档设定的目标完全对齐"——
9 差距集中在 PH5 的"自动"两个字还没完全兑现、系统还不是真正自驱动的、以及若干 SDD 契约的已知简化。逐条见下。 9 差距集中在 PH5 的"自动"两个字还没完全兑现、系统还不是真正自驱动的、以及若干 SDD 契约的已知简化。逐条见下。
10 **如何维护本文档**: 这是一次性快照,会随实现推进而过时。发现某条已经补上或又出现新缺口时,直接编辑对应条目 10 **如何维护本文档**: 这是一次性快照,会随实现推进而过时。发现某条已经补上或又出现新缺口时,直接编辑对应条目
@@ -77,7 +77,7 @@ @@ -77,7 +77,7 @@
77 77
78 - `development-plan.md`/`unit_matrix.json` 从写就没有随实现更新"done"状态,只能靠 `git log` 和散落在各 Unit 78 - `development-plan.md`/`unit_matrix.json` 从写就没有随实现更新"done"状态,只能靠 `git log` 和散落在各 Unit
79 条目下的事后补记("实现补充"/"真实 bug")判断真实进度。 79 条目下的事后补记("实现补充"/"真实 bug")判断真实进度。
80 -- `docs/build-report.md` 里"⚠️ 尚未真实执行的一项"那一段(称 `RUN_REAL_SELF_HOSTING_E2E` 从未跑过)本身也已 80 +- `docs/done/build-report.md` 里"⚠️ 尚未真实执行的一项"那一段(称 `RUN_REAL_SELF_HOSTING_E2E` 从未跑过)本身也已
81 经过时——`development-plan.md` U-042 条目下 2026-08-03 的记录显示之后确实做了 13 次真实自举压测(`git log` 81 经过时——`development-plan.md` U-042 条目下 2026-08-03 的记录显示之后确实做了 13 次真实自举压测(`git log`
82 里那一长串 `graphlemon: auto-commit`/`merge`/artifact 提交就是证据),只是仍未到达 `finalized`。连"总结性 82 里那一长串 `graphlemon: auto-commit`/`merge`/artifact 提交就是证据),只是仍未到达 `finalized`。连"总结性
83 收口文档"都会在自举持续运行的过程中被甩在后面——这是个会持续放大的风险,不是一次性的疏漏。 83 收口文档"都会在自举持续运行的过程中被甩在后面——这是个会持续放大的风险,不是一次性的疏漏。
docs/reference/graph-engineering-1000-agents-5-steps.md
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 - **来源**:微信公众号文章,https://mp.weixin.qq.com/s/tR0Z_3Yka2dNXQPO7P0OsA 3 - **来源**:微信公众号文章,https://mp.weixin.qq.com/s/tR0Z_3Yka2dNXQPO7P0OsA
4 - **收录方式**:原文由用户在对话中手动粘贴全文文本(微信有反爬验证,工具无法直接抓取正文);正文引用的插图未能一并保存,下文以 `[图片]` 标记原文插图位置。 4 - **收录方式**:原文由用户在对话中手动粘贴全文文本(微信有反爬验证,工具无法直接抓取正文);正文引用的插图未能一并保存,下文以 `[图片]` 标记原文插图位置。
5 - **入库日期**:2026-08-04 5 - **入库日期**:2026-08-04
6 -- **说明**:本文与 `docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md`(14 步版)是同一主题的姊妹篇/精简版,均围绕 Claude Code 的 Dynamic Workflows 功能展开,可与该文对照阅读。 6 +- **说明**:本文与 `docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md`(14 步版)是同一主题的姊妹篇/精简版,均围绕 Claude Code 的 Dynamic Workflows 功能展开,可与该文对照阅读。
7 7
8 --- 8 ---
9 9
docs/reference/graph-engineering-vs-loop-engineering-overview.md
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 - **来源**:腾讯云开发者微信公众号文章,https://mp.weixin.qq.com/s/0DGAr-N-rrYJrzxc2yiGEw 3 - **来源**:腾讯云开发者微信公众号文章,https://mp.weixin.qq.com/s/0DGAr-N-rrYJrzxc2yiGEw
4 - **收录方式**:原文由用户在对话中手动粘贴全文文本(微信有反爬验证,工具无法直接抓取正文);正文引用的插图未能一并保存,下文以 `[图片]` 标记原文插图位置。 4 - **收录方式**:原文由用户在对话中手动粘贴全文文本(微信有反爬验证,工具无法直接抓取正文);正文引用的插图未能一并保存,下文以 `[图片]` 标记原文插图位置。
5 - **入库日期**:2026-08-04 5 - **入库日期**:2026-08-04
6 -- **说明**:与 `docs/reference/graph-engineering-1000-agents-5-steps.md`(5 步版)、`docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md`(14 步版)是同一主题下的第三篇材料,但视角不同——前两篇是"怎么用 Claude Code Dynamic Workflows 动手搭图"的实操教程,本文是更宏观的行业综述:术语起源、五层演进关系、Anthropic 官方模式总结、框架对比(LangGraph/AutoGen/ADK)与真实生产案例(LinkedIn、Uber)。 6 +- **说明**:与 `docs/reference/graph-engineering-1000-agents-5-steps.md`(5 步版)、`docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md`(14 步版)是同一主题下的第三篇材料,但视角不同——前两篇是"怎么用 Claude Code Dynamic Workflows 动手搭图"的实操教程,本文是更宏观的行业综述:术语起源、五层演进关系、Anthropic 官方模式总结、框架对比(LangGraph/AutoGen/ADK)与真实生产案例(LinkedIn、Uber)。
7 7
8 --- 8 ---
9 9
docs/sdd/00-SDD-生成记录.md
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 | 字段 | 值 | 5 | 字段 | 值 |
6 |---|---| 6 |---|---|
7 | project | graphlemon | 7 | project | graphlemon |
8 -| version | v1.7.0(决策:MVP 阶段只用 Claude Code ACP 一种执行后端,不考虑 Hermes CLI,见架构文档 7.2 节;新增 Claude Code plan 额度耗尽的等待重试机制 `p_usage_limit_wait`,见 03-C 3.6 节、04-P 4.4.1 节;新增 PH5 自举能力——Deploy Supervisor 蓝绿部署与自动回滚,见 01-M 1.15/1.16 节、03-C 3.15 节、04-P 4.6 节、06 节 REQ-15,详细设计见 `docs/supervisor-auto-rollback-design.md`) | 8 +| version | v1.7.0(决策:MVP 阶段只用 Claude Code ACP 一种执行后端,不考虑 Hermes CLI,见架构文档 7.2 节;新增 Claude Code plan 额度耗尽的等待重试机制 `p_usage_limit_wait`,见 03-C 3.6 节、04-P 4.4.1 节;新增 PH5 自举能力——Deploy Supervisor 蓝绿部署与自动回滚,见 01-M 1.15/1.16 节、03-C 3.15 节、04-P 4.6 节、06 节 REQ-15,详细设计见 `docs/done/supervisor-auto-rollback-design.md`) |
9 | owner | bboonnee | 9 | owner | bboonnee |
10 | effective_date | 2026-07-30 | 10 | effective_date | 2026-07-30 |
11 | 方法论 | 复用本地 Hermes `planning` profile 的 `project-sdd-planning` skill(MVCP:Model/View/Controller/Process) | 11 | 方法论 | 复用本地 Hermes `planning` profile 的 `project-sdd-planning` skill(MVCP:Model/View/Controller/Process) |
docs/sdd/01-M-模型设计.md
@@ -223,7 +223,7 @@ @@ -223,7 +223,7 @@
223 - `updated_by` string,not null 223 - `updated_by` string,not null
224 - `updated_at` timestamp 224 - `updated_at` timestamp
225 225
226 -## 1.15 实体:`deployment`(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`) 226 +## 1.15 实体:`deployment`(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
227 227
228 **目的**:Deploy Supervisor 对 graphlemon 自身(self-target 场景)执行一次蓝绿部署的记录,覆盖从候选实例拉起到最终 `finalized`/`rolled_back`/`blocked_pending_human` 的完整生命周期。Deploy Supervisor 是独立于 Graph Engine 之外的进程,不要与架构文档里"Supervisor(调度子 Agent,Coder Work Graph 内部监控节点)"混淆,两者是完全不同的概念(详见 `supervisor-auto-rollback-design.md` 开头命名说明)。 228 **目的**:Deploy Supervisor 对 graphlemon 自身(self-target 场景)执行一次蓝绿部署的记录,覆盖从候选实例拉起到最终 `finalized`/`rolled_back`/`blocked_pending_human` 的完整生命周期。Deploy Supervisor 是独立于 Graph Engine 之外的进程,不要与架构文档里"Supervisor(调度子 Agent,Coder Work Graph 内部监控节点)"混淆,两者是完全不同的概念(详见 `supervisor-auto-rollback-design.md` 开头命名说明)。
229 229
@@ -238,7 +238,7 @@ @@ -238,7 +238,7 @@
238 - `previous_instance_dir` string,not null —— 上一个已知良好版本的 checkout 目录,在本次 deployment 监控窗口正常关闭(`finalized`)前不允许清理 238 - `previous_instance_dir` string,not null —— 上一个已知良好版本的 checkout 目录,在本次 deployment 监控窗口正常关闭(`finalized`)前不允许清理
239 - 约束:同一时刻 graphlemon 自身仓库只允许一条处于非终态(不在 `finalized`\|`rolled_back`\|`blocked_pending_human`)的 `deployment` 记录,避免并发蓝绿部署互相干扰 239 - 约束:同一时刻 graphlemon 自身仓库只允许一条处于非终态(不在 `finalized`\|`rolled_back`\|`blocked_pending_human`)的 `deployment` 记录,避免并发蓝绿部署互相干扰
240 240
241 -## 1.16 实体:`deploy_health_signal`(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`) 241 +## 1.16 实体:`deploy_health_signal`(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
242 242
243 **目的**:`deployment` 生命周期内每次健康检查/监控采样的记录,全部是确定性判断结果,不涉及任何 LLM 判断。 243 **目的**:`deployment` 生命周期内每次健康检查/监控采样的记录,全部是确定性判断结果,不涉及任何 LLM 判断。
244 244
docs/sdd/03-C-控制与接口.md
@@ -259,7 +259,7 @@ @@ -259,7 +259,7 @@
259 - 断线重连约束:客户端重连后需带上 `last_event_id` 查询自该事件之后的遗漏事件(通过 `c_task_get` 系接口的时间线字段补齐,而非 WebSocket 层做消息重放缓冲,MVP 阶段简化处理) 259 - 断线重连约束:客户端重连后需带上 `last_event_id` 查询自该事件之后的遗漏事件(通过 `c_task_get` 系接口的时间线字段补齐,而非 WebSocket 层做消息重放缓冲,MVP 阶段简化处理)
260 - `owner_team`:control-plane 260 - `owner_team`:control-plane
261 261
262 -## 3.15 Deploy Supervisor 控制操作(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`) 262 +## 3.15 Deploy Supervisor 控制操作(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
263 263
264 以下五个操作均由 Deploy Supervisor 进程调用,不是控制台前端直接调用的 HTTP API(Deploy Supervisor 是独立于 Graph Engine 之外的确定性 Python 进程,见 01-M 1.15 节),标记为内部接口。所有决策逻辑均为确定性代码,不接入任何 LLM 判断。触发前提:`task` 关联的 `project.target_folder_path == graphlemon 平台自身安装路径` 且 Validator 判定 PASS 后,worktree 分支已 merge 回主分支(见 3.11 节 `core_engine` 路由分支)。 264 以下五个操作均由 Deploy Supervisor 进程调用,不是控制台前端直接调用的 HTTP API(Deploy Supervisor 是独立于 Graph Engine 之外的确定性 Python 进程,见 01-M 1.15 节),标记为内部接口。所有决策逻辑均为确定性代码,不接入任何 LLM 判断。触发前提:`task` 关联的 `project.target_folder_path == graphlemon 平台自身安装路径` 且 Validator 判定 PASS 后,worktree 分支已 merge 回主分支(见 3.11 节 `core_engine` 路由分支)。
265 265
docs/sdd/04-P-流程与状态流转.md
@@ -101,7 +101,7 @@ @@ -101,7 +101,7 @@
101 101
102 约束:清理过程只删除物理文件和更新 `execution_log_file` 状态,不删除 `execution_log_file` 记录本身、不删除任何 `execution_event`/`task`/`artifact` 等业务数据,保证"日志曾经存在过"这件事可审计(对应 01-M 1.13 节约束)。 102 约束:清理过程只删除物理文件和更新 `execution_log_file` 状态,不删除 `execution_log_file` 记录本身、不删除任何 `execution_event`/`task`/`artifact` 等业务数据,保证"日志曾经存在过"这件事可审计(对应 01-M 1.13 节约束)。
103 103
104 -## 4.6 流程:`p_deploy_supervisor_blue_green`(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`) 104 +## 4.6 流程:`p_deploy_supervisor_blue_green`(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
105 105
106 - `start_state`:`staged` 106 - `start_state`:`staged`
107 - `end_state`:`finalized`(安全终态)| `blocked_pending_human`(唯一需人工介入的终态,触发条件严格限定为"自动回滚机制本身失败",不是"代码写得不好") 107 - `end_state`:`finalized`(安全终态)| `blocked_pending_human`(唯一需人工介入的终态,触发条件严格限定为"自动回滚机制本身失败",不是"代码写得不好")
docs/sdd/05-实施约束与依赖.md
@@ -110,9 +110,9 @@ @@ -110,9 +110,9 @@
110 110
111 - **worktree 无条件强制**:`target_folder_path == graphlemon 平台自身安装路径` 时,无论并行还是串行,Coder/Validator 一律强制使用 `git_worktree_ref`,不适用 03-C 3.6 节"仅并行写代码节点才强制申请 worktree"的一般规则——这是该规则的 self-target 特例 111 - **worktree 无条件强制**:`target_folder_path == graphlemon 平台自身安装路径` 时,无论并行还是串行,Coder/Validator 一律强制使用 `git_worktree_ref`,不适用 03-C 3.6 节"仅并行写代码节点才强制申请 worktree"的一般规则——这是该规则的 self-target 特例
112 - **`core_engine` 分类路由**:`bug_record.root_cause_category` 新增枚举值 `core_engine`,覆盖 `.claude/agents/*.md` 身份文件变更、Graph Engine 调度器/执行器抽象层核心逻辑变更、01-M 自身数据库 schema 变更三类场景。命中 `core_engine` 时,`c_org_loop_route`(03-C)路由到 PH5 的 `p_deploy_supervisor_blue_green` 流程(04-P),而不是常规的 `fast_lane`/`full_replan` 112 - **`core_engine` 分类路由**:`bug_record.root_cause_category` 新增枚举值 `core_engine`,覆盖 `.claude/agents/*.md` 身份文件变更、Graph Engine 调度器/执行器抽象层核心逻辑变更、01-M 自身数据库 schema 变更三类场景。命中 `core_engine` 时,`c_org_loop_route`(03-C)路由到 PH5 的 `p_deploy_supervisor_blue_green` 流程(04-P),而不是常规的 `fast_lane`/`full_replan`
113 -- **不要求提交前人工评审**:`core_engine` 改动不强制在合并主分支前经过人工评审(此前"比照 `org_graph_definition` 强制人工 PR 评审"的提案已被用户明确否决),改为合并后由 Deploy Supervisor 持续监控运行时行为、检测异常时自动回滚(见 04-P `p_deploy_supervisor_blue_green`、`docs/supervisor-auto-rollback-design.md`)。5.6 节"允许停机迁移"的简化假设不适用于这类 self-target 场景——self-target 的 schema 变更同样走蓝绿部署+监控流程,不是简单停机迁移 113 +- **不要求提交前人工评审**:`core_engine` 改动不强制在合并主分支前经过人工评审(此前"比照 `org_graph_definition` 强制人工 PR 评审"的提案已被用户明确否决),改为合并后由 Deploy Supervisor 持续监控运行时行为、检测异常时自动回滚(见 04-P `p_deploy_supervisor_blue_green`、`docs/done/supervisor-auto-rollback-design.md`)。5.6 节"允许停机迁移"的简化假设不适用于这类 self-target 场景——self-target 的 schema 变更同样走蓝绿部署+监控流程,不是简单停机迁移
114 114
115 -## 5.9 Deploy Supervisor MVP 固定参数(`docs/supervisor-auto-rollback-design.md` §9,2026-07-30 回填) 115 +## 5.9 Deploy Supervisor MVP 固定参数(`docs/done/supervisor-auto-rollback-design.md` §9,2026-07-30 回填)
116 116
117 | 参数 | 草案值 | 说明 | 117 | 参数 | 草案值 | 说明 |
118 |---|---|---| 118 |---|---|---|
docs/sdd/06-验收与测试锚点.md
@@ -114,7 +114,7 @@ @@ -114,7 +114,7 @@
114 - boundary:配置 `retention_days=1`,构造一条 `created_at` 超过 1 天的 `closed` 状态日志,清理任务运行后应转为 `purged` 且物理文件被删除;`v_log_viewer` 打开该日志时展示"已被清理"提示而非报错 114 - boundary:配置 `retention_days=1`,构造一条 `created_at` 超过 1 天的 `closed` 状态日志,清理任务运行后应转为 `purged` 且物理文件被删除;`v_log_viewer` 打开该日志时展示"已被清理"提示而非报错
115 - failure:订阅一个不存在的 `node_exec_id` 的日志流,应返回 `NODE_EXEC_NOT_FOUND`;清理任务遇到某个文件删除失败(如被占用)时,应仅记录该项失败、不影响其余候选项的清理,且该条目下次周期继续重试 115 - failure:订阅一个不存在的 `node_exec_id` 的日志流,应返回 `NODE_EXEC_NOT_FOUND`;清理任务遇到某个文件删除失败(如被占用)时,应仅记录该项失败、不影响其余候选项的清理,且该条目下次周期继续重试
116 116
117 -## REQ-15:Deploy Supervisor 蓝绿部署与自动回滚(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`) 117 +## REQ-15:Deploy Supervisor 蓝绿部署与自动回滚(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
118 118
119 - 覆盖:M=`deployment`,`deploy_health_signal`,`bug_record`(`root_cause_category=core_engine`);C=`c_deploy_supervisor_stage`,`c_deploy_supervisor_health_check`,`c_deploy_supervisor_cutover`,`c_deploy_supervisor_monitor`,`c_deploy_supervisor_rollback`,`c_org_loop_route`;P=`p_deploy_supervisor_blue_green` 119 - 覆盖:M=`deployment`,`deploy_health_signal`,`bug_record`(`root_cause_category=core_engine`);C=`c_deploy_supervisor_stage`,`c_deploy_supervisor_health_check`,`c_deploy_supervisor_cutover`,`c_deploy_supervisor_monitor`,`c_deploy_supervisor_rollback`,`c_org_loop_route`;P=`p_deploy_supervisor_blue_green`
120 - 验收用例: 120 - 验收用例:
docs/dynamic-work-graph-design-skill-requirements.md renamed to docs/todo/dynamic-work-graph-design-skill-requirements.md
docs/todo/graph-engineering-open-items.md
@@ -70,7 +70,7 @@ @@ -70,7 +70,7 @@
70 70
71 **确认结果(2026-08-05)**:立项。 71 **确认结果(2026-08-05)**:立项。
72 72
73 -**待办**:✅ 需求文档已产出 → `docs/work-graph-time-travel-debugging-requirements.md`。已明确跟 Deploy Supervisor 自动回滚(`docs/supervisor-auto-rollback-design.md`)的命名消歧。最大的数据模型影响点:`work_graph_run` 表现有的 `ux_work_graph_run_active` 唯一约束(同一 `task_id`+`org_node` 同时只能有一个非终态 run)会挡住分叉分支,需要在设计阶段决定怎么扩展。 73 +**待办**:✅ 需求文档已产出 → `docs/work-graph-time-travel-debugging-requirements.md`。已明确跟 Deploy Supervisor 自动回滚(`docs/done/supervisor-auto-rollback-design.md`)的命名消歧。最大的数据模型影响点:`work_graph_run` 表现有的 `ux_work_graph_run_active` 唯一约束(同一 `task_id`+`org_node` 同时只能有一个非终态 run)会挡住分叉分支,需要在设计阶段决定怎么扩展。
74 74
75 --- 75 ---
76 76
@@ -114,7 +114,7 @@ @@ -114,7 +114,7 @@
114 114
115 **确认结果(2026-08-05)**:确认要做,用户强调"这个很核心"。触发规则明确为:**只要 Work Graph 规划了 fan-out/fan-in 的并行节点,节点触发时就应该并行执行**——这跟需求文档 FR-1/FR-2 已经写的内容一致,不需要改需求文档的功能需求本身,只需要在文档里把这句话作为明确的验收口径固化下来。 115 **确认结果(2026-08-05)**:确认要做,用户强调"这个很核心"。触发规则明确为:**只要 Work Graph 规划了 fan-out/fan-in 的并行节点,节点触发时就应该并行执行**——这跟需求文档 FR-1/FR-2 已经写的内容一致,不需要改需求文档的功能需求本身,只需要在文档里把这句话作为明确的验收口径固化下来。
116 116
117 -**待办**:进入详细设计阶段(`topological_layers()` 的具体实现、线程池调度器接口、锁的粒度),需求文档里第 8 节列的开放问题需要在设计阶段逐一敲定。这是 E、F 两条的前置依赖,建议优先推进。 117 +**待办**:✅ 详细设计已产出 → `docs/work-graph-parallel-execution-design.md`。核心设计:新增 `topological_layers()`(层内并发、层间屏障),`_LockedConn` 线程安全代理复用 `backend_server.py` 的锁模式,`create_worktree()` 加防御性锁,`WorkGraphLayerError` 聚合层内多个失败。已知遗留限制(诚实记录,非本轮解决):单节点配额等待可长达数小时,会拖住整层其他已完成节点的返回——需要更细粒度的可中断调度才能解决,超出本次范围。下一步:实施(见设计文档第 11 节的分步实施建议),或先评审设计。
118 118
119 --- 119 ---
120 120
docs/validator-loop-until-dry-skill-requirements.md renamed to docs/todo/validator-loop-until-dry-skill-requirements.md
docs/verifier-fan-out-jury-requirements.md renamed to docs/todo/verifier-fan-out-jury-requirements.md
docs/todo/work-graph-parallel-execution-design.md 0 → 100644
  1 +# Work Graph 节点并行执行——详细设计(SDD)
  2 +
  3 +**日期**: 2026-08-05
  4 +**性质**: 详细设计,对应需求文档 `docs/work-graph-parallel-execution-requirements.md`(已确认,核心优先级)。本设计逐条回答该需求文档第 8 节的开放问题,并给出可直接实施的接口/算法/测试方案。
  5 +**依赖代码**:`src/work_graph_orchestrate.py`(`run_graph`/`topological_order`/`_ensure_node_row`/`_make_run_fn`/`_verify_and_maybe_regenerate`)、`src/work_node_run.py`(`run_work_node`/`create_worktree`)、`src/backend_server.py`(`conn_lock` 先例)。
  6 +**状态**: 设计草案,待评审后实施。
  7 +
  8 +---
  9 +
  10 +## 1. 设计概览
  11 +
  12 +现状:`run_graph()`(`work_graph_orchestrate.py:196`)用 `topological_order()` 把 `graph_manifest` 拍平成一条扁平列表,纯 Python `for` 循环逐节点执行。
  13 +
  14 +改造后:新增 `topological_layers()`,把 Kahn 算法的中间产物(每一轮 in-degree 归零的节点集合)保留为"层"而不是拍平;`run_graph()` 改成"层间顺序、层内并发"——同一层内的节点用 `ThreadPoolExecutor` 并发触发各自的 `run_work_node()`,层与层之间保持屏障(下一层要等上一层全部完成)。
  15 +
  16 +**不改变的部分**(刻意保持稳定,降低改动面):
  17 +- 每个节点仍然是"渲染成一个单节点 workflow 脚本 + 独立 `claude -p` 子进程调用"(`_make_run_fn`/`_render_node_script` 不变)。
  18 +- `run_work_node()` 的状态机、断点续跑逻辑、重试/配额等待逻辑完全不变。
  19 +- `run_graph()` 的对外签名和返回值格式(`{node_id: output_payload}`)不变(需求文档 FR-9)。
  20 +- `verify_fn` 的语义不变(需求文档 FR-8)。
  21 +
  22 +## 2. `topological_layers()`:新增分层拓扑排序
  23 +
  24 +```python
  25 +def topological_layers(graph_manifest):
  26 + """Kahn 算法的分层版本:返回 List[List[node]],每个内层 list 是同一轮
  27 + in-degree 归零的节点集合(互相之间保证没有依赖,可以并发执行)。
  28 + 层内按 node_id 排序(保持确定性,便于测试断言)。
  29 + 依赖来源、报错行为跟现有 topological_order() 完全一致(合并
  30 + depends_on 和 edges 两个来源;有环或引用未知 node_id 时 raise ValueError)。
  31 + """
  32 + nodes = {n["node_id"]: n for n in graph_manifest["nodes"]}
  33 + deps = {node_id: set(n.get("depends_on") or []) for node_id, n in nodes.items()}
  34 + for edge in graph_manifest.get("edges", []):
  35 + to_id, from_id = edge.get("to"), edge.get("from")
  36 + if to_id in deps and from_id in nodes:
  37 + deps[to_id].add(from_id)
  38 +
  39 + in_degree = {node_id: len(d) for node_id, d in deps.items()}
  40 + dependents = {node_id: [] for node_id in nodes}
  41 + for node_id, d in deps.items():
  42 + for dep in d:
  43 + if dep in dependents:
  44 + dependents[dep].append(node_id)
  45 +
  46 + layers = []
  47 + current = sorted(node_id for node_id, degree in in_degree.items() if degree == 0)
  48 + seen_count = 0
  49 + while current:
  50 + layers.append([nodes[node_id] for node_id in current])
  51 + seen_count += len(current)
  52 + next_layer = set()
  53 + for node_id in current:
  54 + for dependent in dependents[node_id]:
  55 + in_degree[dependent] -= 1
  56 + if in_degree[dependent] == 0:
  57 + next_layer.add(dependent)
  58 + current = sorted(next_layer)
  59 + if seen_count != len(nodes):
  60 + raise ValueError(
  61 + "graph_manifest has a dependency cycle or a depends_on/edge references an unknown node_id"
  62 + )
  63 + return layers
  64 +```
  65 +
  66 +`topological_order()` **保留**(其他调用方,如 `e2e_pipeline.py:233` 直接用它拿扁平顺序写 artifact),但改成对 `topological_layers()` 的薄封装,保证行为/顺序完全不变:
  67 +
  68 +```python
  69 +def topological_order(graph_manifest):
  70 + return [node for layer in topological_layers(graph_manifest) for node in layer]
  71 +```
  72 +
  73 +(需要跑一次现有 `test_work_graph_orchestrate.py` 里 `topological_order` 的既有单测,确认拍平后的顺序逐字节相同——这两版算法的排序规则一致,理论上不会有差异,但改动了实现路径,必须实测验证不是"理论上一致"就够。)
  74 +
  75 +## 3. SQLite 并发写入:`_LockedConn` 代理
  76 +
  77 +**问题**:`run_work_node()`/`_ensure_node_row()`/`execution_log.append_log()` 等函数内部有大量分散的 `conn.execute(...)` + `conn.commit()` 调用点。层内并发后,这些调用可能从多个线程同时打到同一个 `sqlite3.Connection` 对象上。
  78 +
  79 +**关键判断**:SQLite 本身同一时刻只允许一个写者(这是 SQLite 的架构限制,不是 Python 线程模型能绕开的),所以不需要设计"细粒度多把锁",**一把锁保护整个连接的读写足够**——真正的并发收益来自于"锁只在单条 SQL 语句执行期间持有,`claude -p` 子进程调用(决定墙钟时间的部分)完全不持锁",而不是来自"多个线程能同时写 SQLite"(这本来就不可能)。
  80 +
  81 +**方案**:不去逐个包裹现有几十处 `conn.execute()` 调用点(改动面大、容易漏改),而是引入一个跟 `sqlite3.Connection` 接口兼容的轻量代理对象,在并发路径上把 `conn` 参数替换成这个代理——调用方代码(`work_node_run.py`/`work_graph_orchestrate.py`/`execution_log.py`)完全不用改一行:
  82 +
  83 +```python
  84 +class _LockedConn:
  85 + """sqlite3.Connection 的线程安全代理:execute/commit/executemany 各自
  86 + 持锁执行,可以直接替代 conn 参数传给现有代码,调用方无需感知。复用
  87 + backend_server.py::_Server 已经验证过的"共享连接 + threading.Lock"模式,
  88 + 只是把锁的粒度做到了每条语句级别(backend_server 现有的 conn_lock 只
  89 + 保护 server_close() 一处,不是通用查询锁——本类是把同一个思路做成可
  90 + 在任意并发场景复用的通用工具,两者不冲突,backend_server 未来可以直
  91 + 接换成这个类)。
  92 + """
  93 + def __init__(self, conn, lock=None):
  94 + self._conn = conn
  95 + self._lock = lock or threading.Lock()
  96 +
  97 + def execute(self, *args, **kwargs):
  98 + with self._lock:
  99 + return self._conn.execute(*args, **kwargs)
  100 +
  101 + def executemany(self, *args, **kwargs):
  102 + with self._lock:
  103 + return self._conn.executemany(*args, **kwargs)
  104 +
  105 + def commit(self):
  106 + with self._lock:
  107 + return self._conn.commit()
  108 +
  109 + def __getattr__(self, name):
  110 + # cursor()/rollback()/close() 等未显式包装的方法透传给底层连接,
  111 + # 调用方目前不会从多线程并发调用这些(没有并发路径用到它们)。
  112 + return getattr(self._conn, name)
  113 +```
  114 +
  115 +`run_graph()` 在进入并发执行前,用一把 `threading.Lock()` 包出一个 `_LockedConn` 实例,传给每个并发任务内部实际调用的 `_ensure_node_row`/`run_work_node`/`_make_run_fn`(后者内部会调用 `execution_log.append_log`)——注意:**只有并发的那一层用 `_LockedConn`,层外(比如 `run_graph()` 自己开头调用 `ensure_placeholder_skill`)继续用原始 `conn`,因为那部分本来就不在并发路径上,没必要多一层锁的开销**。
  116 +
  117 +## 4. Worktree 创建竞态
  118 +
  119 +`work_node_run.py::create_worktree()`(`git worktree add -b <branch> <path>`)在并发场景下可能对同一个 repo 的 `.git/worktrees/` 元数据产生竞态——这是**推测**,需求文档已标注"未经验证"。设计上采用保守做法:给 `create_worktree()` 包一把**进程级模块锁**(跟 SQLite 锁分开,因为这个锁保护的是文件系统/git 内部状态,不是数据库):
  120 +
  121 +```python
  122 +_worktree_creation_lock = threading.Lock()
  123 +
  124 +def create_worktree(repo_path, node_exec_id):
  125 + with _worktree_creation_lock:
  126 + ... # 原逻辑不变
  127 +```
  128 +
  129 +这个锁只包住 `git worktree add` 这一条命令本身(毫秒级),不包住节点其余的执行时间,所以即使锁確实是必要的,对整体并行收益的影响可以忽略——`claude -p` 调用(秒级到分钟级)才是墙钟时间的主体。**验证方式**:写一个真实的并发 `create_worktree` 调用测试(多线程同时对同一个临时 git 仓库调用),先不加锁跑一遍看是否真的出错,出错则保留锁、不出错也保留锁(保守优先,反正代价极小)。
  130 +
  131 +## 5. 并发调度器:`run_graph()` 改造
  132 +
  133 +```python
  134 +import concurrent.futures
  135 +
  136 +_DEFAULT_MAX_WORKERS = 4 # 保守默认值,见第 9 节
  137 +
  138 +def run_graph(
  139 + conn, run_id, org_node, skill_id, target_folder_path, graph_manifest,
  140 + build_prompt, input_schema, output_schema, timeout=600, needs_worktree=False,
  141 + verify_fn=None, sleep=time.sleep, now=time.time, max_workers=_DEFAULT_MAX_WORKERS,
  142 +):
  143 + ensure_placeholder_skill(conn, skill_id, org_node)
  144 + effective_needs_worktree = needs_worktree or paths.is_self_target(target_folder_path)
  145 + layers = topological_layers(graph_manifest)
  146 + outputs = {}
  147 + db_lock = threading.Lock()
  148 + locked_conn = _LockedConn(conn, db_lock)
  149 +
  150 + for layer in layers:
  151 + if len(layer) == 1:
  152 + # 单节点层:不走线程池,直接同步执行——省掉线程池调度开销,
  153 + # 也让现有单节点场景(目前观察到的真实 graph_manifest 全都是
  154 + # 单节点链)行为路径尽量贴近改造前,降低回归风险。
  155 + node = layer[0]
  156 + outputs[node["node_id"]] = _run_one_node(
  157 + conn, run_id, org_node, skill_id, target_folder_path, node, outputs,
  158 + build_prompt, input_schema, output_schema, timeout,
  159 + effective_needs_worktree, verify_fn, sleep, now,
  160 + )
  161 + continue
  162 +
  163 + worker_count = min(max_workers, len(layer))
  164 + errors = []
  165 + with concurrent.futures.ThreadPoolExecutor(max_workers=worker_count) as pool:
  166 + futures = {
  167 + pool.submit(
  168 + _run_one_node, locked_conn, run_id, org_node, skill_id, target_folder_path,
  169 + node, outputs, build_prompt, input_schema, output_schema, timeout,
  170 + effective_needs_worktree, verify_fn, sleep, now,
  171 + ): node
  172 + for node in layer
  173 + }
  174 + # 等整层全部完成(成功或失败)再继续——层内节点互不依赖,
  175 + # 一个节点的失败不该取消已经在跑的兄弟节点(需求文档 FR-5)。
  176 + done, _ = concurrent.futures.wait(futures, return_when=concurrent.futures.ALL_COMPLETED)
  177 + for future in done:
  178 + node = futures[future]
  179 + try:
  180 + outputs[node["node_id"]] = future.result()
  181 + except Exception as e:
  182 + errors.append((node["node_id"], e))
  183 +
  184 + if errors:
  185 + # 聚合报错:全部记录,抛出的异常携带完整清单,不只是"第一个",
  186 + # 方便排查同一层里到底几个节点失败、分别是什么原因。
  187 + raise WorkGraphLayerError(errors)
  188 +
  189 + return outputs
  190 +
  191 +
  192 +def _run_one_node(
  193 + conn, run_id, org_node, skill_id, target_folder_path, node, outputs,
  194 + build_prompt, input_schema, output_schema, timeout, needs_worktree, verify_fn, sleep, now,
  195 +):
  196 + """原 run_graph() 循环体单节点逻辑原样保留,只是抽成一个独立函数,
  197 + 好让同步路径(单节点层)和并发路径(多节点层)共用同一份实现,
  198 + 不重复维护两份"跑一个节点+可选 verify_fn"的逻辑。"""
  199 + node_id = node["node_id"]
  200 + node_exec_id = _ensure_node_row(
  201 + conn, run_id, node_id, org_node, skill_id,
  202 + "work_graph_node_input.schema.json", "work_graph_node_output.schema.json",
  203 + {"node_id": node_id},
  204 + )
  205 + prompt = build_prompt(node, outputs)
  206 + run_fn = _make_run_fn(conn, target_folder_path, node_exec_id, org_node, prompt, output_schema, timeout)
  207 + output = work_node_run.run_work_node(
  208 + conn, node_exec_id, {"node_id": node_id}, input_schema, output_schema, run_fn,
  209 + needs_worktree=needs_worktree, repo_path=target_folder_path, sleep=sleep, now=now,
  210 + )
  211 + if verify_fn is not None:
  212 + output = _verify_and_maybe_regenerate(
  213 + conn, run_id, org_node, skill_id, target_folder_path, node, node_id, prompt,
  214 + output, input_schema, output_schema, timeout, needs_worktree, verify_fn, sleep, now,
  215 + )
  216 + return output
  217 +
  218 +
  219 +class WorkGraphLayerError(Exception):
  220 + """一层内一个或多个节点执行失败——携带 [(node_id,原始异常), ...] 完整
  221 + 清单,调用方可以按需检查是不是全是同一种可恢复错误。"""
  222 + def __init__(self, errors):
  223 + self.errors = errors
  224 + summary = "; ".join("{}: {}".format(node_id, e) for node_id, e in errors)
  225 + super().__init__("{} node(s) failed in one layer: {}".format(len(errors), summary))
  226 +```
  227 +
  228 +**关键设计取舍——`build_prompt(node, outputs)` 里的 `outputs` 读取时机**:层内并发时,`outputs` 字典在整层的所有 `_run_one_node` 调用里都是**同一个共享字典**,但层内节点互相没有依赖,`build_prompt` 不会读到"同层兄弟节点"的输出(拓扑保证),只会读到"更早的层"已经写完的输出——这些更早层的写入在当前层开始前已经全部完成(层屏障保证),所以层内并发读 `outputs` 是安全的,不需要给 `outputs` 字典本身加锁(Python 字典的读操作本身也是线程安全的,只要没有并发写同一个 key,而层内不同节点写的是不同的 `node_id` key,不冲突)。**每个节点各自的 `outputs[node_id] = ...` 写入统一放到层执行完、`concurrent.futures.wait` 返回之后、在主线程里做**(上面伪代码里 `for future in done: outputs[node["node_id"]] = future.result()`),不在子线程里直接写共享字典,进一步避免任何并发写字典的疑虑,即使实际上没有真正的 key 冲突。
  229 +
  230 +## 6. 错误/配额处理的真实限制(诚实记录,不回避)
  231 +
  232 +`work_node_run.run_work_node()` 内部对配额限制(`UsageLimitReachedError`→`_UsageLimitSignal`)的处理是**在函数内部原地 sleep-retry**,退避时间可以长达 30 分钟一次、session 级超时 8 小时、weekly 级超时 9 天(`work_node_run.py:42-43`)。这意味着:如果同一层里节点 A 触发了配额等待、节点 B 几秒内就正常完成,`concurrent.futures.wait(..., return_when=ALL_COMPLETED)` 会让整层卡在等 A——B 的结果虽然已经算出来了,但因为层屏障语义(下一层要等这一层全部完成),实际上整个 `run_graph()` 调用也会被拖住。
  233 +
  234 +**这是本设计接受的限制,不是本轮要解决的问题**:Python 线程一旦启动就没有干净的"取消并回收"机制(`Future.cancel()` 只能取消还没开始跑的任务),要让 B 提前继续、A 在后台继续等配额而不阻塞整层,需要给 `run_work_node()` 引入可中断/可分离的配额等待机制——这是比本次改造大得多的另一个课题,不在本次范围内(需求文档"非目标"第 1 条已经声明"不做完整的数据流式调度器",这个限制是同一类取舍的自然延伸)。如果未来这个限制成为真实瓶颈,需要单独立项评估。
  235 +
  236 +## 7. 断点续跑兼容性——不需要新逻辑
  237 +
  238 +`run_work_node()` 已有的"已 `done` 节点短路返回持久化输出"逻辑对并发路径天然兼容,不需要改动:进程重启后重新调用 `run_graph()`,`topological_layers()` 重新计算分层,对每一层内的节点,`_run_one_node()` 里的 `run_work_node()` 调用会对"已完成"的节点立即返回持久化结果(几乎不占用线程池工作时间),对"未完成"的节点正常重新执行——同一层里有的节点秒回、有的节点真正重跑,线程池天然处理这种混合情况,不需要额外判断"这层要不要重跑"。
  239 +
  240 +## 8. 可观测性
  241 +
  242 +`_log_event`(`work_graph_orchestrate.py:74`)现有的 `node_start`/`node_completed`/`node_failed`/`quota_limit` 四类事件保持不变,每条事件本身已经带 `node_exec_id`,事件的时间戳(`execution_log.append_log` 底层写入时机)已经足够回答"这次运行到底并发了多少节点"——只要看同一个 `run_id` 下多个 `node_start` 事件的时间戳有没有重叠,不需要新增专门的"分组开始/分组结束"事件类型(呼应需求文档开放问题第 4 条:复用现有事件、靠时间戳重叠反推,不新增事件类型,保持改动面最小)。
  243 +
  244 +## 9. 并发度默认值
  245 +
  246 +`_DEFAULT_MAX_WORKERS = 4`。理由:现在观察到的真实 `graph_manifest` 规模是个位数节点,4 是一个不会让"一次任务同时拉起过多 `claude` 子进程"的保守值,同时足够覆盖当前能想到的 fan-out 场景(比如 Planner 未来 PRD 阶段 3-5 个场景并行)。作为 `run_graph()` 的一个具名参数(而不是硬编码常量或环境变量),调用方可以按场景覆盖,但不新增管理后台配置项(需求文档非目标已声明不做这类配置暴露,YAGNI——等真的有需要按项目/按 Org Agent 类型区分并发度时再加)。
  247 +
  248 +## 10. 测试计划(对应需求文档验收标准 1-7)
  249 +
  250 +| 验收标准 | 测试方式 |
  251 +|---|---|
  252 +| 1. 同层节点确实并发触发 | mock `run_fn`,每次调用记录 `(node_id, 开始时间, 结束时间)` 到共享列表(用锁保护这个记录列表本身,测试代码自己的锁,不是被测代码的),断言同层两个节点的时间区间有重叠 |
  253 +| 2. 单测断言并发+层间顺序 | 同上,另外断言"下一层任意节点的开始时间 >= 上一层全部节点的结束时间"(层屏障) |
  254 +| 3. 断点续跑:已完成节点不重跑 | 第一次调用 mock 让某层其中一个节点抛异常模拟中断(`WorkGraphLayerError` 传播出来),检查 DB 里其余节点确实落库为 `done`;第二次调用同一个 `run_graph()`,断言 `run_fn` 没有被再次调用去跑那些已 `done` 的节点(用调用计数 mock 断言) |
  255 +| 4. SQLite 并发写入无异常 | 用真实 `:memory:`(或临时文件)SQLite 库跑一个多节点并发层,不 mock `conn`,跑多轮(并发问题往往概率复现)确认不抛 `database is locked` |
  256 +| 5. worktree 并发创建互不冲突 | 真实临时 git 仓库,多线程调用 `create_worktree`,断言 `git worktree list` 输出的路径数量和唯一性符合预期 |
  257 +| 6. 配额限制 + 正常完成共存 | mock 一个节点的 `run_fn` 触发 `_UsageLimitSignal` 且配置极短的超时(依赖注入的 `sleep`/`now`,不真的等待),另一个节点正常返回,断言正常节点的输出被保留在最终 `outputs` 里,且整体调用最终正确抛出/传播配额相关信号 |
  258 +| 7. 既有测试兼容性 | 跑 `src/test_work_graph_orchestrate.py`、`src/test_work_graph_execute.py`、`src/test_e2e_pipeline.py` 全部既有用例,逐个确认通过;对断言"严格串行"这一实现细节的用例(如果存在)需要单独识别并更新断言方式(改成断言"层间顺序"而不是"节点间顺序") |
  259 +
  260 +## 11. 实施步骤建议(拆成可独立验证的小步)
  261 +
  262 +1. `topological_layers()` 新增 + `topological_order()` 改写成基于它的薄封装,跑既有测试确认零回归(不改 `run_graph()`,先证明分层算法本身正确)。
  263 +2. 引入 `_LockedConn`,先只在新代码路径里用,不碰现有串行路径。
  264 +3. `create_worktree()` 加锁,写第 10 节测试用例 5 验证是否真的需要(哪怕结论是"其实不需要",锁本身开销极小,可以保留作为防御性设计)。
  265 +4. `run_graph()` 改造成层级调度(第 5 节的完整实现),单节点层走同步路径、多节点层走线程池路径。
  266 +5. 补全第 10 节全部测试用例。
  267 +6. 找一个真实存在多节点并行结构的 `graph_manifest` 场景做一次端到端验证(如果当前还没有这样的真实场景,构造一个人工测试场景即可,不强求等 E 落地后才能验证 G 本身)。
  268 +
  269 +## 12. 开放问题的最终决定
  270 +
  271 +回答需求文档 `docs/work-graph-parallel-execution-requirements.md` §8 列的开放问题:
  272 +
  273 +- **`topological_layers()` 实现方式**:新增函数,`topological_order()` 改写成基于它的薄封装(第 2 节已给出实现)。
  274 +- **`max_workers` 默认值**:4,作为 `run_graph()` 的具名参数,不做管理后台配置项(第 9 节)。
  275 +- **FR-5 异常聚合规则**:层内全部完成后统一收集,抛出携带完整 `[(node_id, exception), ...]` 清单的 `WorkGraphLayerError`(第 5 节),调用方可以按需检查列表内容做进一步判断(比如是否全是可重试的配额问题)。
  276 +- **`execution_event` 是否需要新事件类型**:不需要,复用现有事件+时间戳重叠反推(第 8 节)。
  277 +- **worktree 加锁粒度**:只锁 `create_worktree()` 内部 `git worktree add` 这一条命令,不锁整个节点执行(第 4 节)。
docs/work-graph-parallel-execution-requirements.md renamed to docs/todo/work-graph-parallel-execution-requirements.md
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 **日期**: 2026-08-04(2026-08-05 更新:已确认,标记为核心优先级,进入详细设计阶段) 3 **日期**: 2026-08-04(2026-08-05 更新:已确认,标记为核心优先级,进入详细设计阶段)
4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应对 `docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).diff-with-graphlemon.md` 开放问题第 1 条的讨论——"现在的每节点独立子进程设计,断点续跑收益是否值得放弃 fan-out 并行?能否两者兼得?"。详细设计(SDD 层面:`topological_layers()` 的具体实现、线程池调度器接口、锁的粒度)留到需求确认后单独产出。 4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应对 `docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).diff-with-graphlemon.md` 开放问题第 1 条的讨论——"现在的每节点独立子进程设计,断点续跑收益是否值得放弃 fan-out 并行?能否两者兼得?"。详细设计(SDD 层面:`topological_layers()` 的具体实现、线程池调度器接口、锁的粒度)留到需求确认后单独产出。
5 -**状态**: ✅ 已确认。验收口径固化为一句话(2026-08-05 用户原话):"只要 Work Graph 规划了 fan-out、fan-in 的并行节点,就可以在节点触发的时候并行执行"——这与本文档 FR-1/FR-2 一致,无需改动功能需求本身。下一步进入详细设计阶段,见 `docs/todo/graph-engineering-open-items.md` 条目 G。 5 +**状态**: ✅ 已确认。验收口径固化为一句话(2026-08-05 用户原话):"只要 Work Graph 规划了 fan-out、fan-in 的并行节点,就可以在节点触发的时候并行执行"——这与本文档 FR-1/FR-2 一致,无需改动功能需求本身。详细设计已产出,见 `docs/work-graph-parallel-execution-design.md`。
6 6
7 ## 1. 背景 7 ## 1. 背景
8 8
docs/work-graph-time-travel-debugging-requirements.md renamed to docs/todo/work-graph-time-travel-debugging-requirements.md
@@ -4,14 +4,14 @@ @@ -4,14 +4,14 @@
4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应 `docs/todo/graph-engineering-open-items.md` 条目 D 的确认结果——"time travel 可以立项"。 4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应 `docs/todo/graph-engineering-open-items.md` 条目 D 的确认结果——"time travel 可以立项"。
5 **状态**: 草案,待评审。 5 **状态**: 草案,待评审。
6 6
7 -**命名消歧(仿照 CLAUDE.md「两个 Supervisor」的先例)**:本文档说的"Time-Travel 调试"跟 `docs/supervisor-auto-rollback-design.md` 描述的 Deploy Supervisor 自动回滚是两件完全不同的事——不要混淆: 7 +**命名消歧(仿照 CLAUDE.md「两个 Supervisor」的先例)**:本文档说的"Time-Travel 调试"跟 `docs/done/supervisor-auto-rollback-design.md` 描述的 Deploy Supervisor 自动回滚是两件完全不同的事——不要混淆:
8 8
9 | | Time-Travel 调试(本文档) | Deploy Supervisor 自动回滚 | 9 | | Time-Travel 调试(本文档) | Deploy Supervisor 自动回滚 |
10 |---|---|---| 10 |---|---|---|
11 | 回滚的对象 | 某一次任务的 Work Graph 执行过程(某个 `work_node_execution` 的中间状态) | graphlemon 自身的代码版本(git commit) | 11 | 回滚的对象 | 某一次任务的 Work Graph 执行过程(某个 `work_node_execution` 的中间状态) | graphlemon 自身的代码版本(git commit) |
12 | 触发者 | 人工调试意图("我想看看如果这一步换个输入会怎样") | 自动健康检查失败信号 | 12 | 触发者 | 人工调试意图("我想看看如果这一步换个输入会怎样") | 自动健康检查失败信号 |
13 | 影响范围 | 单个 task 的执行历史,不影响其他任务 | 整个 graphlemon 进程的运行版本 | 13 | 影响范围 | 单个 task 的执行历史,不影响其他任务 | 整个 graphlemon 进程的运行版本 |
14 -| 相关文档 | 本文档 | `docs/supervisor-auto-rollback-design.md` | 14 +| 相关文档 | 本文档 | `docs/done/supervisor-auto-rollback-design.md` |
15 15
16 ## 1. 背景 16 ## 1. 背景
17 17
src/schema.py
@@ -276,7 +276,7 @@ CREATE TABLE log_retention_config ( @@ -276,7 +276,7 @@ CREATE TABLE log_retention_config (
276 updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP 276 updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
277 ); 277 );
278 278
279 --- 1.15 deployment (PH5, U-034, 2026-08-02 -- see docs/supervisor-auto-rollback-design.md) 279 +-- 1.15 deployment (PH5, U-034, 2026-08-02 -- see docs/done/supervisor-auto-rollback-design.md)
280 CREATE TABLE deployment ( 280 CREATE TABLE deployment (
281 deployment_id TEXT PRIMARY KEY, 281 deployment_id TEXT PRIMARY KEY,
282 git_commit_hash TEXT NOT NULL, 282 git_commit_hash TEXT NOT NULL,
supervisor/deploy_supervisor.py
1 """Deploy Supervisor process skeleton (U-036). 1 """Deploy Supervisor process skeleton (U-036).
2 2
3 Independent OS process (started via `python3 -m supervisor.deploy_supervisor`), 3 Independent OS process (started via `python3 -m supervisor.deploy_supervisor`),
4 -pure stdlib, no LLM CLI dependency -- per docs/supervisor-auto-rollback-design.md 4 +pure stdlib, no LLM CLI dependency -- per docs/done/supervisor-auto-rollback-design.md
5 §2's "three independent processes, no shared failure domain" principle: 5 §2's "three independent processes, no shared failure domain" principle:
6 Deploy Supervisor crashing must not affect Live/Candidate, and Live/Candidate 6 Deploy Supervisor crashing must not affect Live/Candidate, and Live/Candidate
7 crashing must not affect Deploy Supervisor's own ability to keep observing 7 crashing must not affect Deploy Supervisor's own ability to keep observing
supervisor/isolation/__init__.py
@@ -3,7 +3,7 @@ Deploy Supervisor (the independent process that handles blue-green deploy + @@ -3,7 +3,7 @@ Deploy Supervisor (the independent process that handles blue-green deploy +
3 automatic rollback for graphlemon's own self-modification -- not to be 3 automatic rollback for graphlemon's own self-modification -- not to be
4 confused with the "Supervisor(调度子 Agent)" role documented in 4 confused with the "Supervisor(调度子 Agent)" role documented in
5 graphlemon_architecture.md, which is an in-process monitoring node inside 5 graphlemon_architecture.md, which is an in-process monitoring node inside
6 -the Coder Work Graph. See docs/supervisor-auto-rollback-design.md for the 6 +the Coder Work Graph. See docs/done/supervisor-auto-rollback-design.md for the
7 disambiguation. 7 disambiguation.
8 8
9 Adapted 2026-07-30 from graphloop (`/Users/yibo/Documents/lemonAI/graphloop`, 9 Adapted 2026-07-30 from graphloop (`/Users/yibo/Documents/lemonAI/graphloop`,
@@ -16,8 +16,8 @@ source workspace instead of the isolated copy; `change_manifest`&#39;s @@ -16,8 +16,8 @@ source workspace instead of the isolated copy; `change_manifest`&#39;s
16 symlink-rejecting isolated-tree snapshot plus the caller's manifest check 16 symlink-rejecting isolated-tree snapshot plus the caller's manifest check
17 caught it before anything false was reported as evidence). 17 caught it before anything false was reported as evidence).
18 18
19 -See docs/supervisor-design-comparison.md and  
20 -docs/supervisor-auto-rollback-design.md for why this code lives here and how 19 +See docs/done/supervisor-design-comparison.md and
  20 +docs/done/supervisor-auto-rollback-design.md for why this code lives here and how
21 it plugs into the blue-green deploy/rollback state machine. 21 it plugs into the blue-green deploy/rollback state machine.
22 22
23 Module map (unchanged from graphloop): 23 Module map (unchanged from graphloop):