Commit Graph
100 Commits
Author SHA1 Message Date
Dark-Alex-17 68e893d684 fix(adversary): red verification runs block CONFORMS; re-derive timeout envelope; robust runner
verdict.py: a recorded verification run with exit≠0 (including the -1
TIMEOUT sentinel) or a skipped record now forces DIVERGES with a numbered
complaint naming the command — previously a [FAIL] line sat in the report
appendix while the sentinel read CONFORMS, and callers route on the
sentinel alone. Fail-closed by the same rule: an ENVIRONMENT runner-error
marker when commands WERE declared is one red complaint (the declared
commands never ran, so the run is unproven); the "none declared" marker
and an empty declaration stay non-red. Headers name the red-run count.

run_checks.py: decode command output with encoding=utf-8/errors=replace
(a single non-UTF-8 byte used to raise inside communicate() and collapse
the whole evidence record to an ENVIRONMENT marker); bound the post-kill
pipe drain by DRAIN_TIMEOUT_SECS so a session-escaped grandchild holding
the pipe cannot turn a TIMEOUT record into a node kill.

graph.yaml: settings.timeout 5400 → 9000 = 3600 (run_checks node timeout)
+ 5400 (the previous whole-graph budget, kept for every other stage); the
old value predated run_checks and a graph-level timeout kills from
outside, losing the DIVERGES sentinel. Descriptions and README doctrine
updated (CONFORMS = all MET + zero holistic complaints + zero pipeline
faults + zero red runs).

Tests: envelope pin (settings >= run_checks + 5400), gauntlet lane
timeouts > child graph timeouts and gauntlet >= max_attempts × lane +
margin over every agent lane, non-UTF-8 output, bounded drain (unix),
red/skipped/timeout/none-declared/ENVIRONMENT×declaration matrix,
numbering continuity, header punctuation, wording pins on graph.yaml,
run_checks.py and both READMEs. The deadline test's hung command is now
python3-based instead of GNU sleep (cmd.exe on the Windows CI leg).
2026-09-14 10:50:34 -06:00
Dark-Alex-17 156f3193a9 fix(review-suite): detect nested finding-verifier faults; harden render/fault markers
code-reviewer verdict.py: finding-verifier now completes "successfully"
with fault markers INSIDE its FINDING_VERIFIER_RESULTS payload
(parse_fault's `pipeline-fault` sentinel id, verify_fault / verdict_gate
crash-guard notes prefixed PIPELINE-FAULT:), which the old
"Agent node failed:" prefix check never saw — a dead parse degraded
silently to "unverified" with no fault recorded. Detect them, anchored on
the sentinel id / note prefix only (never a substring over the report),
counted over the raw entry list so duplicate crash-guard ids are not
collapsed. Faults lead the attention list uncapped; the display cap of 5
applies to non-fault entries; the "always-human trigger(s) fired" reason
fires only when non-fault triggers exist.

code-reviewer render.py: CONTRACT comments at the verdict and summary
lines review-gauntlet's gate parses; every LLM-influenced value rendered
after the summary line (dropped_titles, rigor, bar_provenance,
resolved_surfaces, reason) is newline-sanitised so none can forge a
second summary line.

finding-verifier verdict_gate.py: the outer crash-guard note carries the
PIPELINE-FAULT: prefix (the after-retry UNVERIFIABLE path stays
unprefixed — model misbehaviour, not infrastructure); README names all
three emitters.

deep-research: vet_fault's UNVETTED note tells the critic not to request
revision solely because the assessment is unavailable (it matched
critique's PREPRINT/UNVERIFIED REVISE trigger verbatim, burning the
revision budget on an infra fault); vet_fault and critique_fault dedupe
repeated faults and preserve accumulated faults on crash via
safe_faults(); question_fault keeps its plan-pinned prefix and appends the
lane's question identity so distinct dead lanes no longer collapse in
combine_findings' dedupe.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 4555af8299 fix(review-gauntlet): summary-line critical count, nested-fault hook, fail-closed lane fallback
verdict_gate.py: the code-review critical count is read from render.py's
deterministic summary line (full-shape regex, LAST match wins — a 🔴
quoted inside a finding body or Changes-table row is not a finding); the
raw 🔴 count remains the fail-closed fallback when the line is absent. A
code-review lane whose own rendered verdict line records a pipeline
fault (degraded nested run) now BLOCKS instead of passing as GREEN
(attention), and fault + reds append both blockers. A non-string lane
result item is BLOCKED as malformed, never routed to GREEN.

default_lanes.py: the crash handler now emits the WIDEST safe selection
(code-review ∪ adversary ∪ security) and records a PIPELINE-FAULT in
signals_error so the gate blocks — previously it repaired malformed state
into a NARROWER passable selection with no fault. Probe is added only
when consumer_surface AND a probe_context (local-run recipe) are present,
mirroring build_items' guard, so degraded selection cannot spawn an
INCONCLUSIVE-by-construction probe.

graph.yaml: re-derive the timeout envelope. Agent-node retries each get a
fresh per-attempt budget (agent.rs retry_transient/bounded_attempt) and
the child's "Graph … timed out" matches the transient anchor, so lanes
exceed their child graph's settings.timeout (code-review 11400 > 10800,
adversary 9600 > 9000) and the gauntlet's own timeout covers
max_attempts × lane timeout + margin (2 × 11400 + 1200 = 24000). The
child graphs' internal retry envelopes are deliberately not derived here
(tracked as a follow-up). README/descriptions updated and pinned.

Tests: summary-count/quoted-🔴/fallback/last-match/nested-fault/both-
blockers/malformed-lane, crash-widens-and-records-fault, probe guard,
ALIASES parity with build_items, and e2e render.py → verdict_gate runs.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 ea76331c49 fix(graph): typed ApiStatusError transient classification; warn on bare output_schema
is_transient_error: a typed crate::client::ApiStatusError anywhere in the
chain is now authoritative (positive and negative) — catch_error renders
429s as "… (type: rate_limit_error)" / "… (status: 429)", which none of
the old string anchors matched, so provider rate limits were never
retried, while a 400 whose body mentioned "rate limit" was. Transient
statuses: 429, 500, 502, 503, 504, 529; message text only classifies
untyped errors, and those anchors are now case-insensitive.

