-
…a664-main-checkout-drift
-
…9856-main-checkout-drift
-
Two real findings from today's live self-hosting run, surfaced while diagnosing why bfd3ee82/71868db6 were blocked: 1. trigger_org_node/trigger_org_node_structured use a fixed wall-clock subprocess.run(timeout=...) -- a long-running-but-genuinely-progressing agent call gets killed just for taking longer than a guessed constant (300s), which is exactly what happened to bfd3ee82's Validator planning call. Per an already-established (but never actually implemented) decision: an agent call should never be judged "stuck" by total duration alone, only by an extended period with no activity AND no error. New requirements doc: docs/todo/agent-call-liveness-not-fixed-duration-timeout-requirements.md. 2. task_execution.py::_mark_blocked's catch-all dumps anything outside a small exception whitelist into blocked_reason='execution_error' -- including ConcurrencyLimitReachedError, which isn't an error at all, just normal backpressure. Boss Agent needs the system to emit distinct, meaningful reasons at the point of failure, not reverse-engineer root cause from a generic bucket -- added as FR-7 to the Boss Agent doc. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Requirements doc (BRD/PRD level) for a new org-level role that monitors blocked_pending_human tasks, does real diagnosis (not blind retry), and acts within an explicit authorization boundary instead of always waiting on a literal human -- generalizing the planner_reviewer precedent (an autonomous judgment agent already replacing a human review gate) to this surface. Motivated by today's real incident: 4 tasks blocked with the same generic execution_error reason but three genuinely different root causes (stale process, legacy narrative-output format, a real concurrency bug), all diagnosed by hand. Also documents watch_resume.py's real concurrency-race flaw as a requirement input (FR-6) so the eventual implementation doesn't repeat it. Deliberately does not pick the autonomy boundary per blocked_reason -- left as an open question for explicit user sign-off, not something Planner should infer silently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
v_task_detail's org-strip only ever covered planner/coder/validator -- no way to see planner_reviewer's verdict trail from the task detail page at all, only from v_graph_console. Rather than maintain two parallel, drifting implementations of "org node status for this task", v_task_detail now reuses the same 4-stage Org meta-graph component (renderOrgMetaGraph/mountOrgMetaGraph) v_graph_console already has. mountOrgMetaGraph/renderOrgMetaStageDetailHtml gain an optional enableNodeClick option so v_task_detail's usage gets real per-node live log-tail expansion (wired through the existing activeLogViewers/closeAllLogViewers/mountLogViewer machinery) while v_graph_console keeps its existing read-only snapshot behavior unchanged. ORG_NODE_DETAIL_RE now also accepts planner_reviewer so deep links into the 4th stage are reachable; landing on one auto-expands and scrolls to it (focusOrgMetaStage), matching the old focusOrgNodePanel's behavior. Dashboard's own 3-node org-strip is untouched. Deleted the now-dead renderTaskDetailOrgStripHtml/renderOrgComboPanelContent/ wireOrgComboArtifactClicks/focusOrgNodePanel. node frontend/test_graph_console_smoke.js passes (extended with coverage for the regex widening and the enableNodeClick threading), independently re-verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real prod incident (2026-08-13): two live tasks crashed with sqlite3.IntegrityError UNIQUE constraint failed: work_graph_run.task_id right after a planner_reviewer FAIL/INCONCLUSIVE verdict. ux_work_graph_run_active (just added this session) allows only one non-terminal work_graph_run row per task_id, but a rejected review's retry called plan_documents() with no run_id, which always INSERTs a fresh row -- the rejected attempt's own row is never marked done/failed just because its content was rejected (Phase 2c: one run spans a task's whole lifecycle). plan_documents() gains append_to_run_id, threaded into plan_work_graph()'s existing append machinery (same convention implement_code() already uses for Coder). _review_planner_output_with_retries() now carries prior_run_id across retry attempts so a re-plan appends into the still-open run instead of colliding with it. The first attempt (no prior run) is unaffected. New test drives a real FAIL-then-PASS retry through the actual plan_work_graph() INSERT path and the real ux_work_graph_run_active index -- confirmed it reproduces the exact production traceback on the pre-fix code and passes post-fix. 801/801 src tests, 127/127 supervisor tests, both independently re-verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real self-hosting DB (2026-08-13) has 10 task_ids with multiple non-terminal work_graph_run rows each (legacy per-org_node-row leftovers from before the merged-graph architecture) -- the migration's final CREATE UNIQUE INDEX ux_work_graph_run_active ON work_graph_run(task_id) crashed against this real data (IntegrityError), and because executescript() commits each DDL statement individually, the crash left the live DB in a partially-migrated state: org_node column dropped, but the index never created, with the old idempotency check (`if "org_node" not in cols: return`) then permanently skipping any further attempt to create it. Adds _reconcile_duplicate_active_work_graph_runs(), which marks all but the most-recently-created non-terminal run per task_id as 'failed' (not 'blocked' -- the index's WHERE clause treats 'blocked' as still-active, which was the first fix attempt and still failed) before the index is created. Recency is ranked by rowid, not run_id: real data has started_at=NULL on every legacy row, so a run_id tiebreak would pick among duplicates by UUID string value, unrelated to actual insertion order. Also splits the idempotency check into column-drop and index-creation halves so a database already in the partially-migrated state (like the live one) gets the missing index created on the next run instead of being skipped. Dry-run verified against a copy of the live 36-row production DB before applying to the real ~/.graphlemon/graphlemon.db (backend_server was down for this, no concurrent writers). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…0cd7-main-checkout-drift
-
…6f22-main-checkout-drift
-
Real production gap found while resuming stuck self-hosting tasks: .claude/agents/planner_reviewer.md was never deployed after Phase 1 (Org Graph unification, commit ea19c7d) added the planner_reviewer role -- role.json/SOUL.md/AGENTS.md all existed, but nobody re-ran agent_profile.deploy_agent_definitions() against this live checkout after adding the role, so every task reaching the planner_reviewer review stage failed immediately with "claude --agent 'planner_reviewer' not found" and landed on blocked_pending_human/execution_error. This has silently been blocking every task since Phase 1 shipped. Re-running deploy_agent_definitions() also caught coder.md/validator.md drifting out of sync with their role.json skills lists (missing design-dynamic-work-graph, work-graph-node-review, skill-defect-remediation, unknown-scope-defect-sweep -- accumulated from several sessions' worth of role.json edits that never got redeployed). planner.md was already in sync. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
This is a point-in-time snapshot doc (dated 2026-08-03) that's explicitly meant to be hand-edited in place as gaps close, not rewritten -- three entries had gone stale relative to work landed since: the Planner-output review gate (Phase 1, already done back in early August but never marked here), the Work Graph node execution deblackbox migration, and the new-Goal auto-dispatch scheduler loop. Updated each to ✅ and refreshed the "most worth watching" summary and the self-hosting task table row accordingly -- the still-open Deploy Supervisor poll_fn gap is called out explicitly as a *different*, still-unsolved closed loop so it doesn't get conflated with the now-solved scheduler entry point gap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Removed the "disabled" framing from docs/done/validator-test-script- narrative-output-issue.md (moved todo/ -> done/, root cause identified, structural fix landed, spot-check clean) -- the mechanism it described (_CANONICAL_DOC_CHAIN, e2e_pipeline._VALIDATOR_MANIFEST) no longer exists in production code, superseded by the 2026-08-08 self-planning rewrite and Org Graph unification Phase 2. Planner's and Validator's self-planning catalogs already carry test_script as a suggested (not required) option; _CODER_NODE_CATALOG was the one gap -- added a matching entry there (mechanical check_script_compiles judgment, same as Validator's). All three org agents now decide per-goal whether this node is worth planning, with no remaining "this node type is globally off" switch anywhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
org-graph-unification-and-universal-self-planning-requirements.md, org-graph-unification-phase2-universal-self-planning-design.md, scheduler-new-goal-dispatch-requirements.md, and work-graph-node-execution-deblackbox-requirements.md all moved docs/todo/ -> docs/done/ (already done in earlier commits via git mv) -- this fixes the remaining docs/todo/ path references to these four files across the docs that cross-link them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Spot-check (real API spend, user-authorized) of whether the historical "narrative text standing in for real output" failure mode (docs/todo/validator-test-script-narrative-output-issue.md) recurs now that Coder self-plans its implementation graph (Org Graph unification Phase 2). Built a non-self-target temp repo with a real rate-limiter module containing a planted bug, and a goal requiring Coder to read it and produce an audit report -- the same "explore then produce content" trigger condition the original incident's root-cause analysis identified. Ran the real production path (plan_work_graph + run_graph) with RUN_REAL_AGENT_E2E=1. Result: not reproduced. The self-planned node's content was a real, line-cited audit report (correctly found the planted bug plus four other genuine issues), not narration standing in for missing work -- despite the Coder catalog's "impl" entry having no suggested_judgment, the planning LLM voluntarily attached its own narrative-detection judgment, closely mirroring work-graph-node-review's methodology. Recorded as a new timeline entry with an explicit single-run-confidence caveat and a concrete next step (_NODE_REVIEW_JUDGE_PROMPT wired directly into the catalog) if a real recurrence shows up later. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Implements docs/done/work-graph-node-execution-deblackbox-requirements.md end to end. The three documented call sites (e2e_pipeline.py:: _check_prd_consistency, work_graph_orchestrate.py::_llm_judgment_verify_fn, work_graph_orchestrate.py::_make_run_fn) all used to render a one-off Dynamic Workflow .js script and run it via `claude -p 'Run /<name>...'` purely to get "agent identity + schema-shaped output" -- a workaround for --json-schema silently conflicting with --agent (05 §5.4.1.1). That workaround was never actually necessary: work_graph_plan.py:: plan_work_graph() solved the identical problem from day one with a flat `claude --agent ... -p ...` call + ```json-fence extraction from free text, already validated in production. New org_node_trigger.trigger_org_node_structured() unifies all three call sites onto that same flat-call approach. extract_json_payload() generalizes work_graph_plan.py's fence-extraction algorithm; _extract_graph_manifest is now a thin wrapper over it. Two more call sites not in the original design doc (written before they existed) were found and migrated too: _run_reflection_perspective and _fix_skill_for_node (Phase 2a's Reflection-review/Fix-Skill). work_graph_fork.py needed no changes -- it only reaches _make_run_fn indirectly via run_graph(), so it benefits automatically. FR-4's real-agent verification (RUN_REAL_AGENT_E2E=1, mandatory, not optional -- src/test_org_node_trigger_e2e.py) confirmed persona loading, --model override, and JSON-fence extraction all work against a real model. write_workflow_script/execute_work_graph/execute_work_graph_with_usage_ limit_retry are confirmed to have no remaining production callers; docstrings mark them historical, the file is kept (not deleted). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
New src/new_goal_dispatch.py (find_queued_tasks() + dispatch_once(), mirroring task_reconciliation.py's shape but deliberately a separate module/loop -- scheduler-task-reconciliation-design.md §7 already established the "don't merge into one mega scheduler loop" principle, this follows it). Wired into backend_server.py::main() as its own daemon thread with --new-goal-dispatch-interval-seconds (default 45s, shorter than reconciliation's 300s to satisfy the near-real-time acceptance bar) and --disable-new-goal-dispatch-loop. Also added the matching --disable-new-goal-dispatch-loop flag to supervisor/deploy_stage.py::_launch_candidate() -- same reasoning as the existing --disable-reconciliation-loop flag there: a health-checking deploy candidate runs against a copy of the production DB and must stay passive until Deploy Supervisor decides it's live, otherwise it would start autonomously dispatching real Goals off stale/copied state. User-confirmed 2026-08-13: full auto-dispatch, not a conservative default-disabled rollout -- this was previously flagged as a standalone "changes the system's own autonomy boundary" decision requiring separate review (docs/done/scheduler-new-goal-dispatch-requirements.md), now resolved. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
FR-5a: a new, standalone four-stage Org meta-graph (renderOrgMetaGraph and friends) rather than extending the existing 3-node renderOrgGraphTopology (left untouched, it's pinned by test_graph_console_smoke.js). Each stage expands in place: planner/coder/validator reuse renderGraphVisualizerPanel unmodified against that stage's latest run (identified via org_nodes_involved, Phase 2c's replacement for the removed single-value org_node column); planner_reviewer has no self-planned work_graph_run, so it shows its planner_review_verdict execution_event history instead (new task_query.py::_planner_review_events_for_task(), mirroring the existing _loop_back_events_for_run() pattern). FR-5b: an attempt_number-ordered, click-to-reveal feedback timeline built from loop_back_events, which was already being fetched but never rendered. New frontend/test_org_meta_graph_smoke.js (35 assertions, same vm.Context sandbox convention as the existing 4 smoke tests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
New skills/work-graph-node-review/SKILL.md documents the methodology for judging a self-planned Work Graph node's output (real structured content vs. narrative prose standing in for it) -- distilled from the real narrative-output incident (docs/todo/validator-test-script-narrative- output-issue.md §6) and the conditional-not-assertive phrasing lesson already applied in _NARRATION_RETRY_HINT. Scoped explicitly against planner-output-review (Planner's fixed doc chain, task-level gate), qa-task-validation (overall task acceptance), and skill-defect-remediation (rewriting a Skill file itself) -- this one judges a single self-planned node's output, nothing broader. Wired into roles/planner_reviewer, roles/coder, roles/validator role.json skills lists. .claude/agents/*.md left untouched -- that's a deploy-time artifact compiled from role.json by agent_profile.deploy_agent_definitions(), not hand-synced (confirmed stale already: coder.md is missing an even earlier Phase 2a skill addition). This completes Phase 2c (§2.1/§2.2/§6/§7.1 all implemented; §7.2's analysis was completed earlier without needing a code change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
A merged graph's one run genuinely spans several org_nodes now (Planner/ Coder/Validator all append their own nodes into the SAME run) -- a single run-level org_node column was never accurate once Phase 2b/2c landed, and real per-node ownership already lives in work_node_execution.subagent_type. Schema: work_graph_run.org_node column removed; ux_work_graph_run_active narrowed from UNIQUE(task_id, org_node) to UNIQUE(task_id) WHERE status NOT IN ('done', 'failed') -- 'blocked' now counts as active on purpose, since resuming a blocked run always appends into the same row rather than inserting a fresh one. New migrate_drop_work_graph_run_org_node() handles already-populated databases, wired into backend_server.py's startup path. Call sites updated: work_graph_plan.py/work_graph_fork.py/ root_cause_trace.py's INSERTs; work_graph_run_query.py/task_query.py now return "org_nodes_involved" (derived from work_node_execution.subagent_type) instead of the removed single-value "org_node"; frontend/app.js's org-strip and run-list rendering updated to the list-based shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Validator's terminal verdict is now a real self-planned node (node_type= 'final_verdict', judgment.kind='mechanical', on_fail.action= 'dynamic_loop_back') appended into the SAME shared work_graph_run Planner and Coder already contribute to (plan_work_graph(..., append_to_run_id= run_id)), instead of a flat post-graph org_node_trigger call against a separate hardcoded Validator manifest. This is what lets a failing verdict genuinely trigger Reflection-review + Fix-Skill + a precise loop back to whichever real ancestor node is at fault, reusing the existing dynamic_loop_back machinery instead of a parallel retry loop. The 2026-08-06 hard gate (a real non-zero exit from Validator's own test script force-overrides the verdict to FAIL, Python-enforced not just prompted) is preserved via a build_prompt wrapper + mechanical check closure sharing mutable state. INCONCLUSIVE is now treated the same as FAIL (triggers a bounded retry via dynamic_loop_back) rather than accepted as a terminal result. _VALIDATOR_MANIFEST/_VALIDATOR_DOC_PROMPTS/_VALIDATOR_VERDICT_PROMPT/ _validator_prompt_builder are kept unused (same precedent as _CODER_PROMPT). org_node column removal (§2.1), index narrowing (§2.2), and the new work-graph-node-review skill (§7.1) remain for a later sub-phase. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Found during Phase 1 real end-to-end verification (2026-08-12): 3 independent real runs, 3 different goals, all hit the same known, unrelated-to-Phase-1 issue -- a self-planned benchmark/test_script node's own judgment rejecting narrative summaries, exhausting loop_back's max_attempts. One of the three ran against the real production backend_server after the FK migration fix (bd1404d), confirming the new Phase 1 status machine routes the failure correctly (blocked_pending_human with a real diagnostic trail) even when the underlying pre-existing bug fires before planner_reviewer is ever reached. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Updates the Phase 2 SDD and requirements doc to reflect that Phase 2a (86fc0bb) and Phase 2b (7c46a2f) are done, not just designed. Records the concrete deviations/fixes found during implementation that the original design didn't anticipate: the run_id=None bug in the artifact-reuse resume path, the missing needs_worktree=True on Coder's run_graph() call, why §2.2's index narrowing needed to move from "maybe safe after 2b" to "actually needs 2c", and the test-fixture check_same_thread=False gap concurrent merged-graph layers exposed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Implements org-graph-unification-phase2-universal-self-planning-design.md §2.3, §3, §6 -- Coder's implement_code() no longer runs one hardcoded synthetic "implement" node; it self-plans via plan_work_graph(conn, "coder", ..., append_to_run_id=run_id), appending its own nodes onto Planner's existing work_graph_run instead of creating a separate one. - work_graph_plan.py: per-org_node suggested node-type catalogs (_CODER_NODE_CATALOG/_VALIDATOR_NODE_CATALOG, "starting points, not requirements" same as _PLANNER_NODE_CATALOG), new judgment/on_fail/ promotes_to_artifact fields in the generic planning prompt. New plan_work_graph(..., append_to_run_id=...) parameter: merges newly planned nodes into an existing run (node_id collision -> raise, never silent overwrite), returns new_node_ids so callers can avoid reprocessing nodes an earlier stage already promoted to artifacts. New shared dispatch_build_prompt() for graphs mixing Planner-shaped (artifact_type) and Coder-shaped (task_prompt) nodes side by side. - e2e_pipeline.py: _write_completed_node_artifacts() now also promotes nodes declaring promotes_to_artifact (not just Planner's artifact_type nodes), with an only_node_ids scope so a merged-graph caller doesn't re-touch artifacts an earlier stage already wrote. implement_code() reworked around self-planning; code_diff stays Python-computed (a real git diff, not any node's own content -- no node can know the diff ahead of running it), test_report is promoted from whatever the self-planned graph declares, falling back to a synthesized concatenation if it declares none. needs_worktree=True now passed to run_graph() for Coder (found missing entirely during this rework -- harmless with the old single-node manifest, a real 03-C §3.6 gap once concurrent code-writing nodes are possible). Also fixed a real bug found while wiring this up: _review_planner_output_with_retries()'s "reuse existing artifacts" branch hardcoded run_id=None, even though every real artifact row already has a valid produced_by_run_id -- Coder had nothing to append into on that resume path. - work_graph_fork.py: _reconstruct_build_prompt now dispatches per-node (via the new shared dispatch_build_prompt) instead of requiring the whole graph to be one uniform shape -- a real regression Phase 2b would otherwise have introduced silently, since a forked merged graph can now genuinely mix both node shapes. Sequencing note (unchanged from Phase 2a's own finding): work_graph_run's org_node column removal and the ux_work_graph_run_active index narrowing (design doc §2.1/§2.2) stay deferred to Phase 2c -- Validator still creates its own separate run until Validator also appends into the shared one, so narrowing the index to task_id-only now would collide with it. python3 -m unittest discover -s src (752 tests) + discover -s supervisor -p "test_*.py" (126 tests) both green, zero regressions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
migrate_add_reviewing_planner_output_status() rebuilds task (create-copy- drop-rename, SQLite's only way to change a CHECK constraint), but task is a parent table referenced by work_graph_run/artifact/execution_event/ bug_record. DROP TABLE task under PRAGMA foreign_keys=ON (schema.connect()'s default) trips FK enforcement the instant any child row exists -- unit tests never seeded a referencing child row, so this never surfaced until a real restart against the live production database (months of real history). Found and fixed live: no data loss (INSERT INTO task_new already succeeded before the DROP failed; verified the two tables held byte-identical rows, cleaned up the stray task_new, backed up the db, re-applied the fixed migration, verified via PRAGMA foreign_key_check, then restarted the real process against production and confirmed a real end-to-end task run through the new Planner -> planner_reviewer -> Coder -> Validator flow). Fix follows the migrate_relax_org_node_constraints() precedent already in this file: PRAGMA foreign_keys=OFF around the rebuild, PRAGMA foreign_key_check after, ON in a finally block. New regression test seeds one referencing row per child table so this class of bug can't pass silently again. Also adds real (non-mocked) coverage for the planner_reviewer role in test_e2e_pipeline_e2e.py: one real call confirming it flags real narrative- text repro fixtures, one confirming it passes real structured content. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Implements org-graph-unification-phase2-universal-self-planning-design.md §4 + §5, the engine-extension sub-phase (Phase 2a) -- no org-flow callers changed yet, this is the underlying capability. - _run_one_node() now resolves subagent_type/skill_ref per-node (node.get with the run-level org_node/skill_id as fallback), threaded through _ensure_node_row/_make_run_fn/_resolve_node_verify_fn -- a node's own judgment.kind="llm" call runs under its own declared identity, not always the graph-level default. Found and fixed a real gap surfaced by writing this: run_graph() only ensures the run-level default skill_id exists as a placeholder skill (work_node_execution.skill_ref's FK target) -- a node declaring its own skill_ref needs the same guarantee or the first INSERT for that skill_id fails FOREIGN KEY constraint. - New on_fail.action="dynamic_loop_back": Reflection-review (_judge_root_cause_node) runs three real parallel perspectives (content/ connectivity/history) under the Validator identity, each picking a target_node_id from the failing node's real ancestors plus a defect_kind (content_error vs skill_defect), majority-voted by _tally_reflection (own small Counter-based implementation, not imported from root_cause_trace.py -- that module already imports this one). A runtime ancestor check in _run_one_node backs up _tally_reflection's own degradation guarantee. - skill_defect triggers Fix-Skill (_fix_skill_for_node) before the loop back fires: reads the target node's declared skill_ref, builds a rewrite prompt from what was asked/produced/why it failed/prior loop history, runs it under the Validator identity (user-confirmed via AskUserQuestion), then calls the existing but -- until now -- never actually invoked by any production path bug_record.create_bug_record() + skill_update.update_skill(), against skill_query._REPO_ROOT (never target_folder_path). Logs a real 'skill_iterated' execution_event (a CHECK-enum value that has existed since the original U-001 schema but was never written before). Rate-limited to _MAX_SKILL_FIXES_PER_RUN_PER_SKILL rewrites of the same Skill per run. - New total-attempts-per-source-node loop_limit guard alongside the existing per-(target,source) one -- dynamic_loop_back can pick a different target each round, which would otherwise dodge the per-pair cap indefinitely. New settings_config key shape "{org_node}:{source_node_id}:total", no interface change needed there. - New skills/skill-defect-remediation/SKILL.md (Validator-only), wired into roles/validator/role.json. python3 -m unittest discover -s src (741 tests) + discover -s supervisor -p "test_*.py" (126 tests) both green, zero regressions. Deferred to Phase 2b (per design doc's own sequencing note): §2.1/§2.2 (work_graph_run.org_node removal + ux_work_graph_run_active index narrowing) -- narrowing that index now, before the append-to-existing-run mechanism lands, would break the current production flow, since Planner's and Coder's work_graph_run rows both stay non-terminal indefinitely on the happy path today (only Validator's own graph ever reaches 'done'). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>