• graphlemon authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »

  • graphlemon authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »

  • 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>
    bboonnee authored
     
    Browse Dir »
  • …3d96-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • …a664-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »

  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »

  • 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>
    bboonnee authored
     
    Browse Dir »
  • … 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • …76ae-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • …4843-main-checkout-drift
    graphlemon authored
     
    Browse Dir »

  • 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>
    bboonnee authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • …437e-main-checkout-drift
    graphlemon authored
     
    Browse Dir »

  • graphlemon authored
     
    Browse Dir »
  • …d7f3-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • 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>
    bboonnee authored
     
    Browse Dir »
  • Per CLAUDE.md's docs/ role split (todo = pending, done = shipped into the
    SDD/code): all seven Graph Engineering series items with a real
    implementation (B, D, E, F, G, H -- A had no separate doc, resolved
    directly in graphlemon_architecture.md; C is explicitly won't-do) now have
    their requirements+design docs moved from docs/todo/ to docs/done/,
    git mv'd to preserve history.
    
    Every cross-reference to the 12 moved files -- across the other moved
    docs themselves, graph-engineering-open-items.md, 2 Skill files, and 9
    src/*.py docstrings/comments -- updated from docs/todo/<file> to
    docs/done/<file>. Verified no stale docs/todo/ references to any of the
    12 filenames remain anywhere in the repo.
    
    graph-engineering-open-items.md itself stays in docs/todo/ (not moved) --
    it's a living index that keeps gaining new entries from later
    comparison-note/gap-analysis passes (H itself was added this way, and a
    new item already appeared while this cleanup was in progress), not a
    single finished conclusion. Its own header now reflects that all of
    A/B/D/E/F/G/H are done and explains why it wasn't moved. Also fixed two
    pre-existing stray path references (missing todo/done/ prefix entirely,
    predating this cleanup) in its G section.
    
    Full src suite (625 tests) passes -- this is a pure documentation/comment
    change, no behavior touched.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Implements docs/todo/role-package-abstraction-design.md. Requirements
    review (docs/todo/graph-engineering-open-items.md's H section) had already
    corrected the requirements doc's own dependency analysis before this;
    scope confirmed with user 2026-08-06: relax all 4 org_node CHECK
    constraints in one pass, not staged.
    
    Format is JSON, not YAML: 05 §5.7's pure-stdlib constraint rules out
    PyYAML, and unlike Skill files (whose YAML frontmatter only Claude Code
    itself ever parses, never this codebase's own Python), role.json genuinely
    needs to be read by agent_profile.py -- json.load is the zero-risk choice
    over a hand-rolled parser.
    
    role.json is a REFERENCE manifest, not a content copy: identity.soul_md_path/
    agents_md_path still point at the untouched agents/<role>/SOUL.md/AGENTS.md
    files. This is what makes "deployed output must stay identical" close to
    automatic -- render_agent_definition()'s body-assembly logic doesn't
    change at all, only the frontmatter's data source does (hardcoded
    FRONTMATTER dict -> parsed role.json). Verified byte-for-byte: diffed the
    old (git HEAD) render_agent_definition() output against the new one for
    all three roles, zero differences.
    
    agent_profile.py: ORG_NODES is now discovered by scanning roles/ for
    role.json files (_discover_role_ids()) instead of a hardcoded 3-tuple;
    FRONTMATTER/_DESCRIPTIONS/_MIGRATED_FROM_PROFILE dicts are gone. Every
    public function takes an optional roles_dir override specifically so a
    test can prove "a new role needs zero code changes" against a throwaway
    fixture directory (NewRoleWithoutCodeChangeTest) rather than asserting it
    by inspection.
    
    schema.py: skill.owner_org_node/task.assigned_org_node/work_graph_run.org_node/
    agent_profile.org_node relaxed from CHECK(x IN ('planner','coder','validator'))
    to CHECK(x != ''). Real blast radius found during design (bigger than the
    requirements doc's own risk note): task/work_graph_run/skill are
    referenced by several other tables' foreign keys, but since only the
    parent tables' own CHECK text changes (child FK definitions are untouched),
    only these 4 parent tables need rebuilding -- followed SQLite's documented
    procedure for schema changes under active foreign keys (foreign_keys=OFF
    for the duration, rebuild, foreign_key_check to confirm nothing went
    dangling, foreign_keys=ON). migrate_relax_org_node_constraints() depends
    on D's migrate_add_work_graph_fork_columns() having run (called
    unconditionally, idempotent) so work_graph_run's rebuild has the right
    column set either way. ux_work_graph_run_active and task's 3 indexes
    verified to survive the rebuild.
    
    work_graph_run_query.py's own separately-hardcoded _VALID_ORG_NODES
    (a real dependency the requirements doc's risk section missed) now reuses
    agent_profile.ORG_NODES instead of maintaining a second copy.
    
    Explicitly NOT done (scoped out during requirements review, not an
    oversight): frontend/app.js's 4 hardcoded 3-role spots (route regex, sort
    order, dropdown, lookup object) -- a new role still won't be recognized by
    the PH3 console. A `role` table with real FK referential integrity was
    also not built; this uses the simpler "just relax the enum" approach.
    
    Full src suite (623 tests) + supervisor suite (125 tests) pass with zero
    regressions.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Implements docs/todo/work-graph-time-travel-debugging-design.md, backend
    and console together (user confirmed 2026-08-06).
    
    Key finding that shrank the v1 scope: ux_work_graph_run_active is a
    PARTIAL unique index (only covers non-terminal statuses), not a blanket
    one -- forking from a parent run that's already reached a terminal status
    never collides with it at all. v1 restricts forking to exactly that case,
    so no index/constraint change is needed, only two new nullable columns
    (parent_run_id, fork_of_node_exec_id).
    
    Resolved the requirements doc's FR-1 ambiguity ("override input_payload,
    re-trigger execution") as OUTPUT SUBSTITUTION instead: the fork point's
    own output_payload is replaced with the human-supplied content directly,
    it is never re-executed via a new claude -p call -- only the strict
    downstream subgraph actually re-runs. This sidesteps reconciling the
    different build_prompt/input_payload conventions each caller uses
    (Planner's artifact_type lookup, E's generic task_prompt field, F's jury)
    -- work_graph_fork.py only needs the persisted graph_manifest and each
    node's output_payload.
    
    Implementation needs ZERO changes to the execution engine: pre-seeding
    every non-downstream node as a 'done' work_node_execution row before
    calling the unmodified run_graph() on the unmodified graph_manifest is
    enough -- run_work_node()'s existing REQ-06 breakpoint-recovery
    short-circuit (already-'done' nodes return their persisted output without
    calling run_fn) does all the "reuse without re-executing" work for free.
    _reconstruct_build_prompt supports two graph shapes generically from
    persisted state alone (Planner's canonical doc chain via
    e2e_pipeline._planner_prompt_builder, and E's generic task_prompt field);
    anything else raises ForkGraphShapeUnsupportedError rather than producing
    broken prompts.
    
    New: src/work_graph_fork.py (create_fork/execute_fork/fork_work_graph_run/
    start_fork_in_background, the last splitting fast DB-only setup from slow
    real execution so an HTTP caller gets run_id back immediately, same
    pattern task_execution.start_task_execution_in_background already uses).
    work_graph_run gets parent_run_id/fork_of_node_exec_id (SCHEMA_SQL +
    migrate_add_work_graph_fork_columns for existing databases).
    work_graph_run_query.py/task_query.py surface both fields. New endpoint
    POST /api/work-nodes/:node_exec_id/fork. Console: a "从这里分叉" form on
    each 'done' node in v_run_detail, a fork badge linking back to the parent
    run, and a 🔀 marker on v_graph_console's run list -- no diff/merge UI
    (non-goal #3), read-only branch visibility only.
    
    Verified end-to-end for real: started the actual backend, sent a real
    HTTP fork request, confirmed the new run persisted with correct
    parent_run_id/fork_of_node_exec_id lineage.
    
    Tests cover requirements doc acceptance standards 1-3 (PRD swapped ->
    SDD/Benchmark genuinely re-execute, BRD untouched; original run completely
    unaffected; the same node forked twice produces independent branches) plus
    schema migration idempotency, query-layer field surfacing, the HTTP route,
    and 3 frontend smoke-test assertions. Full src suite (608 tests) + all 3
    frontend smoke tests pass.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »