Skip to content

A listener finds, K assigned fixers land in parallel, and every widening re-enters the gate - #98

Open
Shashankss1205 wants to merge 5 commits into
mainfrom
listener-fixer-a1
Open

A listener finds, K assigned fixers land in parallel, and every widening re-enters the gate#98
Shashankss1205 wants to merge 5 commits into
mainfrom
listener-fixer-a1

Conversation

@Shashankss1205

Copy link
Copy Markdown
Collaborator

Phase 1 of the agents-as-nodes plan: autonomous agents as graph nodes, with the admission gate deciding every round. Five commits, each PR-sized with its own failure-mode gate tests; the full suite is green after each one (2,025 passed at HEAD).

What lands

  • grapharc.registries.fix_issues — the listener/fixer job under the full RegistryBundle contract: scan_issues appends one issue per entry, the planner proposes one fix_one instance per outstanding issue (all edged from the same predecessor, so they execute in one superstep, concurrently), then verify_fixes and report. A round may carry a continuing scan and fixers for already-found issues — pipeline overlap with zero new mechanism, and every widening re-enters admission. The scripted rehearsal runs the refusal story against the default policy: round 1's eager fixer is rejected edge_denied and never executes, and the final report says out loud how to enable fixing.
  • Args under admission (closes ROADMAP §5.6 minimally)NodeSpec.args_schema puts a proposal's args under a sixth check, one rejection per failing field with the remedy naming the schema; the Materializer forwards the validated dump and re-validates on build, so an edited proposal or a swapped registry refuses to build. The slim parse path gains args/note so a small model's fixer does not arrive argless. fix_one requires its FixAssignment — the fingerprint an approval binds now includes who was assigned what.
  • Write leases — a harness pre-hook: the first writer holds a path, the second is refused with the holder named, and the refusal lands in the loser's report as data. Reads are never gated; the lease dies with its node.
  • The delegated tier ladder — the default for Claude-CLI-backed agent nodes changes from bypassPermissions to an allowlist mapped from the node's own tools (read_fileRead, … run_commandBash), so one operator declaration governs both tiers; bypass is reachable only by name and stamped on every trace event. Two adjacent defects fixed: --max-tokens under claude-cli was accepted and silently unapplied (now refused with the reason), and a timed-out delegate orphaned Claude Code's own shells (the CLI now runs in its own session and the deadline kills the whole group).
  • The V1 gate task — bench fix_all_issues: three planted defects under src/, each file's docstring stating the invariant its code violates, graded per fix (files_contain; half done is not a success), config/ protected. ROADMAP V1 stays mechanism done, gate not run and now names precisely which recorded live-model run flips it.

Verified

  • 30 new gate tests across test_fix_issues_gate.py, test_admission.py, test_slim_proposal.py, test_leases.py, test_delegate_tiers.py, test_agent_delegate.py — each pinning a refusal, not a happy path.
  • The scripted rehearsal end-to-end through the real CLI: grapharc plan "fix the issues in this repo" --registry grapharc.registries.fix_issues:build_registry --scripted --go → rejected, admitted, admitted, goal_met, honest report.
  • All four bench grading states (unfixed / half-fixed / fixed / protected-dir tampered) behave correctly.
  • Docs moved with the code: admission's six-check table, the governance cookbook's byte-verified transcripts, README and deep-dive Limits, both architecture diagrams, ROADMAP §5.6 and the V1 row.

🤖 Generated with Claude Code

Shashankss1205 and others added 5 commits August 7, 2026 01:07
…sed first

grapharc.registries.fix_issues ships the listener/fixer job under the full
RegistryBundle contract: scan_issues appends one issue per entry, the planner
proposes one fix_one instance per outstanding issue — all edged from the same
predecessor, so they land in one superstep — and every widening re-enters the
admission gate. fix_one is registered and denied by default; the scripted
rehearsal runs against that default on purpose, so the free path demonstrates
the refusal and ends with a report that says what was refused and how to
enable it. With no model, nothing is registered: naming a kind then fails as
unregistered_node at the gate, not at materialisation.

Gate tests pin the refusals: the eager fixer never executes, a three-fixer
round beyond the token budget is rejected with the shortfall recorded, and two
fixers finishing together merge through the reducers instead of colliding.

Phase 1 of the agents-as-nodes plan (PR A1). Args-carrying assignments,
write leases, and the delegated tier ladder follow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s them

ROADMAP 5.6 closed minimally. A kind may declare NodeSpec.args_schema — an
operator's Pydantic model, extra=forbid — and admission gains a sixth check:
a proposal's args for that kind must validate, one rejection per failing
field with the schema and its fields named in the remedy. The Materializer
forwards the *validated* dump and re-validates on build, so a registry
swapped underneath the decision or a proposal edited after it refuses to
build; the fingerprint already hashed args, so an approval binds who was
assigned what. Kinds without a schema keep the old contract, stated where it
was always stated: args uninspected, dropped unless forward_args=True.

The slim parse path gains args and note — a schema-declaring kind proposed
by a small model otherwise always arrived argless and was always refused —
with null and prose args tolerated down to {}, because the gate, not the
reader, is what judges whether none was enough.

fix_one now requires its FixAssignment: each fixer proposal says which issue
it takes, verbatim, and the text feeds the fixer's prompt, never a tool
call. The governance cookbook, deep dive, README limit, and both
architecture diagrams move in the same commit, so no page says five checks
over a gate that runs six.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…by name

grapharc.tools.leases: the first write_file/edit_file to touch a path claims
it for that node, and a second writer's call is denied with the holder named
— enforcement as a harness pre-hook, per call, after permissions and before
the executor, with the tools themselves untouched. An agent reads its tool
refusals, so the losing fixer learns who has the file and works on something
else instead of crashing the batch. Reads are never gated: serializing the
listeners is the parallelism the lease exists to keep safe.

Leases key on the resolved path, so a dressed-up spelling contends with the
plain one; a workspace escape stays the tool's own refusal, which names the
actual problem. A lease lives exactly as long as its node's execution — the
fix_issues factory releases in a finally — so a later round edits what an
earlier round wrote. Scope stated plainly in the module: advisory within one
process, core write tools only; run_command children and delegated agents
mutate un-leased.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…allowlist

A delegated agent node ran Claude Code under bypassPermissions by
construction — the unconfined tier was the only tier. It is now a ladder
with the fail-closed rung as the default: `allowlist` hands Claude Code
exactly the node's own registered tools, mapped name for name
(read_file->Read, ... run_command->Bash), so one operator declaration
governs both the governed loop and the delegated one; anything unlisted
falls to Claude Code's headless default gating, which fails closed.
`bypass` still exists and still means everything — but only by name, and
the construction warning and every trace event say which tier ran.

Two neighbouring defects fixed in the same change, both of the
limit-that-exists-only-in-the-invocation kind: --max-tokens under
--executor claude-cli was accepted and silently unapplied, and is now
refused with the reason; and the subprocess deadline killed only the direct
child, so Claude Code's own spawned shells survived as orphans — the CLI
now runs in its own session and the deadline kills the whole group, the
same way the sandbox executor does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… each

bench gains fix_all_issues — the listener/fixer shape, measured: three
planted defects under src/, each file's docstring stating the invariant its
code violates, graded per fix with a new files_contain check (half done is
not a success), config/ protected with the temptation planted in the retry
policy's own docstring. It exists to be ROADMAP milestone V1's gate for
grapharc.registries.fix_issues; the row stays "mechanism done, gate not run"
and now says precisely which recorded run flips it. The deep dive states the
lease limit alongside the other edges: concurrent fixers conflict as data,
and nothing merges divergent edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant