diff --git a/.claude/agent-memory/planner/MEMORY.md b/.claude/agent-memory/planner/MEMORY.md
index c34a7ff..c587ff5 100644
--- a/.claude/agent-memory/planner/MEMORY.md
+++ b/.claude/agent-memory/planner/MEMORY.md
@@ -2,3 +2,4 @@
- [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
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
new file mode 100644
index 0000000..9e8b2ac
--- /dev/null
+++ b/.claude/agent-memory/planner/project_artifact_overview_click_through_bug.md
@@ -0,0 +1,39 @@
+---
+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
+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:
+
+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.
+2. The router (`frontend/app.js:77-132`, `routes` object + `render()`) does exact-string
+ lookup on `window.location.hash` (`routes[hash]`), with only two regex-based dynamic
+ routes today: `TASK_DETAIL_RE` (`#/tasks/:id`) and `SKILL_AUDIT_RE`
+ (`#/skills/:id/audit`). There is **no query-string parsing anywhere** in the router —
+ a hash like `#/artifacts?artifact_id=xxx` would fail `routes[hash]` *and* both regexes
+ and fall through to "Not found". Fixing this bug requires extending routing (either a
+ new `#/artifacts/:id`-style dynamic route matched the same way as `TASK_DETAIL_RE`, or
+ adding query-string stripping/parsing to `render()` before route lookup) — it is not
+ just a matter of adding an `href`.
+3. `mountArtifactBrowser()` (frontend/app.js:1582) already has the exact mechanism needed
+ to *display* a specific artifact once you have its id — `showPreview(artifactId)` sets
+ `selectedArtifactId` and fetches `/api/artifacts/:id` — but it is only ever invoked from
+ a click on `li[data-artifact-id]` inside `refreshList()`, never from a URL param. The
+ comment at app.js:1353-1356 ("v_artifact_browser and v_task_board don't parse URL query
+ params today ... out of scope") was written about the *"查看全部"* link specifically
+ (intentionally unfiltered) — it is not evidence that per-item deep-linking is already
+ supported or was rejected; don't misread it as covering this case.
+
+**How to apply**: when writing the SDD for this task, the technical design must cover (a)
+adding `data-artifact-id` + click → hash navigation in `renderArtifactSummaryList`, (b) a
+route-level change (new dynamic route or hash query-parsing) so `#/artifacts` can carry an
+artifact id, and (c) `mountArtifactBrowser` reading that id on mount and calling the
+existing `showPreview()`/`refreshList()` flow to preselect it — reusing `c_artifact_get` /
+`GET /api/artifacts/:artifact_id` as-is, no new content-preview logic. The "查看全部" links
+(app.js:1387-1390) must keep pointing at plain `#/artifacts` / `#/tasks` — do not add the
+id param to those.
diff --git a/docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).diff-with-graphlemon.md b/docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).diff-with-graphlemon.md
new file mode 100644
index 0000000..69570e4
--- /dev/null
+++ b/docs/reference/Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).diff-with-graphlemon.md
@@ -0,0 +1,68 @@
+# 对照笔记:《Graph Engineering with Claude: 14-Step roadmap》vs graphlemon 实际代码
+
+- **对照对象**:[Graph Engineering with Claude_ 14-Step roadmap from 0 to graph architect (Full Course).md](./Graph%20Engineering%20with%20Claude_%2014-Step%20roadmap%20from%200%20to%20graph%20architect%20(Full%20Course).md)(本文简称"14 步版")
+- **和已有对照的关系**:`docs/graphlemon_architecture.md` §10 在 **2026-07-29(设计阶段,尚未开始实现)** 就对 14 步版做过一轮逐条映射,并在 §10.1 决策"Work Graph 执行层直接复用 Claude Code 原生动态工作流"。当时是设计意图,不是已验证的实现。本笔记的定位不同:**现在全部 43 个 Unit 已实现并自举运行**,所以这次是拿 14 步版去对照 `src/work_graph_orchestrate.py`、`src/work_graph_execute.py`、`src/work_graph_plan.py` 的真实代码,检验当初 §10.1 的设计意图有多少真的落地、有多少在实现过程中被推翻或修正。
+- **状态**:讨论草稿,待后续会话继续展开。
+- **整理日期**:2026-08-04
+
+---
+
+## 0. headline:Work Graph 执行层并没有真的用上 Claude 原生 `parallel()`/`pipeline()`
+
+这是本次对照发现的最重要的一条,也是对 §10.1 原决策的一处实质性修正,值得放在最前面单独讲清楚。
+
+`src/work_graph_orchestrate.py` 的模块 docstring 直接写明了这个偏离,是有意为之:
+
+> Design: each node's actual work happens via its OWN one-node dynamic workflow script (`write_workflow_script` + `execute_work_graph`), **not one big multi-node script for the whole graph**. This is a deliberate trade-off, not the "amortize orchestration cost across many nodes in one `claude -p` call" pattern 03-C §3.5 originally motivated `execute_work_graph` with — REQ-06's breakpoint-recovery requirement (resume after a process restart without re-running already-`done` nodes) is only achievable with per-node, Python-level persisted state, which is fundamentally incompatible with wrapping the WHOLE graph in one opaque `claude` subprocess call Python has no visibility into mid-flight.
+
+再看 `run_graph()` 的实际实现(`work_graph_orchestrate.py:196-270`):
+
+```python
+ordered = topological_order(graph_manifest) # Kahn 算法,拒绝有环图
+outputs = {}
+for node in ordered: # 纯 Python 顺序 for 循环
+ ...
+ run_fn = _make_run_fn(...) # 每个节点单独渲染成一个"单节点"JS 脚本
+ output = work_node_run.run_work_node(...) # 一次一个节点地跑,串行
+ outputs[node_id] = output
+```
+
+也就是说:
+
+- **graph_manifest 里没有依赖关系的两个节点(本该 fan-out 并行)也是一个跑完再跑下一个**,没有任何线程池/并发调度。14 步版第 5 步 `parallel()` 描述的"N 个独立节点同时起飞"在 graphlemon 现在的执行引擎里完全没有发生。
+- 每个节点被单独渲染成一个**只有一次 `agent()` 调用的"单节点"JS 脚本**(`_render_node_script`),再通过 `write_workflow_script` + `execute_work_graph`(一次独立的 `claude -p "Run /