diff --git a/.claude/agent-memory/planner/MEMORY.md b/.claude/agent-memory/planner/MEMORY.md
index c587ff5..315b793 100644
--- a/.claude/agent-memory/planner/MEMORY.md
+++ b/.claude/agent-memory/planner/MEMORY.md
@@ -2,4 +2,5 @@
- [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
- [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
- [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
-- [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
+- [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
+- [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
diff --git a/.claude/agent-memory/planner/project_artifact_overview_click_through_bug.md b/.claude/agent-memory/planner/project_artifact_overview_click_through_bug.md
index 9e8b2ac..66e860b 100644
--- a/.claude/agent-memory/planner/project_artifact_overview_click_through_bug.md
+++ b/.claude/agent-memory/planner/project_artifact_overview_click_through_bug.md
@@ -1,13 +1,21 @@
---
name: project-artifact-overview-click-through-bug
-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
+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."
metadata:
type: project
---
-Investigated 2026-08-04 for the "Planner 产出总览面板列表项点击无反应" bug-fix task
-(graph_manifest planned same session). Root cause is real and narrower than it first
-looks — three separate gaps, not one:
+**RESOLVED as of 2026-08-06** (re-verified by reading `frontend/app.js` directly while
+investigating the unrelated Org Graph click-through bug, see [[project-org-strip-click-through-bug]]):
+all three gaps below have been fixed. `renderArtifactSummaryList`/`renderArtifactSummaryList`'s
+`
` now carries `data-artifact-id` (app.js:1360), `ARTIFACT_DETAIL_RE` (`#/artifacts/:id`) is a
+real dynamic route wired in `render()` (app.js:99,125-129), and `mountArtifactBrowser(initialArtifactId)`
+(app.js:1603) seeds `selectedArtifactId` from it and calls the existing `showPreview()` flow. Don't
+re-flag this as open without re-reading the file first — but do keep re-verifying, per CLAUDE.md's
+general warning that autonomous self-hosting runs can fix or re-break things between sessions.
+
+**Original investigation (2026-08-04)**, kept for history — root cause was real and narrower than
+it first looked, three separate gaps, not one:
1. `renderArtifactSummaryList()` (frontend/app.js:1344) renders bare `` with no
`data-artifact-id` and no click handler — confirmed, matches the task's stated root cause.
diff --git a/.claude/agent-memory/planner/project_loop_round_bug_already_fixed.md b/.claude/agent-memory/planner/project_loop_round_bug_already_fixed.md
index 221590f..67c0980 100644
--- a/.claude/agent-memory/planner/project_loop_round_bug_already_fixed.md
+++ b/.claude/agent-memory/planner/project_loop_round_bug_already_fixed.md
@@ -17,7 +17,7 @@ i.e. a follow-up request to "add a one-line clarifying comment above `_LOOP_LIMI
REQ-05/REQ-09" is *also* already satisfied verbatim in substance. Two independent asks about this
same three-line region of the file have both turned out to be already-done on inspection.
-**Why:** `docs/self-development-feasibility.md` §"场景" uses "fix the loop_round off-by-one
+**Why:** `docs/done/self-development-feasibility.md` §"场景" uses "fix the loop_round off-by-one
(`loop_round > 3` should be `loop_round >= 3`)" as the canonical illustrative example goal for
graphlemon's self-hosting/self-modification demo, and this exact string is hardcoded as the goal
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 —
was written already-correct.
**How to apply:** If asked again to plan/fix/investigate this specific goal text (or anything
-that cites `docs/self-development-feasibility.md`'s example scenario), re-verify current state of
+that cites `docs/done/self-development-feasibility.md`'s example scenario), re-verify current state of
`src/org_loop_route.py` and `src/test_org_loop_route.py` first rather than assuming the bug is
still open — it may have been fixed between conversations, or the request may actually be testing
the *planning process* (e.g. via the self-hosting E2E harness) rather than asking for a real code
diff --git a/.claude/agent-memory/planner/project_org_strip_click_through_bug.md b/.claude/agent-memory/planner/project_org_strip_click_through_bug.md
new file mode 100644
index 0000000..d323796
--- /dev/null
+++ b/.claude/agent-memory/planner/project_org_strip_click_through_bug.md
@@ -0,0 +1,51 @@
+---
+name: project-org-strip-click-through-bug
+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.
+metadata:
+ type: project
+---
+
+Investigated 2026-08-06 for "控制台 Org Graph 三节点条点击没有反应" (graph_manifest planned same
+session). The stated root cause (`renderOrgStrip()` renders bare divs, no `data-*`, no click, no
+`cursor:pointer`) is real, but it is **one of three separate render sites with the same gap**, not
+the only one — don't scope the fix to just `renderOrgStrip`:
+
+1. `renderOrgStrip()` (frontend/app.js:589-598, mounted in `mountDashboard`, `id="org_strip"`,
+ under the "Org Graph 三节点状态" heading in `v_dashboard`). Confirmed: no `data-*`, no listener.
+2. `v_task_detail`'s inline org-strip (app.js:1085-1088, inside `renderDetail()`, under an actual
+ `Org Graph
` heading) — same `.org-strip`/`.org-node` markup pattern, same gap, built
+ inline rather than via a shared function. If `renderOrgStrip` gets a shared click-handling
+ helper, this call site needs the same wiring, not a copy-paste duplicate.
+3. `renderOrgGraphTopology()` (app.js:1322-1347, used only by `#/graph`'s `mountGraphConsole`) is a
+ *different, newer* implementation (`.graph-topo-node`, not `.org-strip`/`.org-node` — added
+ 2026-08-03 as `v_graph_console` FR-1, explicitly "not org-strip" per the comment at app.js:1314)
+ that already shows per-node `status-*` classes and an active/retry-loop edge, but its 3 nodes
+ are *also* not clickable — only the Work Graph sub-nodes inside it are (`mountWorkNodeClickExpand`,
+ wired via `renderGraphVisualizerPanel(run, { enableNodeClick: true })`, which already has real
+ click → `renderLogViewerHtml`/`mountLogViewer` wiring, confirmed working).
+
+**What already exists and must be reused, not rebuilt** (confirmed by reading the code, not by
+trusting the task's framing): `renderGraphVisualizerPanel(run, {enableNodeClick:true})` +
+`mountWorkNodeClickExpand` + `renderLogViewerHtml`/`mountLogViewer` (real WS live-tail +
+`c_execution_log_get` static fallback, `frontend/app.js:942-1016`) is the exact combo `#/graph`
+already uses per-task. `mountArtifactsOverviewPanel`'s doc/code lists already link through to
+`#/artifacts/:artifact_id` (`v_artifact_browser`, `showPreview()`), see
+[[project-artifact-overview-click-through-bug]] (now resolved) for that mechanism's shape.
+
+**Backend note**: no new endpoint needed. `GET /api/tasks/:task_id` (`task_query.get_task`,
+`src/task_query.py:115-171`) already returns `work_graph_runs` (each with `org_node`, `run_id`,
+`status`, `nodes`) ordered by `started_at` ASC, and `artifacts` (each with `produced_by_run_id`
+linking back to a `work_graph_run.run_id`) ordered by `created_at` ASC. "Latest work_graph_run for
+org_node X" = filter `work_graph_runs` by `org_node === X` and take the last (loop retries can
+produce more than one run per org_node, e.g. `full_replan` re-triggers planner) — this is pure
+frontend filtering of data already on the wire, not a new query.
+
+**CSS**: `.org-node`/`.graph-topo-node` (`frontend/style.css:42-47,127-139`) have no `cursor:pointer`
+today, unlike `.work-node` (style.css:104, already clickable) and `ul.artifact-list li` (style.css:156).
+
+**How to apply**: when writing the SDD, scope the fix as one shared click-handling mechanism
+(scoped to the *current task* being viewed, not global across tasks) applied at all 2-3 render
+sites that actually ship on this pass, composing already-existing panels (DAG + log viewer +
+artifact link-through) rather than inventing a 4th display mechanism. Decide and record explicitly
+whether `renderOrgGraphTopology`'s topology nodes are in scope for this task (they're a newer,
+separate implementation with the same gap) or deferred — don't silently skip them without a note.
diff --git a/.claude/agent-memory/validator/project_self_hosting_loop_round_probe.md b/.claude/agent-memory/validator/project_self_hosting_loop_round_probe.md
index 6248861..3a4cb68 100644
--- a/.claude/agent-memory/validator/project_self_hosting_loop_round_probe.md
+++ b/.claude/agent-memory/validator/project_self_hosting_loop_round_probe.md
@@ -9,7 +9,7 @@ Task `14fd9ab6-e2c8-4233-8381-002419de85bb` ("fix loop_round > 3 should be >= 3
p_org_task_lifecycle") turned out to be a **no-op investigation task**: `src/org_loop_route.py`
has used `loop_round >= _LOOP_LIMIT` (limit=3) since the very first commit that introduced the
file (`59749fc`, U-016) — it was never `> 3`. The `> 3` phrasing only appears in
-`docs/self-development-feasibility.md:50`, an explicitly-labeled **hypothetical illustration**
+`docs/done/self-development-feasibility.md:50`, an explicitly-labeled **hypothetical illustration**
in §3 "模拟走一遍:修复自己状态机里的一个边界 bug" (a PH5 self-hosting feasibility walkthrough,
not a real bug report). I independently re-derived this from `git log -p --follow` on the file
and by running `python3 -m unittest src.test_org_loop_route -v` (11/11 pass) — confirmed the
@@ -17,7 +17,7 @@ Planner/Coder's claim rather than trusting it.
**Why this matters:** this repo (graphlemon) sometimes runs tasks *against its own source tree*
(self-hosting/self-modification, see CLAUDE.md's "Two things named Supervisor" section and
-`docs/self-development-feasibility.md`). PH5 (actual self-hosting execution, U-034~U-043) is
+`docs/done/self-development-feasibility.md`). PH5 (actual self-hosting execution, U-034~U-043) is
explicitly **not in scope** per CLAUDE.md yet — but *investigation-only* self-referential tasks
like this one (verify graphlemon's own code against its own spec) apparently are being run ahead
of PH5, and "no code change, verified no defect" is a legitimate, complete outcome for such a
diff --git a/CLAUDE.md b/CLAUDE.md
index 6e0a36f..be5c3e1 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -131,7 +131,7 @@ python3 -m src.backend_server --port 8000 # default --port 0 means OS-assigned
(U-034~U-043): `deploy_stage.py` (stage a candidate), `deploy_health_check.py` (poll for
startup readiness, not one-shot — see U-038 fix), `deploy_cutover.py`, `deploy_monitor.py`,
`deploy_rollback.py`. Fail-closed, never-auto-restart philosophy adapted (not imported) from
- graphloop's `hermes_watchdog.py`. See `docs/supervisor-auto-rollback-design.md` for the full
+ graphloop's `hermes_watchdog.py`. See `docs/done/supervisor-auto-rollback-design.md` for the full
design and the disambiguation note at the top of that file.
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).
worth reading for architecture patterns; its `hermes_watchdog.py`/`phase10_controller.py` are
worth reading for the "fail-closed, deterministic, never-auto-restart" supervision philosophy
(graphlemon's Deploy Supervisor deliberately extends this into auto-rollback rather than just
-auto-halt — see `docs/supervisor-design-comparison.md` for why). Do not import graphloop code
+auto-halt — see `docs/done/supervisor-design-comparison.md` for why). Do not import graphloop code
directly; anything worth reusing has already been copied and adapted into this repo
(`supervisor/isolation/`, adapted verbatim per the U-036 note in `development-plan.md`).
diff --git a/docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md b/docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md
deleted file mode 100644
index 104bd30..0000000
--- a/docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md
+++ /dev/null
@@ -1,844 +0,0 @@
-# Graph Engineering with Claude: 14\-Step roadmap from 0 to graph architect \(Full Course\)
-
-
-
-img\_placeholder
-
-
-
-大多数人尝试构建多步智能体时,最终得到的都是一条直线。第一步、第二步、第三步——每一步都乖乖地等上一步做完才开始。
-
-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\.
-
-
-
-十分之九的人会发现,其中一半的步骤根本不需要等待。
-
-**9/10 notice that half those steps never needed to wait at all\.**
-
-
-
-它们不路由,不分支,不并行。它们只是排队——一个脑袋,一个上下文,一次只做一件事——直到窗口被填满,智能体忘记自己在做什么。
-
-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\.
-
-
-
-> Follow my Substack to get fresh AI alpha:
->
-> [movez\.substack\.com](https://movez.substack.com/)
->
->
->
->
-
-这套十四步执行路线图,能把单文件单行式简单任务拆解成完整任务图谱:该图谱可分发至海量智能体集群并行运算、自主校验全部检索结果,最终收敛输出一套单智能体完全无法承载处理规模的完整结论。
-
-***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\.
-
-
-
-
-
-这里有一个没人明说的转变:prompt提示词是一句话,loop循环是一个周期,harness框架是智能体脚下的地板。
-
-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\.
-
-
-
-但工作本身的形状——什么在什么之前运行、什么可以同时运行、什么必须等待其他所有东西——这个形状就是一个图。节点负责思考,边负责传递结果。
-
-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\.
-
-
-
-Claude Code 已经内置了直接构建这些图的工具:动态工作流。
-
-Claude Code shipped the tooling to build these graphs directly: **dynamic workflows**\.
-
-
-
-Claude 编写一个普通的 JavaScript 编排脚本,然后生成一个协调的子智能体舰队来执行它——而协调本身不消耗任何模型 token,因为它是代码,不是对话。
-
-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\.
-
-
-
-
-
-## Nodes are jobs\. Edges are what *flows\.*
-
-\#\# 节点代表工作,边代表流转关系
-
-
-
-一个图只有两样东西(节点\+边 Nodes\+Edges),把它们搞清楚就能解决大部分困惑。
-
-**节点**是一个工作单元——一个智能体、一个有界任务、一个输入一个输出。
-
-A graph has exactly two things, and getting them straight fixes most of the confusion\.
-
-A **node** is a unit of work \- one agent, one bounded job, one input in and one output out\.
-
-
-
-**边**是一个依赖关系:含义是这个节点的输出作为另一个节点的输入。仅此而已。
-
-An **edge** is a dependency: it says *this node’s output feeds that node’s input\.* Nothing more\.
-
-
-
-常见的错误是把「然后」当作边。「总结文件然后告诉我天气」——这两者之间没有边,天气不消费摘要的数据。
-
-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\.
-
-
-
-那是两个不相连的节点,被线性脚本不必要地串联在一起。边只存在于数据真正跨越它的时候。
-
-That’s two disconnected nodes that a linear script needlessly chains\. The edge only exists when data actually moves across it\.
-
-
-
-学会对你的智能体中每个「然后」提问:下一步是否读取上一步的输出?如果不是,就没有边,等待是浪费的。
-
-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\.
-
-
-
-python
-
-1\. 用方框和箭头画出来。方框是一个 agent\(\) 调用,箭头是一个变量从一个调用的返回值传到另一个的提示词里。如果你画不出箭头——如果没有变量跨越——两个方框就是独立的,而独立性是你在本课程剩余部分要利用的东西。
-
-```Python
-Draw it as boxes and arrows. A box is an agent() call.
-An arrow is a variable passed from one call’s return into another’s
-prompt. If you can’t draw the arrow - if no variable crosses - the two
-boxes are independent, and independence is the thing you’ll exploit
-for the rest of this course.
-```
-
-
-
-
-
-## Your linear script is a *degenerate* graph
-
-\#\# 你的线性脚本是一个退化的图
-
-
-
-当你把智能体写成「先做A,再做B,再做C,再做D」时,你已经画了一个图——一条不分叉的单链。每个节点恰好有一条边进、一条边出。
-
-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\.
-
-
-
-它能正确运行,但也运行得又慢又脆,因为链条没有冗余:如果C卡住了,D永远不会执行,A的工作被困在上游无处可去。
-
-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\.
-
-
-
-
-
-图工程的第一项真正技能是重画这条链。拿起你的线性智能体,对每条箭头问第一步的问题。
-
-The first real skill of graph engineering is **redrawing the chain**\. Take your linear agent and, for each arrow, ask the Step 1 question\.
-
-
-
-大多数链条有两三条不携带数据的箭头——它们只是你碰巧输入东西的顺序。
-
-Most chains have two or three arrows that don’t carry data \- they’re just the order you happened to type things in\.
-
-
-
-剪掉这些箭头,链条就坍缩成更宽的东西:几个可以同时运行的独立节点,喂给一个需要它们全部的单一节点。
-
-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\.
-
-
-
-
-
-## Give every node a *contract*
-
-\#\# 给每个节点一个契约
-
-一个你无法推理的节点就是一个你无法并行的节点。解决方案是契约:有界输入、有界输出、恰好一个任务。
-
-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\.**
-
-
-
-输入是节点读取的任何东西——显式传入,永远不从共享窗口假设。输出是一个定义好的形状,最好经过验证,这样下一个节点可以无猜测地消费它。
-
-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\.
-
-
-
-在工作流中,这个契约用 schema 强制执行。当你给 Claude 一个带 JSON schema 的 agent\(\) 调用时,Claude 生成的子智能体被迫返回验证过的结构化数据——验证发生在工具调用层,所以 Claude 在不匹配时重试,而不是给你一段需要解析和祈祷的自由文本。
-
-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\.
-
-
-
-这就是一个 Claude 能接入图的节点和一个只有人类读输出才能工作的节点之间的区别。
-
-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\.
-
-
-
-python
-
-// 真正有契约的节点:有界输入,验证输出,单一任务。
-
-```Python
-// A node with a real contract: bounded in, validated out, one job.
-const ITEM = {
- type: 'object', additionalProperties: false,
- properties: {
- title: { type: 'string' },
- url: { type: 'string' },
- impact: { type: 'string', enum: ['high', 'medium', 'low'] },
- },
- required: ['title', 'url', 'impact'],
-};
-const result = await agent(source.prompt, {
- label: `research:${source.key}`,
- schema: ITEM, // forces validated structured output
- agentType: 'general-purpose',
-});
-// result is now a shape the next node can trust — not free text.
-```
-
-
-
-
-
-## 04\. Treat the edge as a *data contract*
-
-\#\# 04\. 把边当作数据契约
-
-
-
-边不只是简单表示 “任务 A 执行完再跑任务 B”,它更是一份数据传输约定:A 输出固定格式的数据结构,而 B 的设计本身就适配读取该结构。如果以传输的数据(而非执行先后顺序)来命名边,两件事会变得清晰易懂。
-
-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\.
-
-
-
-
-
-你可以一眼看出边是否真实(数据真的在移动吗?),而且只要形状保持不变,你可以替换任一端的节点而不破坏图。
-
-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\.
-
-
-
-实际实现中,边由原生 JavaScript 编写。在任务分发与结果汇总之间的归约步骤(扁平化、去重、过滤),本质就是一段处理各节点输出数据结构的代码。
-
-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\.
-
-
-
-全程无需智能体参与。基于图谱架构设计有一项不易察觉的显著优势:大量原本需要消耗大模型算力令牌处理的逻辑,本质只是一条数据流边,而数据流边完全零算力开销。
-
-**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\.
-
-
-
-python
-
-人们很容易下意识新建智能体来 “合并多份结果”,但一定要避免这种做法。 如果合并操作仅需扁平化、去重,直接用 `results.flatMap(...)` 搭配集合(Set)即可;运算结果确定、瞬时完成,完全不消耗模型 Token。 智能体应当留给需要主观判断的场景,而非单纯的数据流转处理。 倘若图谱里每条数据边都依靠智能体中转,相当于为自身的基础数据流链路持续付出不必要的算力成本。
-
-```Python
-The temptation is to spawn an agent to “combine the results.” Resist
-it. If combining means flatten-and-dedupe, that’s results.flatMap(...)
-and a Set — deterministic, instant, zero tokens. Save agents for
-judgment, not for plumbing. A graph where every edge is an agent is a
-graph paying rent on its own wiring.
-```
-
-
-
-
-
-## Fan out with *parallel\(\)*
-
-\#\# 用 parallel\(\) 实现任务并行分发
-
-
-
-这是能大幅压缩整体资源开销的核心设计思路。当存在 N 个相互独立节点(例如 N 份待核查数据源、N 个待评审文件、N 条待审计路由)时,切勿采用串行链式执行方案。
-
-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\.
-
-
-
-你告诉 Claude 把它们fan\-out(任务分解分发)并同时运行。在工作流中这就是 parallel\(\):Claude 接收一个延迟执行函数 thunk 数组,为每个 thunk 单独生成一个子智能体,全部并发执行,然后把结果数组返回给你。
-
-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\.
-
-
-
-两个细节让它稳健。第一,parallel\(\) 是一个屏障——它等所有 thunk 完成才返回,所以下一阶段看到的是完整集合。第二,抛异常的 thunk 解析为 null 而不是拒绝整个批次,所以一个不稳定的智能体不会搞砸整个运行。
-
-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\.
-
-
-
-总是对结果做 \.filter\(Boolean\)。并发量受限于你的核心数,多余的会排队,所以你可以传一百个 thunk 它们都会完成——只是每次跑一小批。
-
-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\.
-
-
-
-python
-
-1\. phase\('Research'\);
-// 九个来源,九个智能体,全部同时。
-const raw = await parallel\(
- SOURCES\.map\(\(s\) =\> \(\) =\>
- agent\(s\.prompt, \{
- label: \`research:$\{s\.key\}\`,
- phase: 'Research',
- schema: ITEM\_SCHEMA, // 每个节点返回验证过的 JSON
- agentType: 'general\-purpose',
- \}\),
- \),
-\);
-const collected = raw\.filter\(Boolean\); // 丢掉失败智能体的 null
-
-```Python
-phase('Research');
-// Nine sources, nine agents, all at once.
-const raw = await parallel(
- SOURCES.map((s) => () =>
- agent(s.prompt, {
- label: `research:${s.key}`,
- phase: 'Research',
- schema: ITEM_SCHEMA, // each node returns validated JSON
- agentType: 'general-purpose',
- }),
- ),
-);
-const collected = raw.filter(Boolean); // drop the nulls from failed agents
-```
-
-fan\-out(分发并行执行)存在于 Claude 写的代码中,不在模型对话中。Claude 自己的上下文从不同时持有九个来源——每个子智能体携带自己的上下文,只有最终答案回来。
-
-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\.
-
-
-
-这就是让 Claude 把工作流扩展到几十或几百个子智能体而不淹没会话的原因。编排层不消耗 token,因为它不是 Claude 思考的另一轮。
-
-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\.
-
-
-
-
-
-## Fan in at a *barrier*
-
-\#\# 基于屏障节点完成结果扇入聚合
-
-Fan\-in :结果聚合,汇总归约
-
-
-
-fan\-out只有在有东西收集它时才有用。fin\-in是边汇聚的节点——一个智能体(或一段代码)同时看到所有上游结果,并做需要整个集合才能做的事:跨来源去重、按影响排序、总量为空时提前退出。这是屏障值得其墙钟成本的唯一地方。
-
-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\.
-
-
-
-保障任务图谱高效运行的核心准则:
-
-仅当某一阶段确实需要集齐所有前置任务结果时,才使用屏障同步机制。需要对全部数据源统一去重?那就启用屏障 —— 这么做是合理的。
-
-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\.
-
-
-
-python
-
-1\. // 边:纯 JS,无智能体,零 token。
-const flat = collected\.flatMap\(\(c\) =\> c\.items\);
-log\(\`Collected $\{flat\.length\} items\`\);
-phase\('Curate'\);
-// 屏障节点:需要整个集合来去重\+排序。
-const curated = await agent\(
- \`Dedupe and rank these by impact:\\n$\{JSON\.stringify\(flat\)\}\`,
- \{ phase: 'Curate', schema: CURATED\_SCHEMA \},
-\);
-
-```Python
-// The edge: plain JS, no agent, zero tokens.
-const flat = collected.flatMap((c) => c.items);
-log(`Collected ${flat.length} items`);
-phase('Curate');
-// The barrier node: needs the WHOLE set to dedupe + rank.
-const curated = await agent(
- `Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,
- { phase: 'Curate', schema: CURATED_SCHEMA },
-);
-```
-
-
-
-如果只是单纯对列表做扁平化处理?直接依托数据流边内联处理即可。 有一套直白严苛的判断标准:倘若你的代码写成「并行分发 → 数据转换 → 再次并行分发」,且中间转换步骤不存在多条数据之间的交叉依赖,那本该改用流式管道处理,完全省去屏障同步。
-
-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\.
-
-
-
-
-
-## The *diamond*: split → work → merge
-
-\#\# 菱形执行模型:拆分任务 → 并行处理 → 汇总合并
-
-
-
-把fan\-out和fan\-in放在一起,你就得到了每个严肃智能体图的主力拓扑:菱形执行模型。
-
-Put fan\-out and fan\-in together and you get the workhorse topology of every serious agent graph: the **diamond**\.
-
-
-
-单个节点负责任务拆分,大量节点并行执行处理,再由单个节点汇总合并。市场信息巡检、依赖项审计、代码评审、深度调研报告等场景底层都采用这套拓扑结构;只需替换数据源与提示词,同一套基础框架就能适配各类业务需求。
-
-
-
-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\.
-
-
-
-这套标准范式有个值得牢记的命名:扇出分发 → 归约处理 → 汇总生成。 扇出阶段拓宽信息覆盖范围;依靠原生代码做归约,精简海量原始数据;最后交由终端智能体整合输出完整结论。
-
-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\.
-
-
-
-一旦你看到了菱形,你就不再问「如何让我的智能体做更多步骤」,而是开始问「拆分在哪里,合并在哪里」——这才是真正能扩展的问题。
-
-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\.
-
-
-
-
-
-## Route the edge at runtime with a *conditional*
-
-\#\# 通过条件分支在运行时动态路由数据流边
-
-
-
-并非所有任务图谱都是静态固定的。有时要走哪条数据流边,取决于上游节点的输出结果。路由节点会解析返回数据,判定启用哪一条下游执行分支:比如先对工单分类,再分流至对应的处理程序;或是先判断代码变更体量,再选择快速简易评审,或是启动完整全量审计。
-
-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\.
-
-
-
-在工作流中这只是对节点验证输出的 JavaScript if 或 switch,因为控制流存在于代码中。
-
-In a workflow this is just a JavaScript if or switch on a node’s validated output, because control flow lives in code\.
-
-
-
-这里确定性成了特性而非限制。路由器的决策可以是 Claude 驱动的(子智能体分类),但路由是 Claude 写的代码——所以对相同分类每次都以相同方式运行。
-
-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\.
-
-
-
-
-
-节点环节依托 Claude 的大模型判断能力,数据流边环节依靠脚本代码稳定可靠。不会出现意外情况 —— 比如莫名其妙触发 “Claude 自行跳过审计流程”,因为跳过流程的逻辑必须显式定义在任务图谱中,而当前图谱并未配置该分支。
-
-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\.
-
-
-
-python
-
-1\. // 路由节点:智能体分类,代码选边。
-const \{ severity \} = await agent\(
- \`Classify this diff's risk:\\n$\{diff\}\`,
- \{ schema: \{ type: 'object',
- properties: \{ severity: \{ enum: \['low', 'high'\] \} \},
- required: \['severity'\] \} \},
-\);
-let review;
-if \(severity === 'high'\) \{
- // 重路径:全面并行审计
- review = await parallel\(FILES\.map\(\(f\) =\> \(\) =\> agent\(\`Audit $\{f\}\`\)\)\);
-\} else \{
- // 轻路径:一次快速审查
- review = await agent\(\`Quick review of $\{diff\}\`\);
-\}
-
-```Python
-// Router node: an agent classifies, code picks the edge.
-const { severity } = await agent(
- `Classify this diff's risk:\n${diff}`,
- { schema: { type: 'object',
- properties: { severity: { enum: ['low', 'high'] } },
- required: ['severity'] } },
-);
-let review;
-if (severity === 'high') {
- // heavy path: full parallel audit
- review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));
-} else {
- // light path: one quick pass
- review = await agent(`Quick review of ${diff}`);
-}
-```
-
-
-
-
-
-## Put a *verifier* on the edge
-
-\#\# 在数据流边配置放验证器
-
-
-
-任务图谱真正的核心优势,不在于创建更多智能体,而在于你能搭建一套标准化流程框架包裹智能体,让输出结果具备可信赖度。
-
-The real leverage of a graph isn’t more agents \- it’s the structure you can wrap around them to produce confidence\.
-
-
-
-校验节点部署在数据流边上,数据放行至下游前会先经过它,它的唯一作用就是筛除无效检测结果。数据校验通过才可继续流转;一旦校验失败,该结果就不会参与最终结论的生成。
-
-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\.
-
-
-
-
-
-有三种模式值得掌握。
-
-Three patterns are worth having in your hands\.
-
-对抗式校验:针对每一条检测结论,启动 N 个独立质疑智能体,指令其逐条反驳该结论;仅当多数质疑校验均未推翻结论时,才保留这条结果。
-
-多视角差异化校验:为每个校验器分配专属评判维度 —— 逻辑正确性、安全风险、问题可复现性;多元视角能捕捉到批量相同校验无法发现的各类失效场景。
-
-评审专家组机制:从不同角度生成 N 套解决方案,并行交由多名评审打分;以最优方案为基底汇总输出,同时吸纳其他备选方案中的优质内容。
-
-- **Adversarial verify:** for each finding, spawn N independent skeptics prompted to refute it; keep it only if a majority survive\.
-
-- **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\.
-
-- **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\.
-
-这正是让一个真实团队移植 Bun 运行时并把对抗性代码审查内建到循环中的模式。
-
-This is exactly the pattern that let a real team port the Bun runtime with adversarial code review baked into the loop\.
-
-
-
-
-
-## Isolate nodes so one failure can’t *poison* the graph
-
-\#\# 节点隔离,避免单点故障污染整个任务图谱
-
-###
-
-串行链路中故障会连锁扩散:若节点 C 执行失败,节点 D 将无法启动,整条流程直接中断。而在任务图谱架构里,故障应当被限制在发生问题的单个节点内。
-
-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\.**
-
-
-
-这一点其实已经部分实现:在 `parallel()` 内部,若某个延迟执行函数抛出异常,其结果会解析为 `null`;即便有一个子代理执行失败,其余八个正常的子代理仍能正常返回数据。调用 `.filter(Boolean)` 过滤空值,就是实现故障隔离的手段。
-
-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\.
-
-
-
-设计每一处扇入聚合逻辑时,都要兼容缺失输入数据,不要默认所有上游结果都齐全。
-
-Design every fan\-in to tolerate missing inputs rather than assume a full set\.
-
-
-
-更微妙的失败是节点互相踩踏。当智能体并行写文件时,它们可能冲突。
-
-The subtler failure is nodes stepping on each other\. When agents write files in parallel, they can collide\.
-
-
-
-解决方案是隔离:worktree——每个智能体在自己的 git worktree 中运行,在沙箱中做工作,然后干净地合并。
-
-The fix is isolation: "worktree" \- each agent runs in its own git worktree, does its work in a sandbox, and merges cleanly\.
-
-
-
-仅当多个节点确实并行执行时,才启用屏障同步机制。屏障是专为并行拓扑配套的安全防护措施,并非所有流程运行都必须承担的固定性能开销。
-
-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\.
-
-
-
-
-
-## Add a cycle \- but make it *converge*
-
-\#\# 加一个循环——但要让它收敛
-
-
-
-有时你直到开始才知道工作有多大:未知规模的探索、发现一个bug会暴露三个更多的bug扫描。这需要循环——一条回到更早节点的受控边。
-
-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\.
-
-
-
-危险很明显:不收敛的循环是无限循环,会生成智能体直到你的预算耗尽。
-
-The danger is obvious: a cycle that doesn’t converge is an infinite loop that spawns agents until your budget is gone\.
-
-
-
-这套收敛模式叫做「循环直至无新增结果」:持续启动检索探查节点,连续 K 轮都不再产出新内容时,才终止循环。决定该模式成败的关键细节 —— 也是绝大多数人初次实现都会踩的坑 —— 是去重比对所依据的基准对象
-
-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\.
-
-
-
-去重校验要和所有已检索到的条目做比对,不能只拿已确认通过的结果去重。否则被校验驳回的无效结论会每一轮重复出现,循环永远无法收敛;最终你的系统会持续消耗算力,反复挖掘完全相同的无效死胡同。
-
-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\.
-
-
-
-```Plain Text
-const seen = new Set(); const confirmed = []; let dry = 0;
-
-while (dry < 2) { // stop after 2 empty rounds
- const found = (await parallel(
- FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))
- )).filter(Boolean).flatMap((r) => r.bugs);
-
- const fresh = found.filter((b) => !seen.has(key(b)));
- if (!fresh.length) { dry++; continue; } // nothing new → toward dry
- dry = 0;
- fresh.forEach((b) => seen.add(key(b))); // dedupe vs SEEN, not confirmed
-
- // diverse-lens verify each fresh finding before it counts
- const judged = await parallel(fresh.map((b) => () =>
- parallel(['correctness', 'security', 'repro'].map((lens) => () =>
- agent(`Judge "${b.desc}" via ${lens} — real?`, { schema: VERDICT })))
- .then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));
-
- confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));
-}
-```
-
-
-
-
-
-## Tier the models across the *nodes*
-
-\#\# 按节点职能分层选用梯度规格的大模型
-
-##
-
-不是每个节点都需要你最好的模型。图让这变得明显,而单个智能体永远不会:有些节点是有界且重复的(提取这个字段、分类这个工单),有些承载真正的判断(综合报告、裁决发现)。
-
-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\)\.
-
-
-
-无聊的节点用更便宜的模型跑,把你的昂贵 token 花在判断真正存在的地方。
-
-Run the boring nodes on a cheaper model and spend your expensive tokens where judgment actually lives\.
-
-
-
-在工作流中,Claude 生成的每个子智能体默认继承你的会话模型,除非脚本覆盖它——所以默认情况下一次大运行全部按你的会话层级计费。单个 agent\(\) 调用上的 model 选项告诉 Claude 只把那个节点路由到别处。
-
-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\.
-
-
-
-大运行前检查 /model,然后让 Claude 把扇出的重复节点路由到更便宜的模型,把合并节点留在高端。这就是把一个吃 token 的图从昂贵变成经济的杠杆,不需要改变它的形状。
-
-**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\.
-
-
-
-
-
-## Topology *is* your cost and latency
-
-\#\# 拓扑就是你的成本和延迟
-
-
-
-任务图谱的拓扑结构绝非锦上添花的表面设计,而是直接影响实际运行耗时的核心关键变量。绝大多数人都会踩坑的一个选择:`parallel()`(并行)与 `pipeline()`(流式管道)。`parallel()` 依赖屏障机制,所有分支必须等待最慢的节点执行完毕,下一阶段才能启动。
-
-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\.
-
-
-
-`pipeline()` 以流式方式让每条数据独立走完全流程,全程无屏障阻塞:条目 A 处理到第三阶段时,条目 B 可能还停留在第一阶段。处理速度快的数据会提前完成,不会卡在慢速任务后方空等。
-
-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\.
-
-
-
-
-
-默认采用流式管道 `pipeline()`;仅当某一阶段确实需要一次性拿到上游全部结果时,才使用屏障机制 —— 例如全量数据去重、基于全部统计结果提前终止流程、需要对比多条检索结论的综合提示词场景。 “代码写起来更整洁”“各阶段逻辑划分清晰” 都不足以作为启用屏障的理由;屏障带来的延迟是真实、可量化、纯粹浪费的耗时。逻辑分层清晰,不等于必须同步阻塞等待所有任务。
-
-**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\.
-
-
-
-
-
-## Let Claude draw the graph \- *self\-routing*
-
-\#\# 交由 Claude 自主生成任务图谱:自路由机制
-
-
-
-最后一种方案:对于无法预先规划流程的任务,不再手动编写固化的任务图谱。
-
-The final move is to stop drawing the graph by hand for jobs you can’t plan in advance\.
-
-
-
-借助动态工作流,你只需描述最终目标,Claude 会自行生成任务编排脚本:拆解整体任务、选择并行分发策略、调度一组协同工作的子智能体,最后整合输出结论。系统会生成适配本次任务专属的流程拓扑,而非一套寄希望于能适配所有场景的固化图谱。
-
-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\.
-
-
-
-一共有三种启用动态工作流的方式:
-
-第一种,在提示词中直接写明 “workflow(工作流)”,Claude 会为当前任务自动生成一套流程;
-
-第二种,调用已保存或打包好的预置工作流 ——`/deep-research` 就是一套已投产上线的标准任务图谱,完整链路为:范围界定 → 并行检索 → 内容拉取 → 对抗式校验 → 结果综合,和本教程讲解的标准框架完全一致。
-
-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\.
-
-
-
-第三种方式:开启极速代码模式(ultracode),会话内每一项有一定复杂度的任务,Claude 都会自动规划专属工作流。 某次执行效果理想时,按下快捷键 `s` 即可将编排脚本保存至 `.claude/workflows/` 目录;脚本自带版本管理,可通过名称重复调用,任何克隆该代码仓库的人都能直接启动这套任务图谱。
-
-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\.
-
-
-
-python
-
-执行一套工作流,对 `src/routes/` 下所有接口路由开展鉴权缺失审计:为每一个路由文件单独创建一个子智能体,每条检测结果都先经过校验,再输出审计报告。 ● Claude 已生成任务编排脚本・后台启动执行中… /workflows — auth\-audit(鉴权审计工作流)・运行中 ✓ 范围界定 1/1,消耗 2100 Token,耗时 4 秒 ✓ 并行扇出 18/18,18 个路由文件各分配独立子智能体 ◯ 校验环节 11/18,每条检测结论配备 3 票制对抗质疑校验器…… ○ 汇总合成 0/1,等待校验阶段完成 会话全程保持可交互状态,子智能体集群后台持续运算不阻塞界面
-
-```Python
-› Run a workflow to audit every route under src/routes/ for missing
-auth. Spawn one agent per route file, then verify each finding before
-reporting. ● Claude wrote an orchestration script · launching in
-background… /workflows — auth-audit · running ✓ Scope 1/1 2.1k tok ·
-4s ✓ Fan-out 18/18 one agent per route file ◯ Verify 11/18 3-vote
-skeptics per finding… ○ Synthesize 0/1 waiting on verify session stays
-responsive — keep working while the fleet runs
-```
-
-
-
-\#\# 本周用 Claude 构建的六个图
-
-## Six graphs to build with *Claude* this week
-
-
-
-- **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\.
-
-全链路安全扫描
-
-对每一条业务路径执行安全巡检。Claude 会为每个路由文件单独生成一个子智能体,分别检索缺失的权限校验逻辑;所有风险点需经过校验器二次核验,确认无误后才会录入报告。覆盖广度远超单一上下文所能承载的分析范围。
-
-
-
-- **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\.
-
-基于 /deep\-research 指令生成引证调研报告
-
-该功能图谱已内置在 Claude Code 中。Claude 将你的查询拆解为多个独立分析维度,并行执行多源检索、去重重复资料;随后启用三方复核机制对每一条结论交叉质证,全部核验通过后再撰写最终报告。
-
-
-
-- **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\.
-
-逐文件迁移代码模块
-
-突破 Bun 运行时性能上限,适配你的完整代码仓库。Claude 并行批量转换多个文件,每完成一份文件就执行全套测试用例作为准入关卡;编译 / 测试失败的文件会回流重处理。对抗式复核机制能揪出单次粗转极易遗留的代码缺陷。
-
-
-
-- **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\.
-
-代码变更差分对抗评审
-
-Claude 根据代码改动规模分流评审策略:小幅变更执行一轮快速核查;大规模改动将启动全量并行审计,多评审智能体分别从逻辑正确性、安全漏洞、运行性能三大维度独立审查,最后由合议模块整合各方结论输出综合意见。
-
-
-
-- 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\.
-
-定时生态全域巡检
-
-配置一次,永久复用。Claude 并行拉取多渠道信息:版本发布公告、技术博客、社区讨论帖;按业务影响权重过滤低价值内容,自动生成精简摘要。工作流配置存入版本库 `.claude/workflows/` 目录,支持通过名称一键启动。
-
-
-
-- **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\.
-
-未知范围全域漏洞探查
-
-适用于漏洞总量未知的场景。Claude 并行启动多套漏洞检索器,新发现问题自动与已有结果去重,留存条目全部完成核验;持续循环检索,连续两轮无新增问题后自动终止扫描。
-
-
-
-
-
-## Conclusion:
-
-\#\# 结语:
-
-
-
-提示者问问题。架构师画图。
-
-A prompter asks a question\. An architect draws a *graph\.*
-
-
-
-线性智能体从来不是天花板——它只是第一个形状,每个人都会伸手去拿的那个,因为它匹配我们打字的方式。一行,一个脑袋,一次一件事。
-
-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\.**
-
-
-
-一旦你能看到节点和边,你就不再要求智能体做更多,而是要求图做得更宽:在独立工作处扇出,在信心要紧处给边加门,在判断不需要处分层模型。
-
-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\.
-
-
-
-大多数人会继续排队走直线。学会画图的人将运行一个舰队——而永远不会注意到其他人卡在的天花板。
-
-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\.
-
diff --git a/docs/development-plan.md b/docs/development-plan.md
index 28a091e..9d202ef 100644
--- a/docs/development-plan.md
+++ b/docs/development-plan.md
@@ -27,7 +27,7 @@
| PH2 | 质量闭环:回路、Skill 迭代、护栏、断点恢复 | Risk Gate | 架构文档 §11 验收标准第 4-5 条;REQ-04/05/06/08/10/14 |
| PH3 | 人机交互控制台 | Value Path | REQ-11/12/13 |
| PH4 | 全量验收与固化 | Risk Gate | 06 节全部 14 条 REQ 完整回归通过一次 |
-| 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 步走查场景真实跑通一次 |
+| 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 步走查场景真实跑通一次 |
依赖顺序: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 开始被允许对自己的代码做自主修改"这个新增能力上生效。
@@ -333,8 +333,8 @@ PH2-S1/S2/S3/S4 之间除标注的依赖外相互独立,可并行推进。
**命名说明**:本 Phase 的"Deploy Supervisor"和架构文档里"Supervisor(调度子 Agent,Coder Work Graph 内部监控节点)"是两个不同概念,详见 `supervisor-auto-rollback-design.md` 开头的命名说明,不要混用。
**Goal**:graphlemon 具备安全地自主修改自己代码(含 Org Agent 身份文件、调度器/执行器核心逻辑)的能力——出问题时自动回滚,不需要人工评审代码内容把关。
-**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"全程无需人工介入代码评审。
-**设计依据**:`docs/supervisor-design-comparison.md`(方案选型)+ `docs/supervisor-auto-rollback-design.md`(状态机/数据模型/健康检查/回滚流程/MVP 参数)。
+**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"全程无需人工介入代码评审。
+**设计依据**:`docs/done/supervisor-design-comparison.md`(方案选型)+ `docs/done/supervisor-auto-rollback-design.md`(状态机/数据模型/健康检查/回滚流程/MVP 参数)。
### PH5-S1 数据模型与治理规则
diff --git a/docs/build-report.md b/docs/done/build-report.md
index 4ef28b9..4ef28b9 100644
--- a/docs/build-report.md
+++ b/docs/done/build-report.md
diff --git a/docs/self-development-feasibility.md b/docs/done/self-development-feasibility.md
index 9d636b5..5293231 100644
--- a/docs/self-development-feasibility.md
+++ b/docs/done/self-development-feasibility.md
@@ -4,7 +4,7 @@
**性质**: 探索性分析,不是已确认的架构决策,未写入 `sdd-mvcp-contract.json`。若决定采纳,需要走一次正式的 AskUserQuestion 决策流程,再回填进 05-实施约束与依赖.md 和 contract.json。
**结论先说**:机制上大部分已经具备(三个 Org Agent 落在目标项目 `.claude/agents/*.md`、`c_project_create` 对 `target_folder_path` 没有排除平台自身路径、git worktree 隔离已经是既定决策),但有三个真缺口会让"自己改自己"从"能跑"变成"跑了但可能很危险"。下面先列缺口,再用一个具体场景走一遍,标出每一步是"✅ 已具备"还是"⚠️ 缺口"。
-**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 的原始描述保留作为问题记录,其"推荐方案"段落已更新为指向这两份新文档。
+**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 的原始描述保留作为问题记录,其"推荐方案"段落已更新为指向这两份新文档。
## 1. 前提:自举不是从零开始的
@@ -26,7 +26,7 @@ Validator 在 worktree 里 PASS 之后,现有机制(git_worktree_ref 执行å
**推荐方案(蓝绿实例,而不是原地重启)**:不在主进程里做"重启自己",而是让 Validator 直接对着 worktree 里跑起来的一个**独立的第二份 graphlemon 实例**(换端口、换一份临时/只读数据库)做验证;通过后由一个**在 Graph Engine 控制范围之外**的独立 **Deploy Supervisor**(与架构文档里 Coder Work Graph 内部的"Supervisor(调度子 Agent)"是两个不同概念,注意区分)执行切换:停旧实例 → merge 分支到主分支 → 起新实例。这样"运行中的自己"永远不需要重启自己。
-**(2026-07-30 已完成详细设计并回填正式 SDD)** 完整状态机、健康检查信号、cutover/回滚流程、MVP 固定参数见 `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`。
+**(2026-07-30 已完成详细设计并回填正式 SDD)** 完整状态机、健康检查信号、cutover/回滚流程、MVP 固定参数见 `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`。
### 缺口 3:核心代码/身份文件的自我修改没有对应缺口 3.5 节 `org_graph_definition` 那样的强制人工评审
@@ -39,7 +39,7 @@ Validator 在 worktree 里 PASS 之后,现有机制(git_worktree_ref 执行å
**用户更正(2026-07-30,原话)**:"改坏了的 validator 没有人工评审倒不是问题,坏了由 supervisor 能够监督回滚即可。所以核心是需要一个三方的 agent 来作为补充 supervisor 接口。" —— 即:不要求核心文件改动必须先经人工评审才能生效,而是允许系统自主提交,由缺口 2 的同一个 Supervisor 在部署后持续监控行为信号(例如 Validator 的 approve 率是否偏离历史基线),一旦检测到异常自动回滚,不需要人在"改之前"把关。
-**(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 节),而不是"代码判断谁对谁错"。
+**(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 节),而不是"代码判断谁对谁错"。
`bug_record.root_cause_category` 新增 `core_engine` 分类的建议仍然保留,但用途从"触发强制人工评审"改为"触发时跳过 `fast_lane`、走完整的蓝绿部署+监控流程"(普通 Skill 级别的自主迭代可能不需要这么重的验证)。
@@ -63,6 +63,6 @@ Validator 在 worktree 里 PASS 之后,现有机制(git_worktree_ref 执行å
## 4. 小结
-9 步里 3 步直接可用,1 步大概率可用但没显式设计,剩下 5 步(含最关键的"合并后如何生效")命中三个真缺口。**2026-07-30 更新**:缺口 2(蓝绿实例生效)和缺口 3(核心文件自我修改的治理,按用户更正为"自动回滚而非强制人工评审")已完成详细设计,分别落在 `docs/supervisor-auto-rollback-design.md`(状态机/健康检查/回滚流程/MVP 参数)和 `docs/supervisor-design-comparison.md`(方案选型依据)。缺口 1(worktree 无条件强制)仍是待写入 05 节的一条治理规则,成本低。
+9 步里 3 步直接可用,1 步大概率可用但没显式设计,剩下 5 步(含最关键的"合并后如何生效")命中三个真缺口。**2026-07-30 更新**:缺口 2(蓝绿实例生效)和缺口 3(核心文件自我修改的治理,按用户更正为"自动回滚而非强制人工评审")已完成详细设计,分别落在 `docs/done/supervisor-auto-rollback-design.md`(状态机/健康检查/回滚流程/MVP 参数)和 `docs/done/supervisor-design-comparison.md`(方案选型依据)。缺口 1(worktree 无条件强制)仍是待写入 05 节的一条治理规则,成本低。
如果要推进,建议顺序:先把缺口 1(一条治理规则)写进 05 节和 contract.json,成本低、马上能落地;缺口 2/3 的设计已经具备可实施粒度,可以作为 PH4 之后的独立 Phase(PH5:自举能力)排入 `development-plan.md`,不影响当前 PH0-PH4 主线——具体是现在就把 PH5 的 Unit 分解也做出来,还是先确认设计稿本身没问题再排,由用户决定。
diff --git a/docs/supervisor-auto-rollback-design.md b/docs/done/supervisor-auto-rollback-design.md
index ac72a9a..d4a770e 100644
--- a/docs/supervisor-auto-rollback-design.md
+++ b/docs/done/supervisor-auto-rollback-design.md
@@ -2,7 +2,7 @@
**日期**: 2026-07-30(同日更新:完成 Supervisor 概念改名消歧,见下方命名说明)
**性质**: 详细设计,回应用户"自动回滚机制现在就设计一下,省得到 PH5 阶段有那个阶段的问题"。落地 `self-development-feasibility.md` 缺口 2(应用变更+生效)与缺口 3(用户更正版:不要求人工评审,要求 supervisor 自动监督回滚)。
-**依赖**: `docs/supervisor-design-comparison.md`(方案选型结论:复用 graphloop 隔离/manifest/apply 库,见 `supervisor/isolation/`;Deploy Supervisor 自身决策逻辑必须是确定性代码,不接入 LLM)。
+**依赖**: `docs/done/supervisor-design-comparison.md`(方案选型结论:复用 graphloop 隔离/manifest/apply 库,见 `supervisor/isolation/`;Deploy Supervisor 自身决策逻辑必须是确定性代码,不接入 LLM)。
**状态**: 设计已回填进正式 SDD(`01-M`/`03-C`/`04-P`/`05`/`06`/`sdd-mvcp-contract.json`),代码尚未实现(`supervisor/isolation/` 除外,那是通用库不是本机制专属)。
**命名说明(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 任务存在,一个是常驻的独立进程)。
diff --git a/docs/supervisor-design-comparison.md b/docs/done/supervisor-design-comparison.md
index c89280a..c89280a 100644
--- a/docs/supervisor-design-comparison.md
+++ b/docs/done/supervisor-design-comparison.md
diff --git a/docs/implementation-gap-analysis.md b/docs/implementation-gap-analysis.md
index dbd0803..3899c26 100644
--- a/docs/implementation-gap-analysis.md
+++ b/docs/implementation-gap-analysis.md
@@ -2,9 +2,9 @@
**日期**: 2026-08-03
**性质**: 现状快照,回应用户 "现在整个系统的实现和我们的文档的 goal 和 brd prd sdd 还有哪些差距"。基于对 `git log`、
-`docs/development-plan.md`(含各 Unit 事后补记)、`docs/build-report.md`、`docs/sdd/06-验收与测试锚点.md` 和实际
+`docs/development-plan.md`(含各 Unit 事后补记)、`docs/done/build-report.md`、`docs/sdd/06-验收与测试锚点.md` 和实际
代码(`src/`、`supervisor/`)的交叉核对得出,不是重新跑一遍验收。
-**结论先说**: 45 个 Unit(U-001~U-043 + U-004.5/U-024.5)全部有对应实现和提交,`docs/build-report.md` 的
+**结论先说**: 45 个 Unit(U-001~U-043 + U-004.5/U-024.5)全部有对应实现和提交,`docs/done/build-report.md` 的
"全部完成"判断在"每个 Unit 都有代码落地"这个意义上是对的。但"全部完成"不等于"和文档设定的目标完全对齐"——
差距集中在 PH5 的"自动"两个字还没完全兑现、系统还不是真正自驱动的、以及若干 SDD 契约的已知简化。逐条见下。
**如何维护本文档**: 这是一次性快照,会随实现推进而过时。发现某条已经补上或又出现新缺口时,直接编辑对应条目
@@ -77,7 +77,7 @@
- `development-plan.md`/`unit_matrix.json` 从写就没有随实现更新"done"状态,只能靠 `git log` 和散落在各 Unit
条目下的事后补记("实现补充"/"真实 bug")判断真实进度。
-- `docs/build-report.md` 里"⚠️ 尚未真实执行的一项"那一段(称 `RUN_REAL_SELF_HOSTING_E2E` 从未跑过)本身也已
+- `docs/done/build-report.md` 里"⚠️ 尚未真实执行的一项"那一段(称 `RUN_REAL_SELF_HOSTING_E2E` 从未跑过)本身也已
经过时——`development-plan.md` U-042 条目下 2026-08-03 的记录显示之后确实做了 13 次真实自举压测(`git log`
里那一长串 `graphlemon: auto-commit`/`merge`/artifact 提交就是证据),只是仍未到达 `finalized`。连"总结性
收口文档"都会在自举持续运行的过程中被甩在后面——这是个会持续放大的风险,不是一次性的疏漏。
diff --git a/docs/reference/graph-engineering-1000-agents-5-steps.md b/docs/reference/graph-engineering-1000-agents-5-steps.md
index 49ed228..65773c8 100644
--- a/docs/reference/graph-engineering-1000-agents-5-steps.md
+++ b/docs/reference/graph-engineering-1000-agents-5-steps.md
@@ -3,7 +3,7 @@
- **来源**:微信公众号文章,https://mp.weixin.qq.com/s/tR0Z_3Yka2dNXQPO7P0OsA
- **收录方式**:原文由用户在对话中手动粘贴全文文本(微信有反爬验证,工具无法直接抓取正文);正文引用的插图未能一并保存,下文以 `[图片]` 标记原文插图位置。
- **入库日期**:2026-08-04
-- **说明**:本文与 `docs/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md`(14 步版)是同一主题的姊妹篇/精简版,均围绕 Claude Code 的 Dynamic Workflows 功能展开,可与该文对照阅读。
+- **说明**:本文与 `docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md`(14 步版)是同一主题的姊妹篇/精简版,均围绕 Claude Code 的 Dynamic Workflows 功能展开,可与该文对照阅读。
---
diff --git a/docs/reference/graph-engineering-vs-loop-engineering-overview.md b/docs/reference/graph-engineering-vs-loop-engineering-overview.md
index 69c5514..11c9c79 100644
--- a/docs/reference/graph-engineering-vs-loop-engineering-overview.md
+++ b/docs/reference/graph-engineering-vs-loop-engineering-overview.md
@@ -3,7 +3,7 @@
- **来源**:腾讯云开发者微信公众号文章,https://mp.weixin.qq.com/s/0DGAr-N-rrYJrzxc2yiGEw
- **收录方式**:原文由用户在对话中手动粘贴全文文本(微信有反爬验证,工具无法直接抓取正文);正文引用的插图未能一并保存,下文以 `[图片]` 标记原文插图位置。
- **入库日期**:2026-08-04
-- **说明**:与 `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)。
+- **说明**:与 `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)。
---
diff --git a/docs/sdd/00-SDD-生成记录.md b/docs/sdd/00-SDD-生成记录.md
index 6fcf552..f8b8f64 100644
--- a/docs/sdd/00-SDD-生成记录.md
+++ b/docs/sdd/00-SDD-生成记录.md
@@ -5,7 +5,7 @@
| 字段 | 值 |
|---|---|
| project | graphlemon |
-| 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`) |
+| 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`) |
| owner | bboonnee |
| effective_date | 2026-07-30 |
| 方法论 | 复用本地 Hermes `planning` profile 的 `project-sdd-planning` skill(MVCP:Model/View/Controller/Process) |
diff --git a/docs/sdd/01-M-模型设计.md b/docs/sdd/01-M-模型设计.md
index 3530779..fc9d8b6 100644
--- a/docs/sdd/01-M-模型设计.md
+++ b/docs/sdd/01-M-模型设计.md
@@ -223,7 +223,7 @@
- `updated_by` string,not null
- `updated_at` timestamp
-## 1.15 实体:`deployment`(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`)
+## 1.15 实体:`deployment`(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
**目的**: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` 开头命名说明)。
@@ -238,7 +238,7 @@
- `previous_instance_dir` string,not null —— 上一个已知良好版本的 checkout 目录,在本次 deployment 监控窗口正常关闭(`finalized`)前不允许清理
- 约束:同一时刻 graphlemon 自身仓库只允许一条处于非终态(不在 `finalized`\|`rolled_back`\|`blocked_pending_human`)的 `deployment` 记录,避免并发蓝绿部署互相干扰
-## 1.16 实体:`deploy_health_signal`(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`)
+## 1.16 实体:`deploy_health_signal`(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
**目的**:`deployment` 生命周期内每次健康检查/监控采样的记录,全部是确定性判断结果,不涉及任何 LLM 判断。
diff --git a/docs/sdd/03-C-控制与接口.md b/docs/sdd/03-C-控制与接口.md
index a5fef69..19b5862 100644
--- a/docs/sdd/03-C-控制与接口.md
+++ b/docs/sdd/03-C-控制与接口.md
@@ -259,7 +259,7 @@
- 断线重连约束:客户端重连后需带上 `last_event_id` 查询自该事件之后的遗漏事件(通过 `c_task_get` 系接口的时间线字段补齐,而非 WebSocket 层做消息重放缓冲,MVP 阶段简化处理)
- `owner_team`:control-plane
-## 3.15 Deploy Supervisor 控制操作(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`)
+## 3.15 Deploy Supervisor 控制操作(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
以下五个操作均由 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` 路由分支)。
diff --git a/docs/sdd/04-P-流程与状态流转.md b/docs/sdd/04-P-流程与状态流转.md
index e97f9fd..f7fb233 100644
--- a/docs/sdd/04-P-流程与状态流转.md
+++ b/docs/sdd/04-P-流程与状态流转.md
@@ -101,7 +101,7 @@
约束:清理过程只删除物理文件和更新 `execution_log_file` 状态,不删除 `execution_log_file` 记录本身、不删除任何 `execution_event`/`task`/`artifact` 等业务数据,保证"日志曾经存在过"这件事可审计(对应 01-M 1.13 节约束)。
-## 4.6 流程:`p_deploy_supervisor_blue_green`(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`)
+## 4.6 流程:`p_deploy_supervisor_blue_green`(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
- `start_state`:`staged`
- `end_state`:`finalized`(安全终态)| `blocked_pending_human`(唯一需人工介入的终态,触发条件严格限定为"自动回滚机制本身失败",不是"代码写得不好")
diff --git a/docs/sdd/05-实施约束与依赖.md b/docs/sdd/05-实施约束与依赖.md
index 8b160b6..99b1563 100644
--- a/docs/sdd/05-实施约束与依赖.md
+++ b/docs/sdd/05-实施约束与依赖.md
@@ -110,9 +110,9 @@
- **worktree 无条件强制**:`target_folder_path == graphlemon 平台自身安装路径` 时,无论并行还是串行,Coder/Validator 一律强制使用 `git_worktree_ref`,不适用 03-C 3.6 节"仅并行写代码节点才强制申请 worktree"的一般规则——这是该规则的 self-target 特例
- **`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`
-- **不要求提交前人工评审**:`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 变更同样走蓝绿部署+监控流程,不是简单停机迁移
+- **不要求提交前人工评审**:`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 变更同样走蓝绿部署+监控流程,不是简单停机迁移
-## 5.9 Deploy Supervisor MVP 固定参数(`docs/supervisor-auto-rollback-design.md` §9,2026-07-30 回填)
+## 5.9 Deploy Supervisor MVP 固定参数(`docs/done/supervisor-auto-rollback-design.md` §9,2026-07-30 回填)
| 参数 | 草案值 | 说明 |
|---|---|---|
diff --git a/docs/sdd/06-验收与测试锚点.md b/docs/sdd/06-验收与测试锚点.md
index f8e4e6c..cc04953 100644
--- a/docs/sdd/06-验收与测试锚点.md
+++ b/docs/sdd/06-验收与测试锚点.md
@@ -114,7 +114,7 @@
- boundary:配置 `retention_days=1`,构造一条 `created_at` 超过 1 天的 `closed` 状态日志,清理任务运行后应转为 `purged` 且物理文件被删除;`v_log_viewer` 打开该日志时展示"已被清理"提示而非报错
- failure:订阅一个不存在的 `node_exec_id` 的日志流,应返回 `NODE_EXEC_NOT_FOUND`;清理任务遇到某个文件删除失败(如被占用)时,应仅记录该项失败、不影响其余候选项的清理,且该条目下次周期继续重试
-## REQ-15:Deploy Supervisor 蓝绿部署与自动回滚(PH5 新增,2026-07-30,见 `docs/supervisor-auto-rollback-design.md`)
+## REQ-15:Deploy Supervisor 蓝绿部署与自动回滚(PH5 新增,2026-07-30,见 `docs/done/supervisor-auto-rollback-design.md`)
- 覆盖: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`
- 验收用例:
diff --git a/docs/dynamic-work-graph-design-skill-requirements.md b/docs/todo/dynamic-work-graph-design-skill-requirements.md
index a64ce76..a64ce76 100644
--- a/docs/dynamic-work-graph-design-skill-requirements.md
+++ b/docs/todo/dynamic-work-graph-design-skill-requirements.md
diff --git a/docs/todo/graph-engineering-open-items.md b/docs/todo/graph-engineering-open-items.md
index 0ea9d3f..65bb6ed 100644
--- a/docs/todo/graph-engineering-open-items.md
+++ b/docs/todo/graph-engineering-open-items.md
@@ -70,7 +70,7 @@
**确认结果(2026-08-05)**:立项。
-**待办**:✅ 需求文档已产出 → `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)会挡住分叉分支,需要在设计阶段决定怎么扩展。
+**待办**:✅ 需求文档已产出 → `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)会挡住分叉分支,需要在设计阶段决定怎么扩展。
---
@@ -114,7 +114,7 @@
**确认结果(2026-08-05)**:确认要做,用户强调"这个很核心"。触发规则明确为:**只要 Work Graph 规划了 fan-out/fan-in 的并行节点,节点触发时就应该并行执行**——这跟需求文档 FR-1/FR-2 已经写的内容一致,不需要改需求文档的功能需求本身,只需要在文档里把这句话作为明确的验收口径固化下来。
-**待办**:进入详细设计阶段(`topological_layers()` 的具体实现、线程池调度器接口、锁的粒度),需求文档里第 8 节列的开放问题需要在设计阶段逐一敲定。这是 E、F 两条的前置依赖,建议优先推进。
+**待办**:✅ 详细设计已产出 → `docs/work-graph-parallel-execution-design.md`。核心设计:新增 `topological_layers()`(层内并发、层间屏障),`_LockedConn` 线程安全代理复用 `backend_server.py` 的锁模式,`create_worktree()` 加防御性锁,`WorkGraphLayerError` 聚合层内多个失败。已知遗留限制(诚实记录,非本轮解决):单节点配额等待可长达数小时,会拖住整层其他已完成节点的返回——需要更细粒度的可中断调度才能解决,超出本次范围。下一步:实施(见设计文档第 11 节的分步实施建议),或先评审设计。
---
diff --git a/docs/validator-loop-until-dry-skill-requirements.md b/docs/todo/validator-loop-until-dry-skill-requirements.md
index fa295d1..fa295d1 100644
--- a/docs/validator-loop-until-dry-skill-requirements.md
+++ b/docs/todo/validator-loop-until-dry-skill-requirements.md
diff --git a/docs/verifier-fan-out-jury-requirements.md b/docs/todo/verifier-fan-out-jury-requirements.md
index 39eba7a..39eba7a 100644
--- a/docs/verifier-fan-out-jury-requirements.md
+++ b/docs/todo/verifier-fan-out-jury-requirements.md
diff --git a/docs/todo/work-graph-parallel-execution-design.md b/docs/todo/work-graph-parallel-execution-design.md
new file mode 100644
index 0000000..ed6fe2e
--- /dev/null
+++ b/docs/todo/work-graph-parallel-execution-design.md
@@ -0,0 +1,277 @@
+# Work Graph 节点并行执行——详细设计(SDD)
+
+**日期**: 2026-08-05
+**性质**: 详细设计,对应需求文档 `docs/work-graph-parallel-execution-requirements.md`(已确认,核心优先级)。本设计逐条回答该需求文档第 8 节的开放问题,并给出可直接实施的接口/算法/测试方案。
+**依赖代码**:`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` 先例)。
+**状态**: 设计草案,待评审后实施。
+
+---
+
+## 1. 设计概览
+
+现状:`run_graph()`(`work_graph_orchestrate.py:196`)用 `topological_order()` 把 `graph_manifest` 拍平成一条扁平列表,纯 Python `for` 循环逐节点执行。
+
+改造后:新增 `topological_layers()`,把 Kahn 算法的中间产物(每一轮 in-degree 归零的节点集合)保留为"层"而不是拍平;`run_graph()` 改成"层间顺序、层内并发"——同一层内的节点用 `ThreadPoolExecutor` 并发触发各自的 `run_work_node()`,层与层之间保持屏障(下一层要等上一层全部完成)。
+
+**不改变的部分**(刻意保持稳定,降低改动面):
+- 每个节点仍然是"渲染成一个单节点 workflow 脚本 + 独立 `claude -p` 子进程调用"(`_make_run_fn`/`_render_node_script` 不变)。
+- `run_work_node()` 的状态机、断点续跑逻辑、重试/配额等待逻辑完全不变。
+- `run_graph()` 的对外签名和返回值格式(`{node_id: output_payload}`)不变(需求文档 FR-9)。
+- `verify_fn` 的语义不变(需求文档 FR-8)。
+
+## 2. `topological_layers()`:新增分层拓扑排序
+
+```python
+def topological_layers(graph_manifest):
+ """Kahn 算法的分层版本:返回 List[List[node]],每个内层 list 是同一轮
+ in-degree 归零的节点集合(互相之间保证没有依赖,可以并发执行)。
+ 层内按 node_id 排序(保持确定性,便于测试断言)。
+ 依赖来源、报错行为跟现有 topological_order() 完全一致(合并
+ depends_on 和 edges 两个来源;有环或引用未知 node_id 时 raise ValueError)。
+ """
+ nodes = {n["node_id"]: n for n in graph_manifest["nodes"]}
+ deps = {node_id: set(n.get("depends_on") or []) for node_id, n in nodes.items()}
+ for edge in graph_manifest.get("edges", []):
+ to_id, from_id = edge.get("to"), edge.get("from")
+ if to_id in deps and from_id in nodes:
+ deps[to_id].add(from_id)
+
+ in_degree = {node_id: len(d) for node_id, d in deps.items()}
+ dependents = {node_id: [] for node_id in nodes}
+ for node_id, d in deps.items():
+ for dep in d:
+ if dep in dependents:
+ dependents[dep].append(node_id)
+
+ layers = []
+ current = sorted(node_id for node_id, degree in in_degree.items() if degree == 0)
+ seen_count = 0
+ while current:
+ layers.append([nodes[node_id] for node_id in current])
+ seen_count += len(current)
+ next_layer = set()
+ for node_id in current:
+ for dependent in dependents[node_id]:
+ in_degree[dependent] -= 1
+ if in_degree[dependent] == 0:
+ next_layer.add(dependent)
+ current = sorted(next_layer)
+ if seen_count != len(nodes):
+ raise ValueError(
+ "graph_manifest has a dependency cycle or a depends_on/edge references an unknown node_id"
+ )
+ return layers
+```
+
+`topological_order()` **保留**(其他调用方,如 `e2e_pipeline.py:233` 直接用它拿扁平顺序写 artifact),但改成对 `topological_layers()` 的薄封装,保证行为/顺序完全不变:
+
+```python
+def topological_order(graph_manifest):
+ return [node for layer in topological_layers(graph_manifest) for node in layer]
+```
+
+(需要跑一次现有 `test_work_graph_orchestrate.py` 里 `topological_order` 的既有单测,确认拍平后的顺序逐字节相同——这两版算法的排序规则一致,理论上不会有差异,但改动了实现路径,必须实测验证不是"理论上一致"就够。)
+
+## 3. SQLite 并发写入:`_LockedConn` 代理
+
+**问题**:`run_work_node()`/`_ensure_node_row()`/`execution_log.append_log()` 等函数内部有大量分散的 `conn.execute(...)` + `conn.commit()` 调用点。层内并发后,这些调用可能从多个线程同时打到同一个 `sqlite3.Connection` 对象上。
+
+**关键判断**:SQLite 本身同一时刻只允许一个写者(这是 SQLite 的架构限制,不是 Python 线程模型能绕开的),所以不需要设计"细粒度多把锁",**一把锁保护整个连接的读写足够**——真正的并发收益来自于"锁只在单条 SQL 语句执行期间持有,`claude -p` 子进程调用(决定墙钟时间的部分)完全不持锁",而不是来自"多个线程能同时写 SQLite"(这本来就不可能)。
+
+**方案**:不去逐个包裹现有几十处 `conn.execute()` 调用点(改动面大、容易漏改),而是引入一个跟 `sqlite3.Connection` 接口兼容的轻量代理对象,在并发路径上把 `conn` 参数替换成这个代理——调用方代码(`work_node_run.py`/`work_graph_orchestrate.py`/`execution_log.py`)完全不用改一行:
+
+```python
+class _LockedConn:
+ """sqlite3.Connection 的线程安全代理:execute/commit/executemany 各自
+ 持锁执行,可以直接替代 conn 参数传给现有代码,调用方无需感知。复用
+ backend_server.py::_Server 已经验证过的"共享连接 + threading.Lock"模式,
+ 只是把锁的粒度做到了每条语句级别(backend_server 现有的 conn_lock 只
+ 保护 server_close() 一处,不是通用查询锁——本类是把同一个思路做成可
+ 在任意并发场景复用的通用工具,两者不冲突,backend_server 未来可以直
+ 接换成这个类)。
+ """
+ def __init__(self, conn, lock=None):
+ self._conn = conn
+ self._lock = lock or threading.Lock()
+
+ def execute(self, *args, **kwargs):
+ with self._lock:
+ return self._conn.execute(*args, **kwargs)
+
+ def executemany(self, *args, **kwargs):
+ with self._lock:
+ return self._conn.executemany(*args, **kwargs)
+
+ def commit(self):
+ with self._lock:
+ return self._conn.commit()
+
+ def __getattr__(self, name):
+ # cursor()/rollback()/close() 等未显式包装的方法透传给底层连接,
+ # 调用方目前不会从多线程并发调用这些(没有并发路径用到它们)。
+ return getattr(self._conn, name)
+```
+
+`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`,因为那部分本来就不在并发路径上,没必要多一层锁的开销**。
+
+## 4. Worktree 创建竞态
+
+`work_node_run.py::create_worktree()`(`git worktree add -b `)在并发场景下可能对同一个 repo 的 `.git/worktrees/` 元数据产生竞态——这是**推测**,需求文档已标注"未经验证"。设计上采用保守做法:给 `create_worktree()` 包一把**进程级模块锁**(跟 SQLite 锁分开,因为这个锁保护的是文件系统/git 内部状态,不是数据库):
+
+```python
+_worktree_creation_lock = threading.Lock()
+
+def create_worktree(repo_path, node_exec_id):
+ with _worktree_creation_lock:
+ ... # 原逻辑不变
+```
+
+这个锁只包住 `git worktree add` 这一条命令本身(毫秒级),不包住节点其余的执行时间,所以即使锁確实是必要的,对整体并行收益的影响可以忽略——`claude -p` 调用(秒级到分钟级)才是墙钟时间的主体。**验证方式**:写一个真实的并发 `create_worktree` 调用测试(多线程同时对同一个临时 git 仓库调用),先不加锁跑一遍看是否真的出错,出错则保留锁、不出错也保留锁(保守优先,反正代价极小)。
+
+## 5. 并发调度器:`run_graph()` 改造
+
+```python
+import concurrent.futures
+
+_DEFAULT_MAX_WORKERS = 4 # 保守默认值,见第 9 节
+
+def run_graph(
+ conn, run_id, org_node, skill_id, target_folder_path, graph_manifest,
+ build_prompt, input_schema, output_schema, timeout=600, needs_worktree=False,
+ verify_fn=None, sleep=time.sleep, now=time.time, max_workers=_DEFAULT_MAX_WORKERS,
+):
+ ensure_placeholder_skill(conn, skill_id, org_node)
+ effective_needs_worktree = needs_worktree or paths.is_self_target(target_folder_path)
+ layers = topological_layers(graph_manifest)
+ outputs = {}
+ db_lock = threading.Lock()
+ locked_conn = _LockedConn(conn, db_lock)
+
+ for layer in layers:
+ if len(layer) == 1:
+ # 单节点层:不走线程池,直接同步执行——省掉线程池调度开销,
+ # 也让现有单节点场景(目前观察到的真实 graph_manifest 全都是
+ # 单节点链)行为路径尽量贴近改造前,降低回归风险。
+ node = layer[0]
+ outputs[node["node_id"]] = _run_one_node(
+ conn, run_id, org_node, skill_id, target_folder_path, node, outputs,
+ build_prompt, input_schema, output_schema, timeout,
+ effective_needs_worktree, verify_fn, sleep, now,
+ )
+ continue
+
+ worker_count = min(max_workers, len(layer))
+ errors = []
+ with concurrent.futures.ThreadPoolExecutor(max_workers=worker_count) as pool:
+ futures = {
+ pool.submit(
+ _run_one_node, locked_conn, run_id, org_node, skill_id, target_folder_path,
+ node, outputs, build_prompt, input_schema, output_schema, timeout,
+ effective_needs_worktree, verify_fn, sleep, now,
+ ): node
+ for node in layer
+ }
+ # 等整层全部完成(成功或失败)再继续——层内节点互不依赖,
+ # 一个节点的失败不该取消已经在跑的兄弟节点(需求文档 FR-5)。
+ done, _ = concurrent.futures.wait(futures, return_when=concurrent.futures.ALL_COMPLETED)
+ for future in done:
+ node = futures[future]
+ try:
+ outputs[node["node_id"]] = future.result()
+ except Exception as e:
+ errors.append((node["node_id"], e))
+
+ if errors:
+ # 聚合报错:全部记录,抛出的异常携带完整清单,不只是"第一个",
+ # 方便排查同一层里到底几个节点失败、分别是什么原因。
+ raise WorkGraphLayerError(errors)
+
+ return outputs
+
+
+def _run_one_node(
+ conn, run_id, org_node, skill_id, target_folder_path, node, outputs,
+ build_prompt, input_schema, output_schema, timeout, needs_worktree, verify_fn, sleep, now,
+):
+ """原 run_graph() 循环体单节点逻辑原样保留,只是抽成一个独立函数,
+ 好让同步路径(单节点层)和并发路径(多节点层)共用同一份实现,
+ 不重复维护两份"跑一个节点+可选 verify_fn"的逻辑。"""
+ node_id = node["node_id"]
+ node_exec_id = _ensure_node_row(
+ conn, run_id, node_id, org_node, skill_id,
+ "work_graph_node_input.schema.json", "work_graph_node_output.schema.json",
+ {"node_id": node_id},
+ )
+ prompt = build_prompt(node, outputs)
+ run_fn = _make_run_fn(conn, target_folder_path, node_exec_id, org_node, prompt, output_schema, timeout)
+ output = work_node_run.run_work_node(
+ conn, node_exec_id, {"node_id": node_id}, input_schema, output_schema, run_fn,
+ needs_worktree=needs_worktree, repo_path=target_folder_path, sleep=sleep, now=now,
+ )
+ if verify_fn is not None:
+ output = _verify_and_maybe_regenerate(
+ conn, run_id, org_node, skill_id, target_folder_path, node, node_id, prompt,
+ output, input_schema, output_schema, timeout, needs_worktree, verify_fn, sleep, now,
+ )
+ return output
+
+
+class WorkGraphLayerError(Exception):
+ """一层内一个或多个节点执行失败——携带 [(node_id,原始异常), ...] 完整
+ 清单,调用方可以按需检查是不是全是同一种可恢复错误。"""
+ def __init__(self, errors):
+ self.errors = errors
+ summary = "; ".join("{}: {}".format(node_id, e) for node_id, e in errors)
+ super().__init__("{} node(s) failed in one layer: {}".format(len(errors), summary))
+```
+
+**关键设计取舍——`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 冲突。
+
+## 6. 错误/配额处理的真实限制(诚实记录,不回避)
+
+`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()` 调用也会被拖住。
+
+**这是本设计接受的限制,不是本轮要解决的问题**:Python 线程一旦启动就没有干净的"取消并回收"机制(`Future.cancel()` 只能取消还没开始跑的任务),要让 B 提前继续、A 在后台继续等配额而不阻塞整层,需要给 `run_work_node()` 引入可中断/可分离的配额等待机制——这是比本次改造大得多的另一个课题,不在本次范围内(需求文档"非目标"第 1 条已经声明"不做完整的数据流式调度器",这个限制是同一类取舍的自然延伸)。如果未来这个限制成为真实瓶颈,需要单独立项评估。
+
+## 7. 断点续跑兼容性——不需要新逻辑
+
+`run_work_node()` 已有的"已 `done` 节点短路返回持久化输出"逻辑对并发路径天然兼容,不需要改动:进程重启后重新调用 `run_graph()`,`topological_layers()` 重新计算分层,对每一层内的节点,`_run_one_node()` 里的 `run_work_node()` 调用会对"已完成"的节点立即返回持久化结果(几乎不占用线程池工作时间),对"未完成"的节点正常重新执行——同一层里有的节点秒回、有的节点真正重跑,线程池天然处理这种混合情况,不需要额外判断"这层要不要重跑"。
+
+## 8. 可观测性
+
+`_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 条:复用现有事件、靠时间戳重叠反推,不新增事件类型,保持改动面最小)。
+
+## 9. 并发度默认值
+
+`_DEFAULT_MAX_WORKERS = 4`。理由:现在观察到的真实 `graph_manifest` 规模是个位数节点,4 是一个不会让"一次任务同时拉起过多 `claude` 子进程"的保守值,同时足够覆盖当前能想到的 fan-out 场景(比如 Planner 未来 PRD 阶段 3-5 个场景并行)。作为 `run_graph()` 的一个具名参数(而不是硬编码常量或环境变量),调用方可以按场景覆盖,但不新增管理后台配置项(需求文档非目标已声明不做这类配置暴露,YAGNI——等真的有需要按项目/按 Org Agent 类型区分并发度时再加)。
+
+## 10. 测试计划(对应需求文档验收标准 1-7)
+
+| 验收标准 | 测试方式 |
+|---|---|
+| 1. 同层节点确实并发触发 | mock `run_fn`,每次调用记录 `(node_id, 开始时间, 结束时间)` 到共享列表(用锁保护这个记录列表本身,测试代码自己的锁,不是被测代码的),断言同层两个节点的时间区间有重叠 |
+| 2. 单测断言并发+层间顺序 | 同上,另外断言"下一层任意节点的开始时间 >= 上一层全部节点的结束时间"(层屏障) |
+| 3. 断点续跑:已完成节点不重跑 | 第一次调用 mock 让某层其中一个节点抛异常模拟中断(`WorkGraphLayerError` 传播出来),检查 DB 里其余节点确实落库为 `done`;第二次调用同一个 `run_graph()`,断言 `run_fn` 没有被再次调用去跑那些已 `done` 的节点(用调用计数 mock 断言) |
+| 4. SQLite 并发写入无异常 | 用真实 `:memory:`(或临时文件)SQLite 库跑一个多节点并发层,不 mock `conn`,跑多轮(并发问题往往概率复现)确认不抛 `database is locked` |
+| 5. worktree 并发创建互不冲突 | 真实临时 git 仓库,多线程调用 `create_worktree`,断言 `git worktree list` 输出的路径数量和唯一性符合预期 |
+| 6. 配额限制 + 正常完成共存 | mock 一个节点的 `run_fn` 触发 `_UsageLimitSignal` 且配置极短的超时(依赖注入的 `sleep`/`now`,不真的等待),另一个节点正常返回,断言正常节点的输出被保留在最终 `outputs` 里,且整体调用最终正确抛出/传播配额相关信号 |
+| 7. 既有测试兼容性 | 跑 `src/test_work_graph_orchestrate.py`、`src/test_work_graph_execute.py`、`src/test_e2e_pipeline.py` 全部既有用例,逐个确认通过;对断言"严格串行"这一实现细节的用例(如果存在)需要单独识别并更新断言方式(改成断言"层间顺序"而不是"节点间顺序") |
+
+## 11. 实施步骤建议(拆成可独立验证的小步)
+
+1. `topological_layers()` 新增 + `topological_order()` 改写成基于它的薄封装,跑既有测试确认零回归(不改 `run_graph()`,先证明分层算法本身正确)。
+2. 引入 `_LockedConn`,先只在新代码路径里用,不碰现有串行路径。
+3. `create_worktree()` 加锁,写第 10 节测试用例 5 验证是否真的需要(哪怕结论是"其实不需要",锁本身开销极小,可以保留作为防御性设计)。
+4. `run_graph()` 改造成层级调度(第 5 节的完整实现),单节点层走同步路径、多节点层走线程池路径。
+5. 补全第 10 节全部测试用例。
+6. 找一个真实存在多节点并行结构的 `graph_manifest` 场景做一次端到端验证(如果当前还没有这样的真实场景,构造一个人工测试场景即可,不强求等 E 落地后才能验证 G 本身)。
+
+## 12. 开放问题的最终决定
+
+回答需求文档 `docs/work-graph-parallel-execution-requirements.md` §8 列的开放问题:
+
+- **`topological_layers()` 实现方式**:新增函数,`topological_order()` 改写成基于它的薄封装(第 2 节已给出实现)。
+- **`max_workers` 默认值**:4,作为 `run_graph()` 的具名参数,不做管理后台配置项(第 9 节)。
+- **FR-5 异常聚合规则**:层内全部完成后统一收集,抛出携带完整 `[(node_id, exception), ...]` 清单的 `WorkGraphLayerError`(第 5 节),调用方可以按需检查列表内容做进一步判断(比如是否全是可重试的配额问题)。
+- **`execution_event` 是否需要新事件类型**:不需要,复用现有事件+时间戳重叠反推(第 8 节)。
+- **worktree 加锁粒度**:只锁 `create_worktree()` 内部 `git worktree add` 这一条命令,不锁整个节点执行(第 4 节)。
diff --git a/docs/work-graph-parallel-execution-requirements.md b/docs/todo/work-graph-parallel-execution-requirements.md
index be99657..6f70eba 100644
--- a/docs/work-graph-parallel-execution-requirements.md
+++ b/docs/todo/work-graph-parallel-execution-requirements.md
@@ -2,7 +2,7 @@
**日期**: 2026-08-04(2026-08-05 更新:已确认,标记为核心优先级,进入详细设计阶段)
**性质**: 需求文档(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()` 的具体实现、线程池调度器接口、锁的粒度)留到需求确认后单独产出。
-**状态**: ✅ 已确认。验收口径固化为一句话(2026-08-05 用户原话):"只要 Work Graph 规划了 fan-out、fan-in 的并行节点,就可以在节点触发的时候并行执行"——这与本文档 FR-1/FR-2 一致,无需改动功能需求本身。下一步进入详细设计阶段,见 `docs/todo/graph-engineering-open-items.md` 条目 G。
+**状态**: ✅ 已确认。验收口径固化为一句话(2026-08-05 用户原话):"只要 Work Graph 规划了 fan-out、fan-in 的并行节点,就可以在节点触发的时候并行执行"——这与本文档 FR-1/FR-2 一致,无需改动功能需求本身。详细设计已产出,见 `docs/work-graph-parallel-execution-design.md`。
## 1. 背景
diff --git a/docs/work-graph-time-travel-debugging-requirements.md b/docs/todo/work-graph-time-travel-debugging-requirements.md
index ffabb5a..bf7a295 100644
--- a/docs/work-graph-time-travel-debugging-requirements.md
+++ b/docs/todo/work-graph-time-travel-debugging-requirements.md
@@ -4,14 +4,14 @@
**性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应 `docs/todo/graph-engineering-open-items.md` 条目 D 的确认结果——"time travel 可以立项"。
**状态**: 草案,待评审。
-**命名消歧(仿照 CLAUDE.md「两个 Supervisor」的先例)**:本文档说的"Time-Travel 调试"跟 `docs/supervisor-auto-rollback-design.md` 描述的 Deploy Supervisor 自动回滚是两件完全不同的事——不要混淆:
+**命名消歧(仿照 CLAUDE.md「两个 Supervisor」的先例)**:本文档说的"Time-Travel 调试"跟 `docs/done/supervisor-auto-rollback-design.md` 描述的 Deploy Supervisor 自动回滚是两件完全不同的事——不要混淆:
| | Time-Travel 调试(本文档) | Deploy Supervisor 自动回滚 |
|---|---|---|
| 回滚的对象 | 某一次任务的 Work Graph 执行过程(某个 `work_node_execution` 的中间状态) | graphlemon 自身的代码版本(git commit) |
| 触发者 | 人工调试意图("我想看看如果这一步换个输入会怎样") | 自动健康检查失败信号 |
| 影响范围 | 单个 task 的执行历史,不影响其他任务 | 整个 graphlemon 进程的运行版本 |
-| 相关文档 | 本文档 | `docs/supervisor-auto-rollback-design.md` |
+| 相关文档 | 本文档 | `docs/done/supervisor-auto-rollback-design.md` |
## 1. 背景
diff --git a/src/schema.py b/src/schema.py
index fae17d6..1ca4610 100644
--- a/src/schema.py
+++ b/src/schema.py
@@ -276,7 +276,7 @@ CREATE TABLE log_retention_config (
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
--- 1.15 deployment (PH5, U-034, 2026-08-02 -- see docs/supervisor-auto-rollback-design.md)
+-- 1.15 deployment (PH5, U-034, 2026-08-02 -- see docs/done/supervisor-auto-rollback-design.md)
CREATE TABLE deployment (
deployment_id TEXT PRIMARY KEY,
git_commit_hash TEXT NOT NULL,
diff --git a/supervisor/deploy_supervisor.py b/supervisor/deploy_supervisor.py
index b33e0cd..d26fb73 100644
--- a/supervisor/deploy_supervisor.py
+++ b/supervisor/deploy_supervisor.py
@@ -1,7 +1,7 @@
"""Deploy Supervisor process skeleton (U-036).
Independent OS process (started via `python3 -m supervisor.deploy_supervisor`),
-pure stdlib, no LLM CLI dependency -- per docs/supervisor-auto-rollback-design.md
+pure stdlib, no LLM CLI dependency -- per docs/done/supervisor-auto-rollback-design.md
§2's "three independent processes, no shared failure domain" principle:
Deploy Supervisor crashing must not affect Live/Candidate, and Live/Candidate
crashing must not affect Deploy Supervisor's own ability to keep observing
diff --git a/supervisor/isolation/__init__.py b/supervisor/isolation/__init__.py
index 285a380..188c425 100644
--- a/supervisor/isolation/__init__.py
+++ b/supervisor/isolation/__init__.py
@@ -3,7 +3,7 @@ Deploy Supervisor (the independent process that handles blue-green deploy +
automatic rollback for graphlemon's own self-modification -- not to be
confused with the "Supervisor(调度子 Agent)" role documented in
graphlemon_architecture.md, which is an in-process monitoring node inside
-the Coder Work Graph. See docs/supervisor-auto-rollback-design.md for the
+the Coder Work Graph. See docs/done/supervisor-auto-rollback-design.md for the
disambiguation.
Adapted 2026-07-30 from graphloop (`/Users/yibo/Documents/lemonAI/graphloop`,
@@ -16,8 +16,8 @@ source workspace instead of the isolated copy; `change_manifest`'s
symlink-rejecting isolated-tree snapshot plus the caller's manifest check
caught it before anything false was reported as evidence).
-See docs/supervisor-design-comparison.md and
-docs/supervisor-auto-rollback-design.md for why this code lives here and how
+See docs/done/supervisor-design-comparison.md and
+docs/done/supervisor-auto-rollback-design.md for why this code lives here and how
it plugs into the blue-green deploy/rollback state machine.
Module map (unchanged from graphloop):