validator: warn for an llm/agent node whose output_schema lacks a
non-empty object `properties` map (auto-merge writes nothing since
402d25a) unless the node lifts keys via a non-empty state_updates; the
same absent-or-empty predicate now also gates validate_fallback_capture.
Bundled and graph.example.yaml warning baselines are unchanged.

Tests build the 429 fixture through real catch_error output and a
.context() chain, cover the status table, typed-overrides-text, and all
validator branches.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 84b5a72eac fix(review-suite): mark example verification commands as placeholders; pin variable description and prompt note
Adversary re-review follow-ups to 665fee9/402d25a:

- architect/sisyphus spawn templates: the pinned cargo commands are only an
  EXAMPLE in a language-agnostic orchestrator — a literal follower in a
  non-Rust repo would record 'cargo: command not found' and yield a false
  DIVERGES. Each --variables line now carries a substitution cue (pinned).
- run_checks.py: a non-list prior pipeline_faults value is no longer iterated
  character by character (test case added).
- Pins: both graphs' verification_commands description wording; the
  run_adversary prompt states where the commands come from; the replacement
  sentence sits on one physical line.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 27101909e0 fix(graph): auto-merge only schema-declared keys from llm output
state_updates::apply merged EVERY top-level key of an llm/agent node's JSON
output into graph state whenever the node had an output_schema, and
structured::extract never validated the output against that schema. Any
prompt text able to steer the model — including plan/diff text pasted from a
repo under review — could therefore plant an undeclared key (e.g.
verification_commands) into state and overwrite a caller-declared value
before a downstream script consumed it.

- apply() now takes the node's output_schema and merges only keys present in
  schema.properties; undeclared keys are dropped. No schema, or a schema
  without an object properties map, merges nothing.
- Nodes without output_schema are unchanged (None == the old false path);
  explicit state_updates are unchanged and still see the full output via
  {{output.<key>}}, which remains the author-controlled way to lift a key.
- Callers: llm.rs, agent.rs (x2), rag.rs (None).
- Sweep test bundled_graph_output_schemas_declare_every_key_relied_on: for
  every bundled graph, every consumed state key has a declared writer
  (initial_state, variable, state_updates target, map key, script-emitted key,
  or an output_schema property), and no schema node's field list names a key
  its schema omits — so this restriction cannot silently break a shipped
  graph. It passes with no graph.yaml changes.
- graph.example.yaml documents the declared-keys-only merge; the residual
  disclosed in 665fee9's README/docstring notes is now recorded as closed.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 300c7aeff0 fix(review-suite): pass verification_commands as a declared variable, never LLM-extracted from prose
verification_commands — the shell commands the adversary's run_checks stage
executes — were extracted by the LLM parse node from {{initial_prompt}}, a
flat prompt that also carries untrusted plan/diff text pasted from the repo
under review. A planted '## VERIFICATION' block was indistinguishable from the
caller's declaration: prompt injection -> arbitrary command execution.

- adversary + review-gauntlet graphs declare verification_commands as a graph
  variable (default '[]'); the key leaves initial_state (or_insert seeding
  would otherwise shadow it) and leaves both parse nodes' instructions,
  output_schema.properties and required.
- review-gauntlet forwards it to run_adversary via a structured inputs:
  passthrough (lone {{verification_commands}} template), not lane-prompt prose.
- run_checks.py accepts a JSON list or a JSON-encoded string of one (variables
  arrive as strings); '[]'/[]/missing = none declared; any other shape fails
  closed with a PIPELINE-FAULT (nothing executed) that verdict.py turns into
  DIVERGES complaint #1.
- architect/sisyphus spawn templates and both READMEs document the
  --variables / --agent-variable form and the trust boundary. Known residual
  (llm-node auto-merge of undeclared output keys) is disclosed in the docs.
- Tests: list/JSON-string acceptance, fail-closed no-execution proof,
  prompt-prose injection proof, verdict wiring, asset pins for both graphs and
  orchestrator configs, dispatch-inputs > variable-override > default
  precedence.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 00c3b60c2f feat(graph): stream llm-node completions so node timeout governs long generations
