-
Real incident (2026-08-15, task b243dfe8): this exception's message used to truncate the failing response to [:2000] chars -- fine for the "process exited nonzero" error paths in this same file, where a short excerpt is enough, but actively counterproductive here: diagnosing a real production extraction failure with only the 2000-char preview, it was impossible to tell "the model's response was genuinely cut off mid-generation" from "this is just where the display limit happened to land" -- both look identical (text stopping mid-word) without the full text. No length cap now: this flows into task_execution._mark_blocked's str(exc), stored as a plain TEXT execution_event column with no practical size limit, so keeping the complete response is the honest, actually-diagnosable choice -- the same investigation that found this needed the full text and didn't have it. New test confirms a >2000-char failing response is preserved whole in the exception, not truncated. 20/20 in this file; every file today's fixes touch stays green (the only failures anywhere in the full suite are 15 pre-existing ones in a concurrent, unrelated task's own test file, confirmed identical before and after this change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds targeted coverage for the synthetic "__planning__" node work_graph_plan.py now records (real work_node_execution + log, task_query.py display-only prepend) -- the row exists and is done, the persisted work_graph_run.graph_manifest stays pure for execution/fork/append-mode, the log carries the real prompt and manifest, task_query surfaces it as nodes[0] with a clickable node_exec_id, and append mode doesn't duplicate it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-15, task b243dfe8), diagnosed live with the user after they pushed back on "is this over-constrained code" and asked why a check step needs to be this strict. The honest answer turned out to be the opposite of "too many rules": the old implementation UNDER-used what's available, approximating JSON-object-boundary-finding with a regex (_JSON_FENCE_RE, ```(?:json)?\s*(\{.*?\})\s*```) instead of using the real parser Python already ships. That regex is non-greedy, so it stops at the FIRST '}' anywhere in the response text that happens to be followed by whitespace and a closing fence. Content containing real code (a test script -- guaranteed to contain '{'/'}': dict literals, f-strings, function calls) can trivially contain that exact sequence incidentally, deep inside an otherwise perfectly valid JSON string value -- no malformed input required. Confirmed with fully valid, json.dumps()-produced input: {"content": "d = {\"a\": 1} ```\n..."} silently failed to extract, because the regex stopped right after `{"a": 1}` (legal spaces, then a literal ```), discarding the real, complete payload and raising StructuredOutputExtractionError on genuinely good content. This is exactly what happened to a real Coder node writing a real test script today. A regex fundamentally can't track nested/balanced braces correctly (a classical limitation, not a carefulness gap) -- fixed by using json.JSONDecoder.raw_decode() to attempt a real decode starting at every '{' in the text, which understands JSON's actual string-escaping/nesting grammar and is immune to this whole class of failure. Same fix applied to _extract_graph_manifest's own separate wrapper-key fallback scan (same regex, same vulnerability, different key shape). _JSON_FENCE_RE and its now-unused `import re` are removed -- less code, not more, and a strictly more correct mechanism. New ExtractJsonPayloadTest class (extract_json_payload had no direct unit tests before this) plus a wrapper-key-scan test on ExtractGraphManifestTest, both driving the exact valid-JSON-with-braces scenario that broke the old regex. 502/502 across every file this and today's other fixes touch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
Real incident (2026-08-15, task b243dfe8): a node that went through loop_back/retry_self gets a fresh "<base>-attemptN" work_node_execution row each retry (work_graph_orchestrate._next_exec_node_id, N counted from real loop_back_triggered events) -- but graph_manifest only ever names the BASE node_id, never any "-attemptN" variant. _write_completed_node_artifacts's lookup (topological_order yields base node_ids) always resolved to done_outputs[base] -- attempt 1's REJECTED content, the only one filed under the bare node_id -- permanently stale the instant a node was ever rejected-then-retried-then-accepted. First surfaced today because this task is the first to actually reach "retried, then eventually accepted" for a node with real downstream artifact promotion. Fixed by resolving each base node_id to its highest-numbered "-attemptN" sibling's content before the promotion loop runs -- the highest attempt number is the most recently executed one, which is only still 'done' in the DB if the graph actually moved past it (a still-rejected final attempt would have either retried again or raised, both already handled by the existing _failed_node_ids() exclusion, which operates on base node_ids and is unaffected by this remap since only the dict VALUE changes, not the key). New tests: one drives the real 3-attempt scenario and confirms the promoted artifact holds attempt 3's content, not attempt 1's (verified failing on the pre-fix code with the exact real content mismatch); another confirms the exhausted-retry exclusion still works correctly after the remap. 847 tests total in the suite (24 new ones landed via a concurrent, unrelated self-hosting run -- see conversation); the 8 files this fix and today's other fixes actually touch are 476/476 green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-13, task b243dfe8), diagnosed live with the user after they asked "what's the actual bug behind this block": the task's benchmark regression-check node kept looping back to itself, then hit "exceeded max_attempts=3" -- but the DB had no record of WHY the final (3rd) attempt was rejected, only attempts 1 and 2's feedback. This made the block impossible to properly diagnose: reading attempt 3's actual content (now possible thanks to the artifact-promotion fix) showed it had correctly grounded itself in the task's own approved SDD, unlike attempts 1/2 -- but whether the judge agreed was unknowable from the DB alone. Root cause: _run_layers()'s exceeded-limit branch raised ConsistencyCheckFailedError immediately, skipping the _record_loop_back_event() call a few lines below entirely -- sig.feedback (the judge's real, specific reason for rejecting THIS FINAL attempt, the one that actually exhausted the budget) was silently discarded. Every other rejected attempt's feedback gets persisted via that same call; only the budget-exhausting one didn't. Fixed by recording it before raising, same as every other rejection. New test drives a real 3-attempt exhaustion (max_attempts=2 -> attempts 1, 2, 3) and asserts all three loop_back_triggered events exist with attempt 3's own distinct feedback text -- confirmed it reproduces the exact real gap (2 events, not 3) on the pre-fix code. 823/823 tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-13, task b243dfe8), diagnosed live with the user after they correctly challenged the architecture: "every node should have its own output, and node-to-node communication IS input/output flowing through edges -- why doesn't a node have an artifact?" They were right to push on this. work_node_execution.output_payload (the real Work Graph data-flow layer) was never the problem -- it's populated the instant any node completes, exactly as expected. The actual bug was one level up: _write_completed_node_artifacts's OLD `only_node_ids` parameter skipped any node not in "this specific append call's own newly-planned nodes", assuming "not new to this call" means "an earlier stage already promoted it". That assumption breaks the instant an earlier call's OWN promotion attempt never actually ran (e.g. it crashed even earlier in that same call, before reaching this function) -- which is exactly what happened to b243dfe8 across multiple retries today: brd/prd/sdd/benchmark nodes kept completing successfully ('done', real content), but zero artifact rows and zero .graphlemon/runs/<task_id>/ files were EVER created, going back to the task's original 2026-08-04 run. Every later append-mode retry kept treating those nodes as "not new, must already be handled" and excluded them forever. Renamed to `new_node_ids`, semantics inverted to check the real source of truth: a node whose artifact_type already has a genuine artifact row for this run_id is skipped UNLESS the node itself is newly-planned by this call (which correctly allows a retry-loop's superseding content to still promote a fresh artifact over a rejected one -- the scenario the previous attempt at this fix broke, caught by the existing test_rejected_first_attempt_replans_into_same_run_not_a_second_row test). New tests cover both real scenarios directly, plus a fix to test_orphaned_preexisting_run_with_no_artifacts_appends_not_collides's own setup (the orphaned node needs to be seeded as already-done, matching the real incident, not left for run_graph() to execute for real a second time). 822/822 tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
…3d96-main-checkout-drift
-
User-reported gap: the alert banner only ever showed task.blocked_reason (a coarse category like 'execution_error') with zero indication of the actual cause -- the real message only existed in the matching task_status_changed execution_event's own detail text, with no path to the UI at all. Diagnosing today's several distinct real blockers required querying the DB directly each time, which the user correctly flagged as something the console itself should surface. task_query.get_task() now returns blocked_detail (the most recent blocking event's detail text) when status is blocked_pending_human. v_task_detail shows it as an expandable <details>/<summary> inside the alert banner (no JS wiring needed, matches the plain-vanilla-no-framework convention) so a long or multi-line real error message doesn't force itself into the banner's layout by default but is one click away in full, not truncated or hidden. Also investigated (not a bug, documented here for the record): two node logs the user found empty really are empty on disk -- execution_log_file rows exist but their target files were never created, for two nodes dating back to the task's original 2026-08-04 run, long before today's fixes. The log viewer's "暂无日志内容" is accurately reporting that state, not a rendering bug. 820/820 tests, frontend smoke passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-13, task b243dfe8): _normalize_graph_manifest() calls _validate_loop_back_targets() on the newly-planned manifest BEFORE it's merged with the existing persisted one (append mode). A new node's on_failure edge legitimately targeting an already-persisted node from an earlier append (e.g. a new verification node looping back to an earlier SDD node) was wrongly rejected as "does not exist in this graph_manifest" -- it genuinely doesn't exist in the brand-new manifest by itself, only once merged. _validate_loop_back_targets() gains additional_valid_target_ids, threaded from plan_work_graph()'s existing_manifest through _normalize_graph_manifest(). Ancestor-chain validation is skipped for a target in this set -- an already-persisted node's ancestry was validated when it was originally planned. New unit test for the validation function directly, plus a real end-to-end test through plan_work_graph() in append mode. 817/817 tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-13, task b243dfe8): run_work_node() unconditionally INSERTed a new execution_log_file row every call. Fine the first time, but if the process crashed/restarted between that INSERT and execute_work_graph() genuinely returning, work_node_execution.status is left at 'running' (not 'done', not a fresh node) and an execution_log_file row already exists for that node_exec_id. The next run_work_node() call for the SAME node_exec_id (REQ-06 requires this to be resumable) hit 'sqlite3.IntegrityError: UNIQUE constraint failed: execution_log_file.node_exec_id' and could never recover -- this exact bug is what task 2084590a's own (still-blocked) goal describes. Reuses the existing row (log_file_id, file_path) instead of re-inserting, same "resume must reuse what already exists" pattern this function already applies to git_worktree_ref a few lines below. New test confirms the exact production traceback on the pre-fix code and passes post-fix. Also adds docs/todo/2026-08-13-blocked-pending-human-incident-retrospective.md, today's consolidated retrospective + fix/execution-order plan for the 6 tasks that were blocking on various independent real causes. 815/815 tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-13, task b243dfe8): _handle_task_route's auto-continue allowlist was ("queued", "planning_brd_prd_sdd", "coding", "validating"). Org Graph unification Phase 1 (2026-08-08) added 'reviewing_planner_output' to task_execution._RESUMABLE_STATUSES, but nobody updated this separate allowlist to match -- the same exact bug the 2026-08-07 'validating' fix (see the existing comment right above this change) was supposed to have taught us to watch for, just one status later. Effect: manual_resume onto a task landing on 'reviewing_planner_output' flipped the DB status back and returned 200, but spawned no background thread. The task sat completely idle (zero new execution_event rows, no real subprocess) -- which looks identical to "still stuck" from the console, even though the actual underlying bug that originally blocked it (a separate, already-fixed issue) was long gone. Diagnosed live: resumed b243dfe8, watched it sit with no activity for 7+ minutes, found zero events past the resume's own status-change row and no claude subprocess running anywhere. New regression test mirrors the existing 'validating' one exactly. 814/814 tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
Requested by user after today's incident retrospective: a manually-assignable execution-order hint across a project's tasks, shown as a new "执行顺序" column on v_task_board. Serial steps are whole numbers (1, 2, 3); parallel siblings of one step share the same integer part as decimals (2.1, 2.2). Purely advisory -- nothing in the scheduler/dispatch loop reads it, it's a human/Boss-Agent-facing planning aid, not an enforcement mechanism. - schema.py: task.execution_priority REAL (nullable), migrate_add_task_execution_priority (plain ADD COLUMN, no CHECK constraint). Two earlier CHECK-rebuild migrations that also touch task (migrate_relax_org_node_constraints, migrate_add_reviewing_planner_ output_status) now call it as a prerequisite and include the column in their own rebuilt task_new, same "depends on a later additive column" pattern migrate_relax_org_node_constraints already used for migrate_add_work_graph_fork_columns. - task_query.py: list_tasks/get_task expose it; new update_task_execution_priority (unlike update_task_goal_text, NOT gated to status='queued' -- it's a display hint, not a planning input, so it stays settable on in-flight/blocked tasks too). - backend_server.py: PUT /api/tasks/:id now accepts execution_priority independently of goal_text on the same endpoint. - frontend/app.js: new editable column on v_task_board, PUTs on change. Also adds docs/todo/append-run-node-id-collision-across-org-nodes-requirements.md, a third real finding from today's retrospective (task 911861fe: Coder and Validator's suggested node catalogs both converge on node_type="test_script", so their self-planned node_ids collide inside a shared merged work_graph_run -- PlanningFailedError, uncaught by _ROUTABLE_EXCEPTIONS, falls into the same generic execution_error bucket FR-7 already flagged). 813/813 src tests, 134/134 supervisor tests, frontend smoke passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Real incident (2026-08-13, task 71868db6): ConcurrencyLimitReachedError isn't an error -- it just means this dispatch attempt found no free slot, which is normal, expected backpressure. Routing it through the same blocked_pending_human/ execution_error bucket as a genuine failure was itself a bug: it forced a task that should just wait its turn into looking like it needs human judgment. _check_concurrency() raises before any task.status transition happens for this attempt, so task_execution.py::_mark_blocked() now leaves status untouched (still 'queued' for a fresh dispatch) instead of blocking -- the next new_goal_dispatch tick naturally retries it, no human/Boss Agent involvement needed. Still logged as a new 'concurrency_wait' execution_event (new migrate_add_concurrency_wait_event_type, same accumulated-CHECK-enum pattern as the existing event-type migrations) so the wait stays auditable, not silently invisible. This is the first concrete instance of FR-7 in docs/todo/boss-agent-autonomous-blocked-task-arbiter-requirements.md (system should emit distinguishable blocked_reasons/non-error states at the point of failure, not dump everything into one generic bucket). 803/803 src tests, 134/134 supervisor tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…a664-main-checkout-drift
-
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>
-
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>
-
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>
-
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>
-
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> -
Phase 1 (planner_reviewer review gate + P0 resume-bug fix) shipped as ea19c7d, matching org-graph-unification-phase1-review-gate-design.md exactly (new task.status value, org role, _REQUIRED_ARTIFACTS narrowing, both bugs fixed). Moving that design doc and the P0 bug doc it subsumed to docs/done/, updating their status headers, and fixing the doc-path references left pointing at their old docs/todo/ location across agents/planner_reviewer/{SOUL,AGENTS}.md, src/schema.py migration docstrings, and the other Phase 2/deblackbox docs. Full suite green (731 tests) both before and after. Also ran Phase 2 SDD's §9 interface-reconciliation checklist against the real Phase 1 implementation now that it exists for real: all three items check out (run_id is included in plan_and_review_documents()'s return shape, the new task.status value is exactly reviewing_planner_output as assumed, and planner_reviewer's role.json doesn't pre-reserve a skill slot for Phase 2's new skill, which is fine -- a one-line addition when Phase 2c lands, not an incompatibility). Updated §9 to reflect this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Inserts Planner -> planner_reviewer -> Coder -> Validator into the fixed Org Graph flow (was Planner -> Coder -> Validator), per docs/todo/org-graph-unification-phase1-review-gate-design.md. A new planner_reviewer role independently judges whether Planner's BRD/PRD/SDD/ Benchmark output is real structured content (not narrative prose) before Coder is allowed to start, with a 3-attempt retry loop and a narration heuristic informed by the real narrative-output incident (docs/todo/validator-test-script-narrative-output-issue.md). Also fixes, in the same code path: - The P0 bug in task-execution-resume-assumes-planner-artifacts-exist.md: resuming a task no longer assumes sdd/prd/benchmark artifacts exist -- falls through to real replanning when they don't. - A fresh-run KeyError (not resume-specific) when a self-planned graph omits the prd/benchmark node types. - agent_profile.migrated_from_profile was NOT NULL, which broke as soon as a role with no legacy Hermes profile (planner_reviewer) was recorded. org_node_lifecycle._REQUIRED_ARTIFACTS["coder"] is narrowed from (brd,prd,sdd,benchmark) to (sdd,) -- the old 4-artifact gate never actually blocked anything since artifact_write.py sets validation_status='passed' unconditionally; planner_reviewer is the first real semantic gate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
… work, compress bloated incident log - Delete org-graph-as-work-graph-unified-loop-requirements.md: fully superseded by org-graph-unification-and-universal-self-planning- requirements.md; folded its one non-duplicated detail (the 2084590a benchmark-narration trigger incident) into that doc's background instead. - Split scheduler-production-polling-loop-{requirements,design}.md by completion status: FR-2 (in-flight task reconciliation, shipped) moves to docs/done/scheduler-task-reconciliation-{requirements,design}.md; FR-1 (new-goal auto-dispatch, still undecided) becomes a small standalone docs/todo/scheduler-new-goal-dispatch-requirements.md instead of staying bundled with a doc that's mostly done. - Compress validator-test-script-narrative-output-issue.md's six ad-hoc "第N轮更新" paragraphs crammed into one status line into a scannable timeline table; moved the task 2084590a benchmark evidence into §1 where the other repro examples live instead of leaving it stranded in the header. - implementation-gap-analysis.md: logged two new gaps found today (no pre-Coder review of Planner's output; node execution still depends on native Dynamic Workflow) plus the architecture.md §10.1 staleness itself, per that doc's own "edit in place" maintenance convention. - Updated the doc-path references left in src/task_execution.py and src/task_reconciliation.py docstrings to point at the new locations. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
work-graph-partial-artifact-persistence and work-graph-self-planning- and-loop-back (requirements + design) are all shipped and tested; move them out of docs/todo/ and fix up the docs/todo/*.md and src/*.py path references that pointed at their old location. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…76ae-main-checkout-drift
-
…4843-main-checkout-drift
-
ConsistencyCheckFailedError is now raised by both the PRD-consistency check and the test_script mechanical compile-check (07a0a27), but scheduler._blocked_reason_for still labeled every instance 'prd_consistency_check_failed', misleadingly blaming the PRD for test_script node failures. Distinguishes by node_id substring match (see docstring for the ponytail ceiling/upgrade path). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Mitigates the recurring test_script/validator_test_script narration issue (100% repro across 3 tasks, 6 nodes): the existing compile-error feedback buries its "not a narrative description" note under raw tracebacks, which wasn't enough to stop the model repeating narrative output on retry. Adds _NARRATION_RETRY_HINT as a separate, directive feedback item alongside the compiler detail. See docs/todo/validator-test-script-narrative-output-issue.md §6-7 for the full root-cause analysis and rationale. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…437e-main-checkout-drift
-
…d7f3-main-checkout-drift
-
Recovers tasks abandoned mid-flight (status in queued/planning_brd_prd_sdd/ coding/validating with no recent execution_event) by re-driving them through task_execution.run_or_resume_task_lifecycle on a background poll, guarded by an in-process in-flight lock to avoid double dispatch. Responds to a real 2026-08-06 incident where a task sat stuck in 'validating' for 15+ hours with nothing polling it. Scoped to FR-2 only from docs/todo/scheduler-production-polling-loop- requirements.md; FR-1 (new-goal auto-dispatch) stays out per that doc's own non-goals -- it's a separate, larger autonomy-boundary decision. Design recorded in docs/todo/scheduler-production-polling-loop-design.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>