Graph llm nodes (and the structured-output extractor they share with agent
nodes) now obtain the model reply through a quiet streaming transport,
`call_chat_completions_streaming_quiet`, instead of the non-streaming call.
Because chunks arrive as they are generated, reqwest's client-level
`read_timeout` (default 300s) bounds only a genuine stall between chunks and
the node's own `timeout` becomes the effective bound on total generation.
Previously zero bytes arrived until the whole reply finished, so a 300s
read_timeout silently capped every llm node regardless of its `timeout`
(seen live: code-reviewer's `synthesize` node dying on a 61-file review).

The quiet transport renders nothing (silent SseHandler, no spinner), keeps
tool-call handling identical to the non-streaming path (call-loop detection
off), falls back to the non-streaming request for catalog `no_stream` models
(raced against the abort), and never returns partial output as success.

Supporting changes:
- `sse_stream`/`json_stream` keep the typed `reqwest::Error` in the anyhow
  chain so `is_transient_error` still classifies read_timeout stalls as
  transient and `max_attempts` retries them (previously stringified).
- `structured::extract` takes the graph/session AbortSignal and refuses to
  issue an extraction request for an already-aborted graph.
- `run_chat_loop` gains an injectable CompletionRunner seam (owning client
  construction + the before-hook) so the timeout/retry/abort behaviour is
  unit-testable over a paused clock; a fake `impl Client` drives the real
  trait-default streaming for 600s of virtual time; real-socket tests pin
  that read_timeout fires on a stall but not on a slow continuous stream.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 22421a4e74 test(adversary): pin run_checks process-group kill and deadline-vs-timeout ordering
run_checks.py SIGKILLs the timed-out command's whole process group so a
backgrounded grandchild cannot outlive the record; nothing proved it.
Add a unix-only test that backgrounds `sleep 30`, records its pid, and
probes `kill -0` after the 1s deadline fires.

TOTAL_DEADLINE_SECS (run_checks.py) and the run_checks node's `timeout:`
(graph.yaml) live in different files and could drift until the node
executor kills the script before its in-script degradation runs. Parse
the script's default and assert it stays strictly below the node
timeout, without hardcoding either value.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 fa5ee5f4fc test(graphs): lock retry counts, fallback wiring, and check_criterion's read-only tools
Sweep every agent node in the six bundled graphs and assert max_attempts
is exactly 2 (step-runner/implement stays 1) against a pinned node list,
so a dropped `max_attempts:` line cannot silently revert a lane to
one-shot (the serde default is 1). Sweep every llm/agent node for a
declared, existing fallback and pin the specific fault routes in
review-gauntlet, code-reviewer, and adversary.

Pin check_criterion's tool list to fs_read/fs_cat/fs_grep/ast_grep and
its 'judged ONLY against the recorded verification runs' instruction,
so the criterion branch can never grow an execution tool unnoticed.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 ba3a904baf test(graph): prove per-attempt timeout budget and cover extraction-fault text
agent.rs: the retry-after-timeout test ran its second attempt with no
timeout bound, so it passed whether or not the retry owned a fresh
budget. Attempt 2 now runs under the same 1s bounded_attempt and sleeps
900ms under the paused clock; it only succeeds if the deadline is
per-attempt rather than shared with the attempt that already used it.

llm.rs: the `LLM node structured-extraction failed: {e:#}` branch had no
test because `run` cannot succeed without a model. Split the post-run
half of execute into `finish(node, state, ctx, result)` (byte-identical
body, mirrors agent.rs's outcome_from seam) and add tests feeding
Ok("not json") through it, with and without a fallback, asserting the
captured text prefix and the extractor's full context chain.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 8e42a59b98 docs(adversary): fix mermaid node label and criterion_fault join edge
Node B is the graph's `facts` node (script diff_facts.py), not a node
named diff_facts. criterion_fault has no `next`; it reaches holistic
through the map join, so draw it dashed like the other branch-terminal
edges instead of as a solid successor edge.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 081fa2c9d4 test(suite): replace plan-label references with self-contained wording
Rust test comments/assert messages and deep-research script docstrings
cited numbered requirement labels (R3/R5) and task labels (TASK-016,
TASK-021) that are defined only in an external plan document. Reword
each to say what it means on its own: fail-safe crash guards, the
validator's fallback-capture warning, task-queue anchors.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 48753eaa46 fix(agents): scrub GRAPH_STATE* from adversary verification commands; raise review-suite max_agent_depth
FIX A: run_checks.py passed no env= to Popen, so verification commands
inherited the live GRAPH_STATE/GRAPH_STATE_FILE. Every suite script's
load_state() prefers GRAPH_STATE_FILE, so when the adversary reviews a repo
whose own tests exec these scripts (this repo's R5 tests) the leaked file
silently overrode the test state -> false failures. Popen now gets an env
with all GRAPH_STATE* keys removed. The five sibling script-test helpers
(review-gauntlet, code-reviewer, finding-verifier, step-runner,
deep-research) gain .env_remove("GRAPH_STATE_FILE") for hermeticity. New
R5 test loads state via GRAPH_STATE_FILE and asserts a shell-neutral
python3 -c probe sees both vars UNSET.

FIX B: the depth check compares the child's absolute depth against the
SPAWNER's own max_agent_depth (root = 0), so under
architect(0)->sisyphus(1)->review-gauntlet(2)->code-reviewer(3) the
code-review lane faulted "Max agent depth exceeded (4/3)". Limits are now
child depth + 1 headroom along the documented chains, with the chain in a
comment next to each key: code-reviewer 3->5, domain-reviewer 2->6,
architecture-reviewer 2->4, step-runner default->4 (explicit),
review-gauntlet stays 4. domain-reviewer's file-reviewer hop is documented
but inert (can_spawn_agents unset) - left unwired by decision; noted in
config and pinned via a DOCUMENTED_BUT_UNWIRED exemption in the new
bundled-config test, which pins every value and asserts per-hop headroom +
reachability along four chains.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 62ecb5b415 test(adversary): pin exact died-evidence prefix on the criterion_fault crash path
The crash-path assertion checked only "PIPELINE-FAULT:"; verdict.py's died() keys on "PIPELINE-FAULT: criterion ", so a regression there would have passed the test yet stopped the crashed marker from rendering as DIED.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 7ada3fb47c fix(adversary): narrow died-marker predicate, degraded banner for died criteria, hermetic script tests 2026-09-14 10:50:34 -06:00
Dark-Alex-17 1cf42fffa2 fix(adversary): fail closed on PIPELINE-FAULT evidence regardless of status; pin bail-chain and validator shape 2026-09-14 10:50:34 -06:00
Dark-Alex-17 66b30bb06a test(code-reviewer): pin synthesize max_attempts and fail-closed wiring
A live code-reviewer run degraded through synthesize's fallback; the
graph already retries once (max_attempts: 2) and falls back to verdict
with synth_failure captured, but nothing pinned that wiring. Add the
wiring test so a future edit cannot silently drop the retry or route.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 3f49db86b2 fix(adversary): fail closed per criterion when check_criterion dies
A live run died at map_criteria: one heavy criterion exhausted
max_iterations (10) with no fallback and sank the whole 17-criterion
review. check_criterion now has a 30-iteration budget, a tool-budget
instruction, and falls back to criterion_fault, which records a
schema-shaped UNMET verdict with a PIPELINE-FAULT evidence marker for
that one criterion; verdict.py renders it as DIED rather than judged.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 7dac4620a8 fix(engine): carry full error chain in llm node fault text
A live code-reviewer run reported "LLM node failed: Failed to call
chat-completions api" with the root cause invisible, because the
state_updates fault text used `{e}` (top context only). Format with
`{e:#}` so fault scripts and warn! logs see the whole anyhow chain.
2026-09-14 10:50:34 -06:00
Dark-Alex-17 5b4462135f style: cargo fmt after doc-comment cleanup 2026-09-14 10:50:34 -06:00
Dark-Alex-17 fdae98adff docs: sync agent READMEs + diagrams with resilience hardening 2026-09-14 10:50:34 -06:00
Dark-Alex-17 f230c28bf4 docs: removed references to individual task files in the code documentation 2026-09-14 10:50:33 -06:00
Dark-Alex-17 13b8eb87a9 fix(TASK-020): skip POSIX bash script tests on Windows 2026-09-14 10:50:33 -06:00
Dark-Alex-17 0ce2654173 feat(TASK-023): deep-research hardening — fail-safe scripts, fault fallbacks, pipeline notes
R3 guards: parse_request (crash → ask_topic), bootstrap_research,
combine_findings (crash → fault-text findings; also lifts branch-local
PIPELINE-FAULT findings into pipeline_faults, deduped), reflexion_gate
(crash fails forward to synthesize), incorporate_feedback (crash still
loops with a generic feedback note), verify_sources (top-level guard +
pipeline_notes fold).

Fallbacks per the plan's naming pins: plan → plan_fault → end_fault
(fail-closed; gains state_updates plan_failure per the TASK-016 rule);
research_one_question → branch-local question_fault (map collects the
fault finding); vet_sources/critique → degrade-visibly markers that
continue to the stage each node's own next pointed to; synthesize →
synth_fault → end_fault. end_fault renders
'DEEP_RESEARCH FAILED — PIPELINE-FAULT: <fault text>'.

Accepted-path visibility: verify_sources folds pipeline_faults into
pipeline_notes ('' when fault-free), appended to end_accepted's output
and the approval question — happy-path output stays byte-identical.
initial_state seeds questions: [] so a dying plan inside the static
fan-out leaves the sibling knowledge_lookup lane mapping over zero
items while end_fault renders.

R5: 19 python3-guarded regression tests + GraphParser wiring test;
bundled warning baseline unchanged.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 c98b6bc9cd test(TASK-021): pin task-queue guidance text in shipped artifacts 2026-09-14 10:50:33 -06:00
Dark-Alex-17 f2d90ed7a6 docs(TASK-021): fix task-queue example IDs + document dispatch/collect/failure semantics
- prompts.rs DEFAULT_SPAWN_INSTRUCTIONS: example IDs task_1/task_2 -> "1"/"2"
  (real numeric-string IDs; old format silently produced dep_error + unblocked
  task), plus task-queue rules: root tasks with agent are never auto-dispatched,
  task_complete is what dispatches dependents, auto-dispatched agents must be
  collected, failed tasks permanently block dependents.
- architect config: optional task-queue mirroring guidance for parallel mode
  (disk task files stay the durable store; queue is in-memory).
- sisyphus config: short note on encoding dependency-shaped bursts as
  agent__task_create chains; todos remain the tracking source of truth.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 22976a79f9 fix(TASK-020): surface step-runner LLM-fault captures where the fallback lands
The three fallback-capture keys (orient_failure, sweep_failure,
handoff_failure) were write-only dead state: captured on LLM-node failure
but interpolated nowhere, so end_failure rendered an empty STEP_FAILED
shell and write_handoff carried no trace a dead sweep.

- new crash-proof script node note_llm_fault distills orient/handoff
  'LLM node...' failure text into fault_note (prefix-anchored; empty when
  neither key holds a fault); orient and write_handoff fallbacks now route
  through it before end_failure
- end_failure renders 'Pipeline fault (if any): {{fault_note}}' (sentinel
  and all existing fields preserved; fault_note defaulted in initial_state)
- write_handoff prompt interpolates {{sweep_failure}} with a
  PIPELINE-FAULT VISIBILITY instruction mirroring the review_report
  treatment (fault -> Deviations entry + step_summary; success/empty
  ignored)
- tests: note_llm_fault fault/clean/crash/truncation paths via the
  GRAPH_STATE harness; wiring test pins the retargeted fallbacks,
  note_llm_fault routing, {{fault_note}} in end_failure, and the
  sweep-fault prompt/instruction anchors; warning baseline unchanged
2026-09-14 10:50:33 -06:00
Dark-Alex-17 4263d684e9 feat(TASK-020): finding-verifier + step-runner fail-closed hardening
finding-verifier: parse gains max_attempts 2 + parse_failure capture +
fallback to a new parse_fault marker that rewrites verdicts to a single
UNVERIFIABLE PIPELINE-FAULT entry and continues to done (sentinel
preserved — the caller's verify lane degrades instead of dying);
verify_one gains a branch-local verify_fault marker normalizing the
engine failure text into a schema-conformant UNVERIFIABLE verdict.

step-runner: implement gains fallback end_failure (no retry — reruns
against a mutated tree are unsafe; STEP_FAILED renders the failure via
coder_result); independent_review gains fallback write_handoff with the
handoff instructions and route_review treating an "Agent node failed:"
review_report as review-did-not-run (fail-visible, guard ahead of the
🔴 grep); orient/edge_case_sweep/write_handoff capture their failure
text via state_updates, shrinking the bundled fallback-capture warning
baseline back to zero.

R5 tests: fault-marker normalization + crash paths (raw non-JSON
GRAPH_STATE), route_review.sh fault/critical/clean routing via bash+jq,
and structural wiring tests pinning fallbacks, sentinels, and
end_failure's initial_state-backed template keys.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 a8496aa50e fix(TASK-019): carry PIPELINE-FAULT prefix through verdict.py's crash guard
render.py's empty-diff stub bypass is anchored on the 'PIPELINE-FAULT:'
prefix in reason/attention, but verdict.py's crash guard emitted
'verdict computation error: …' / 'verdict script error: …' without it —
on an empty diff a crashed verdict gate collapsed into the
'No changes to review.' stub and the fail-closed NEEDS-HUMAN verdict
never reached the final report.

- prefix the crash-guard reason and attention entry with PIPELINE-FAULT:
- strengthen the existing crash test to assert the prefix on both fields
- add R5 chained test: empty diff + crash-inducing state piped
  verdict.py -> render.py must render the fault, never the stub
- add crash-guard symmetry tests for parse_fault.py / aux_fault.py
  (raw non-JSON GRAPH_STATE keeps the fault marker) and a test that a
  fault survives the attention[:5] cap alongside 6 user flags
2026-09-14 10:50:33 -06:00
Dark-Alex-17 4e72c6f5e6 feat(TASK-019): code-reviewer fail-closed review pipeline
Every llm/agent stage that can die persistently now falls back to a
machine-readable PIPELINE-FAULT sentinel instead of killing the graph:

- review_domain -> branch-local domain_fault marker (banner-prefixed
  domain_report the map collects; completeness_gate precedent)
- verify -> verdict (verifier fault renders findings unverified + a
  PIPELINE-FAULT attention line)
- parse -> parse_fault -> render (complete NEEDS-HUMAN verdict_out;
  render's no-changes stub is bypassed for fault verdicts)
- refine_groups -> cover_gate (deterministic-proposal fallback absorbs
  the failure; degradation noted in groups_note)
- aux_lanes -> aux_fault -> synthesize (aux_note fault marker; visible,
  never blocking - aux is enrichment)
- synthesize -> verdict (synth_failure marker; keyed off the failure
  prefix + non-empty diff, never findings==[], so clean zero-finding
  reviews stay MERGE-READY)

verdict.py gains unified fault accounting (R2): any PIPELINE-FAULT in
domain_reports / verifier_output / state fault keys forces NEEDS-HUMAN,
prefix-anchored so a report merely quoting the marker mid-text never
trips it; old 'PIPELINE FAULT' spelling migrated. Faults are prepended
to attention so the [:5] cap can never hide one. Dedicated *_failure
state keys capture engine failure text on every fallback+output_schema
node, keeping the bundled warning baseline at zero.

15 python3-guarded R5 tests: fault paths, quoted-marker precedence,
happy-path byte-compat regression, marker normalization, crash-path
fail-closed (verdict/cover_gate/domain_fault), verifier-fault-on-empty-
diff render, and the no-changes stub regression.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 ebb9e9af62 fix(TASK-018): harden verdict_gate fault routing and widen default_lanes fallback
Adversary finding 1: verdict_gate treated any lane report CONTAINING
'PIPELINE-FAULT:' as a lane fault, so a real review quoting the marker
silently bypassed red-finding counting and sentinel parsing. Lane-fault
detection is now precise — only a report that IS the lane_fault.py
marker (starts with it) routes to the fault rule. Adds the missing
regression tests: reds block despite MERGE-READY, NEEDS-HUMAN without
reds passes with attention, probe INCONCLUSIVE blocks with the
environment note, a gate crash fails closed, and a quoting report is
routed to the red rule, not the fault rule.

Adversary finding 2: default_lanes overwrote caller-forced lanes and
dropped the security lane the deterministic signals guaranteed. The
fallback now unions its defaults with caller-forced lanes (canonicalized
via the same aliases build_items uses) and forces security on
auth/deps/exec signals or hardened posture — selection degrades wider,
never narrower, and stays deterministic via a fixed lane order. Tests
cover forced-lane survival, alias canonicalization, unknown-name
tolerance, each security signal, and determinism.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 80c20afcdd test(TASK-018): cover parse_fault.py fault normalization 2026-09-14 10:50:33 -06:00
Dark-Alex-17 8407729d5a feat(TASK-018): review-gauntlet lane degradation + verification passthrough
- lane_fault.py + fallback on all four run_* lane nodes: an agent-node
  failure is normalized into a PIPELINE-FAULT lane result instead of
  killing the graph
- verdict_gate.py: PIPELINE-FAULT lane results block distinctly from
  SKIPPED and missing-sentinel; a PIPELINE-FAULT in signals_error blocks
  at the gauntlet level
- build_items.py: restructured into main() + fail-closed guard (crash
  emits empty lanes + PIPELINE-FAULT) and folds lanes_degraded into
  lanes_summary
- parse: state_updates/fallback -> parse_fault.py, which records the
  fault in signals_error and jumps to the verdict gate
- select_lanes: state_updates/fallback -> default_lanes.py, a
  deterministic wider-never-narrower lane fallback
- verification_commands threaded through parse into run_adversary's
  prompt (declared-only, never invented)
- run_probe prompt gains the aborted-attempt reconcile line
- 8 gauntlet_* R5 tests in src/config/request_context.rs (script harness
  + graph interpolation)
2026-09-14 10:50:33 -06:00
Dark-Alex-17 1e46743420 fix(TASK-017): bound run_checks runtime and fail-close its node-level death
Adversary conformance-review fixes for the TASK-017 exec-evidence stage:

- run_checks.py: total-runtime deadline (3300s = node timeout minus margin);
  each command gets min(900s, remaining budget), commands past the deadline
  are recorded as {cmd, skipped: deadline} entries so in-script handling
  stays authoritative instead of the node timeout killing the script from
  outside. POSIX hardening: per-command process group (start_new_session)
  + killpg on timeout so hung grandchildren cannot linger; non-POSIX
  limitation documented in the docstring.
- graph.yaml: run_checks gains fallback: pipeline_fault; check_criterion
  treats SKIPPED records as at-best PARTIAL (unproven, never failed).
- pipeline_fault.py: run_checks-stage attribution grounded in real engine
  behavior (script-node fallback writes nothing into state, executor.rs
  script arm) — nonempty diff_text proves facts completed, so the fault is
  the verification runner; emits 'PIPELINE-FAULT: verification runner
  killed'. Empty diff_text still attributes to diff resolution.
- verdict.py: renders skipped records as [SKIPPED] instead of a bogus
  [FAIL] exit-? line; non-degraded verdict semantics untouched.
- tests: outer-except ENVIRONMENT degradation, deadline timeout+skip (env
  seam ADVERSARY_RUN_CHECKS_DEADLINE_SECS), pipeline_fault stage
  attribution (parse / facts / run_checks), check_criterion prompt cites
  {{exec_results}}, verdict SKIPPED rendering; run_adversary_script_env
  helper for env-carrying invocations.
- cargo fmt violations in 3d7dea9 test code fixed (fmt --check exit 0).
2026-09-14 10:50:33 -06:00
Dark-Alex-17 d50059a56f feat(TASK-017): adversary execution evidence + fail-closed routing
- parse extracts caller-declared verification_commands (schema + initial_state);
  never invents commands. parse gains fallback + state_updates capturing the
  failure string (TASK-016 fallback-capture warning stays silent).
- new run_checks script node (facts -> run_checks -> map_criteria, timeout
  3600): runs each declared command in the project dir (900s per-command
  timeout), records exec_results {cmd, exit, duration_s, tail~50}; 'none
  declared' / ENVIRONMENT markers instead of guessing or failing the node.
- check_criterion judges execution criteria against {{exec_results}} only:
  recorded green run = MET citing it, recorded failure = UNMET, markers =
  PARTIAL with an ENVIRONMENT note.
- fail-closed routing (R1/R2): parse/facts fallback -> pipeline_fault marker
  script -> verdict; holistic fallback -> verdict with holistic_failure
  captured. verdict.py: any PIPELINE-FAULT forces DIVERGES with the fault as
  complaint #1, per-criterion results still reported; report gains a
  'Verification runs:' section. Non-degraded semantics unchanged.
- R5 rust tests (python3-guarded, local cmd_available): verdict fault path,
  holistic-failure fault, CONFORMS regression, run_checks markers + recorded
  runs, crit_gate contract regression.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 3dcb9541b1 test(TASK-016): assert fallback warn carries the error root cause 2026-09-14 10:50:33 -06:00
Dark-Alex-17 7dc950a092 test(TASK-016): assert the fallback warn names the node id and fallback target
The adversary conformance review flagged that the fallback warn! at
outcome_from (src/graph/agent.rs:71) was never asserted — only the retry
warn had captured-text coverage. Adds one test installing the shared warn
collector, filtered by the unique node id 'fallback_warn_capture' for
parallel safety, asserting the warn names both the node and the fallback
target and carries the error chain.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 80ace77e44 feat(TASK-016): engine + test fixes from the Phase-1 re-review
- warn-per-retry test via shared test-only WarnCollector (src/testing.rs)
- extraction-placement test: extraction failures are never retried
- is_transient_error: typed reqwest/Elapsed pre-check + anchored 429 matchers
- outcome_from docstring corrected; validator warning for
  fallback+output_schema without state_updates (llm + agent)
- validator error for max_attempts == 0 (llm + agent); runtime backstop kept
- nits: generalized branch-escape error text, Continue payload comment,
  BFS test assignments assert, node_id threaded into AgentNodeExecutor warns
- bundled-graph warning baseline records 3 true-positive step-runner nodes
  (orient, edge_case_sweep, write_handoff) pending state_updates there
2026-09-14 10:50:33 -06:00
Dark-Alex-17 18875a4cc9 feat(TASK-011): traverse agent fallback edges in map peer-discovery BFS 2026-09-14 10:50:33 -06:00
Dark-Alex-17 75cc2a4b9c feat(TASK-010): add max_attempts to read-only agent lanes in bundled graphs 2026-09-14 10:50:33 -06:00
Dark-Alex-17 be0c5462ed feat(TASK-009): add fallback routing to agent nodes
Agent nodes gain an optional fallback target with llm-node parity: on any
failure (retries exhausted, hard error, or structured-extraction failure)
the failure chain is written into state as 'Agent node failed: <chain>'
and the chain routes to the fallback node. Without a fallback the error
propagates unchanged. Validator collects agent fallback edges for
missing-target checks, reachability, and map-branch locality.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 1c68331738 feat(TASK-008): add shared transient matcher and retry support to agent nodes
Hoist the llm node's transient-error matcher to graph::is_transient_error,
extend it with broken-pipe / connection-error / error-sending-request
signatures, and give AgentNode an opt-in max_attempts retry loop with
llm-parity semantics: contexts applied before the transient check, fresh
wall-clock budget per attempt, peer identity (assignment + registry)
re-armed before each retry, retirement exactly once after the final
attempt, and structured extraction outside the loop.
2026-09-14 10:50:33 -06:00
Dark-Alex-17 d5d6dd9df4 test: fixed flaky windows timing test for graph agents 2026-09-12 02:00:25 -06:00
Dark-Alex-17 3254fe9f3c feat: Fixed graph agents with new agent variable passing functionality 2026-09-12 01:37:20 -06:00
Dark-Alex-17 a7528d6e1a feat(graph): warn when a declared variable is shadowed by an initial_state key 2026-09-12 01:37:20 -06:00
Dark-Alex-17 442953ef7d style: applied different styling to agent functions 2026-09-12 01:37:20 -06:00
Dark-Alex-17 1ec7916816 feat(agents): advertise declared agent variables in agent__list_available 2026-09-12 01:37:20 -06:00
Dark-Alex-17 eb7d1421da fix(tests): collision-proof parser test temp files to deflake macOS CI 2026-09-12 01:37:20 -06:00
Dark-Alex-17 f97a83f165 feat(agents): allow passing agent variables to agent__spawn and agent__task_create
- agent__spawn accepts an optional variables object (string->string);
  values overlay the parent-inherited agent variables per key, with
  inheritance preserved as the fallback layer
- unknown variable names, non-string values, and non-object shapes
  return self-correcting status:error JSON listing the agent's
  declared variables
- spawned children can no longer trigger the interactive variable
  prompt: a new interactive gate in Agent::init_agent_variables routes
  missing required variables to the existing bail path while leaving
  CLI/REPL prompting and --info skip semantics unchanged
- agent__task_create persists variables on the task, agent__task_list
  echoes them, and task auto-dispatch threads them into the spawn
- tool schemas and built-in spawn docs updated; unit tests extended
  in agents.rs and taskqueue.rs
2026-09-12 01:37:20 -06:00
Dark-Alex-17 3599b0a861 Merge branch 'main' of github.com:Dark-Alex-17/coyote 2026-09-11 22:42:20 -06:00
Dark-Alex-17 2e7cd6581e feat: seed variables into initial graph state 2026-09-11 22:42:07 -06:00
Dark-Alex-17 99e592565a feat: Significant code review suite rewrite with improved graph agent features 2026-09-11 15:33:21 -06:00
Dark-Alex-17 838335f84d feat: let install commands remove files that have been removed from bundles, both internal and external 2026-09-11 15:28:42 -06:00
Dark-Alex-17 b50ec42868 fix: Don't infinitely compress a session once it passes a threshold when the threshold is close to the max token count 2026-09-11 14:02:28 -06:00
Dark-Alex-17 e744da8870 feat: update the web_search_coyote tool to attempt to use the current model first then fallback to the configured model for web searches 2026-09-11 13:47:44 -06:00
Dark-Alex-17 eb78a0e32b feat: expose the current coyote model to tools 2026-09-11 13:47:20 -06:00
Dark-Alex-17 2f85462e2a fix: fix spinner ctrl-c early exit not clearing message and restoring cursor 2026-09-11 13:01:27 -06:00
Dark-Alex-17 3e91813cff feat: removed temperature specification in deep-research and finding-verifier agents to make them more univerally usable 2026-09-11 11:22:06 -06:00
Dark-Alex-17 99fb6e24f1 fmt: applied formatting 2026-09-11 10:56:28 -06:00
Dark-Alex-17 24b5504e21 fix(graph): build peer retirement guards before spawning their tasks
A task aborted before its first poll never runs its body, so a guard
constructed inside the future did not exist yet and the pre-provisioned
teammate identity stayed messageable. Built outside and moved in, the
guard drops with the unpolled future on both the frontier and map items.

Also rewords the two timeout: 0 validator warnings: the graph-level one
no longer claims max_loop_iterations always applies (it may be 0 too),
and the node-level one names the enclosing bounds that still end the
node instead of saying it runs until it returns.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 02e2227aa0 fix(graph): reject agents that ship both config.yaml and graph.yaml at load
Agent::init refuses that layout, so any agent node pointing at such a
directory failed at run time while validation passed it as graph-capable.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 778aa6449c fix(graph): bound concurrency caps by the semaphore permit limit
Semaphore::new panics above tokio's MAX_PERMITS, so a fixed or templated
map cap (or settings.max_concurrency) that large crashed the run instead
of erroring. The validator rejects both at load, resolve_max_concurrency
rejects every arm at run time, and the executor checks the graph setting
before building its own semaphore, so validation mode makes no difference.

Also corrects run_child_agent_with_graph_inputs' contract comment: inputs
for a config-only agent are rejected, not ignored.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 a9f89f0414 test(graph): wait for the dispatcher before aborting the retirement test
On slow runners bash start-up alone can exceed the fixed 300 ms delay, so
the abort landed before the worker tasks (and their retirement guards)
existed and the observer saw nothing. The dispatcher now leaves a marker
and the trigger waits for it.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 f072aa1b2c docs(graph): drop the output-null gotcha from the example graph 2026-09-11 10:56:28 -06:00
Dark-Alex-17 32f4adb71f fix(graph): make turn-cap and template checks consistent across validation modes 2026-09-11 10:56:28 -06:00
Dark-Alex-17 b4c01e0f28 chore(agents): drop the inert output reducers from librarian and finding-verifier 2026-09-11 10:56:28 -06:00
Dark-Alex-17 1a5824ce4f fix(graph): leave output absent after state_updates when it was absent before 2026-09-11 10:56:28 -06:00
Dark-Alex-17 324dd3397a docs(graph): document abort semantics and map binding rules in the example graph
Comment-only: settings.timeout notes that Ctrl-C ends a run promptly from
anywhere; the map node's as:/output_key: comments state that the two must
differ, and max_concurrency describes the template form as exactly one
{{key}} with nothing around it.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 727cab11ff fix(graph): validate the whole max_concurrency template string
A string max_concurrency passed the load-time check whenever it merely
contained a {{...}} pair, so forms like "n={{k}}" or "{{ key }}" that
can never resolve at runtime were only caught after the map's inputs had
been computed. The check now requires the trimmed string to be exactly
one template reference, matching what interpolate_raw resolves as a pure
reference; padded forms like " {{budget}} " stay valid.

Also: the bash tool build-failure context names the source file instead
of the destination path, and the map-subgraph validator no longer
shadows its member id list with the resolved member nodes.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 ccec38c5b9 fix(graph): reject as == output_key on map nodes
A map node whose item binding shares its name with output_key would have
the binding collected as the chain's result whenever the chain never
wrote it, while the missing-writer warning stayed silent. The collision
is now a validator error attributed to the map node and, with validation
off, a runtime error in run_map before any item forks.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 cce8381962 feat(graph): thread the graph abort into llm nodes and tool execution
LlmNodeExecutor::execute now takes the graph's abort signal and threads
it through run -> run_with_retries -> run_chat_loop, replacing the
loop-private signal, so an in-flight model call is interrupted and every
turn starts with an abort check that bails 'llm node aborted' before any
client is created.

run_llm_function gains an optional abort signal: the synchronous tool
wait loop kills the child and returns a 'Tool call ... aborted' error
within one poll interval when it is set. The tool dispatcher passes the
session abort; the agent _instructions runner passes None.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 1ea755e864 feat(graph): abort a graph run on Ctrl-C from anywhere
A graph run now observes user interrupts wherever it is: GraphExecutor::run
spawns a run-scoped bridge that maps SIGINT or the enclosing session abort
onto the graph's own abort flag (only when a session signal exists; a
pre-set session trips a synchronous check). The frontier join selects on
the abort and, like the timeout path, cancels every in-flight branch task,
failing the run with 'Graph ... aborted during super-step'.

Branch and map item tasks are cancellation-owned via TaskCancelGuard, so
dropping a map task's future no longer detaches its item chains. Peer
retirement is a Drop guard (PeerRetireGuard) constructed before the
semaphore acquire and dropped explicitly right after step(), so the
success-path ordering is unchanged while task abort, panic, and the
pre-step abort return all still retire the identity.
2026-09-11 10:56:28 -06:00
Dark-Alex-17 6aad82c6fd fix(graph): inherit the session abort in agent-node children
A child agent spawned from a graph node now carries its parent's session
abort signal, so a user interrupt is observable from every sync wait in
the agent tree. Background agent__spawn children are unchanged.

run_child_agent_with_graph_inputs also guards the child's own supervisor
with a disarmable CancelOnDrop: if the future is dropped mid-flight or
the child fails before its normal cleanup, its spawned subagents are
cancelled instead of running on invisibly.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 aa1287b902 docs(graph): document llm max_iterations: 0 in the example graph 2026-09-11 10:56:27 -06:00
Dark-Alex-17 d7a16363b8 feat(graph): warn when an llm node's turn cap is disabled 2026-09-11 10:56:27 -06:00
Dark-Alex-17 36601d8668 feat(graph): treat llm max_iterations: 0 as no turn cap 2026-09-11 10:56:27 -06:00
Dark-Alex-17 8e9a1f1ee8 docs: removed unnecessary comment on GraphState.history field 2026-09-11 10:56:27 -06:00
Dark-Alex-17 8f9284cb92 refactor(graph): keep the ordered visit log test-only
GraphState.history was pushed to on every node visit and deep-cloned on
every super-step snapshot and map fork, but its only reader is the
cfg(test) current_node accessor. Gate the field, its initializer, and
the push behind cfg(test) so production builds no longer carry an
unbounded Vec<String> that nothing observes.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 69a3d928ee docs(graph): document max_loop_iterations: 0 in the example graph 2026-09-11 10:56:27 -06:00
Dark-Alex-17 4c5aa8d895 feat(graph): warn when the visit cap is disabled
validate_timeouts now also flags settings.max_loop_iterations: 0, ahead
of the settings.timeout: 0 warning so both opt-outs read top-to-bottom
as they appear in settings. (design §3.14 / C22)
2026-09-11 10:56:27 -06:00
Dark-Alex-17 3aa35e1259 feat(graph): treat max_loop_iterations: 0 as no visit cap
Both enforcement sites (frontier loop and map chain step) skip the
comparison when the cap is 0; visits are still counted and logged.
Adds an executor test that loops past the default cap with 0, a test
pinning the frontier cap message for a non-zero value, and a map-chain
twin at 0. (design §3.14 / C22)
2026-09-11 10:56:27 -06:00
Dark-Alex-17 4dee1a6849 docs: Removed unnecessary unicode arrow from example graph config 2026-09-11 10:56:27 -06:00
Dark-Alex-17 f342dd7dd7 docs(graph): document timeout: 0 in the example graph
Comment-only: each of the five timeout fields in graph.example.yaml now
says that 0 disables the bound. Parsed content is unchanged.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 cd0318f384 feat(graph): warn when a timeout is disabled
A timeout: 0 on settings.timeout or on an agent/script/llm/rag node is
the author's explicit opt-out of the wall-clock bound. It stays legal;
the validator now emits one warning per zero so the choice is visible in
--validate output. Non-zero and unset values do not warn (unset already
means no bound for settings.timeout and llm nodes, matching the
explicit-zero-only convention of the orchestration-limit warnings).

Design §3.13 / C21.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 d630c7e30b feat(graph): treat timeout: 0 as no wall-clock bound
A zero on any of the five timeout fields (settings.timeout, agent, script,
llm, rag) now disables the bound instead of firing an immediate
"timed out after 0s" or graph bail. Unset values keep their defaults
(agent 300, script 30, rag 120, llm/settings none); non-zero values are
unchanged. One resolution helper, wall_clock(secs) -> Option<Duration>,
feeds all five call sites; the C17 peer-retirement mark and every error
message stay where they were.

The one existing test that relied on timeout: 0 firing
(run_item_chain_marks_identity_finished_when_agent_step_times_out) moves
to timeout: 1 with its 5s hold unchanged.

Design §3.13 / C21.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 b666b7e494 docs(graph): illustrate the inputs map on the example agent node
Comment-only block on deep_dive; the example's parsed YAML is unchanged.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 e26543f21a test(graph): cover agent node inputs end to end and at the runtime guards
Two offline chain tests materialize an echo-inputs graph agent whose
script reports the width it sees: a parent with inputs.width gets the
parent's value back, a parent without inputs gets the child's YAML
default. Two agents.rs tests pin the exact runtime guard texts for a
config-only target and a reserved initial_prompt key.

Task T5 of plans/map-subgraph-branches-design.md §3.12.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 10b8fe5cae feat(graph): validate agent node inputs at load time
Inside validate_agents: an agent node with inputs whose target has a
config.yaml but no graph.yaml is an error, and an inputs key named
initial_prompt is rejected. primary_templated_fields now includes every
inputs value for agent nodes, so validate_parallel_reads treats them as
cross-branch reads with no new pass.

Design: plans/map-subgraph-branches-design.md §3.12 V10a–V10c, task T5.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 3393123baf feat(graph): seed a child graph agent's state from an agent node's inputs map
Agent nodes gain an optional `inputs:` map of templates resolved against
the parent state and overlaid on the child graph agent's initial_state
before its executor starts. A lone {{key}} keeps the state value's JSON
type; mixed text renders to a string; initial_prompt is still owned by the
dispatcher. run_child_agent and run_active_agent_graph keep their
signatures as wrappers over the new *_with_inputs variants, and
run_agent_for_graph rejects inputs aimed at a config-only agent or
containing initial_prompt.

Design: plans/map-subgraph-branches-design.md §3.12 (C18), task T5.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 0bef0f2c39 fix(graph): retire the teammate identity before structured extraction
PR #24 follow-up 10. The identity was only retired by the frontier and
chain-exit safety nets, so a peer stayed messageable during
structured::extract after its agent had already stopped. Mark it
finished the moment the agent future resolves, on success and on both
error paths.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 874ef7f6ec fix(graph): reject a literal max_concurrency of 0 instead of clamping it
Copilot review on PR #24. The validator already rejects 0 and the
template path errors on it, but a literal 0 loaded with
validate_before_run off was silently clamped to 1. Fail with the same
error shape as the template path; only the absent-cap default clamps.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 e746ca1f87 fix(graph): clear peer identity fields after every map chain step
Copilot review on PR #24. The fields were only ever cleared by
run_agent_for_graph taking them, so a failure between arming and that
take (e.g. prompt interpolation) left them set on the item's ctx.
Reset both unconditionally once the step returns.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 dc5e3201da refactor(graph): move the cfg(test) OUTPUT_KEY import into llm's test module
PR #24 follow-up 7.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 cdbff88b4f fix(graph): pick the map teammate identity name by BFS from the branch entry
PR #24 follow-up 6. The identity used to fall back to YAML declaration
order after the entry, so the name depended on how the author ordered
the nodes map. Walk next/fallback edges from the entry instead.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 62122fc698 refactor(graph): drop the duplicated locator prefix from map chain step errors
PR #24 follow-ups 3 and 4. run_chain_steps already wraps every step
error with the map/item/node/step locator, so the inner messages
repeated it. Make them prefix-free and turn the unreachable End arm
into an honest internal-invariant message.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 31c33b5a23 fix(graph): name the offending node type when a map branch reaches a disallowed node
PR #24 follow-up 2. With validate_before_run off, a user-authored
approval/input/end/map node inside a branch reached a message that
called itself an internal error. Name the node type and point at the
validator setting instead.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 2f839b332d fix(function): build bundled bash tools without writing back to the source tree
PR #24 follow-up 1. The bundled-tools test called the writing
generate_bash_declarations on the repo's assets/functions/tools/*.sh,
so cargo test could rewrite tracked files. Split the build+export step
into a pure build_bash_tool so the test never touches the source tree.
2026-09-11 10:56:27 -06:00
Dark-Alex-17 b65aa76d2d docs: updated the formatting of a few comments 2026-09-11 10:56:27 -06:00
Dark-Alex-17 d8f1778bc7 docs: describe map branches as per-item subgraph chains in graph.example.yaml
Comment-only rewrite of the map/branch and teammates comments: a branch is
the entry of a per-item chain; chain nodes may use next, state_updates,
output_schema, fallback and script _next inside the chain; output_key is
removed from the fork and must be written by the chain; max_concurrency
accepts an integer or a template; a teammate identity spans the item's
whole chain.
2026-09-11 10:56:27 -06:00