Skip to content

demo: multi-agent verification scenario (agents + independent synthesis)#20

Merged
suzuke merged 4 commits into
mainfrom
demo/multi-agent
Jul 6, 2026
Merged

demo: multi-agent verification scenario (agents + independent synthesis)#20
suzuke merged 4 commits into
mainfrom
demo/multi-agent

Conversation

@suzuke

@suzuke suzuke commented Jul 6, 2026

Copy link
Copy Markdown
Owner

What

Two agents share one repo at once, each on its own branch — and the scenario checks the multi-agent invariants actually hold. verify.sh launches agent-a and agent-b concurrently, each in its own guarded session running agent-run.sh. Each agent does real work, pushes its own branch, tries to interfere with the other, and leaves a machine-recomputable evidence bundle.

Design point (adversarial review): an agent's own PASS is not proof. The supervisor re-derives the truth from git/home/audit state (never the agent's narrative) and checks nine invariants — distinct worktrees on bound branches · git-is-the-shim in both sessions · both branches on the shared origin · per-agent provenance (not mixed) · isolated working trees · shared source repo untouched · one key + both signed bindings · every guarded step behaving (own push ok; cross-agent checkout/force-push/delete all DENIED) · each self-verdict consistent with state.

Why it's a real test

Invariant 8 reads the actual exit codes, so it FAILS if a guard doesn't fire — it is designed to catch a regression of exactly the cross-agent force-push clobber (PR #19) whose gap this scenario's design first surfaced.

Verification

No src/ changes.

🤖 Generated with Claude Code

suzuke and others added 4 commits July 6, 2026 12:52
…ervisor verifies)

Two agents share one repo at once, each on its own branch. verify.sh (the
deterministic supervisor) launches agent-a and agent-b CONCURRENTLY, each in its
own guarded session running agent-run.sh; each agent does real work, pushes its
own branch, tries to interfere with the other, and leaves a machine-recomputable
evidence bundle (every command's raw stdout/stderr/exit-code + a state snapshot).

Per the adversarial design review: an agent's own PASS is not proof — the
supervisor RE-DERIVES the truth from git/home/audit state (never the agent's
narrative) and checks nine invariants: distinct worktrees on bound branches,
git-is-the-shim in both sessions, both branches on the shared origin, per-agent
provenance (not mixed), isolated working trees, the shared source repo
untouched, one integrity key with both signed bindings, every guarded step
behaving (own push ok; cross-agent checkout/force-push/delete all DENIED), and
each agent's self-verdict consistent with the re-derived state.

Invariant 8 reads the real exit codes, so it FAILS if a guard doesn't fire —
it is designed to catch a regression of exactly the cross-agent force-push
clobber (PR #19) whose gap this scenario's design first surfaced.

Verified end-to-end (9/9 invariants held with the installed 0.2.2 + PR #19).
Throwaway world in a temp dir; only remote is a local bare repo. shellcheck
clean (SC2015 excluded: pass/bad are non-failing printf helpers).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…se forged-artifact hole)

fugu review REJECTED: the synthesis still trusted agent-written artifacts for the
canonical-checkout invariant. It only checked the shared project repo, never the
stand-in real checkout, and took the agent's own rc/verdict files as the source
of truth — so an agent could move the canonical checkout's HEAD, write 10.rc=1 +
verdict=PASS, and the synthesis reported success (fugu reproduced it).

Rewrite the synthesis to RE-DERIVE every invariant from independent state:
- worktrees come from the shim-written, HMAC-signed bindings (not the agent's
  evidence.env); each worktree's current branch is read with real git (catches a
  cross-branch checkout that drifted HEAD).
- cross-agent push/delete is caught by the origin state itself: both branches
  present, distinct, each trailered to its OWN agent.
- NEW: the stand-in real checkout's HEAD must equal its pre-run value — this
  independently catches an agent touching your checkout regardless of what it
  reported (the exact hole).
- the agent's rc/verdict becomes a CONSISTENCY cross-check only: it can flag a
  self-report that disagrees, but can never turn a real violation into a pass.

Verified: normal run VERIFIED (11 invariants, re-derived); and fugu's forge
attack — tamper the canonical HEAD + fake agent-a's 10.rc=1/verdict=PASS — is now
caught by the independent canonical invariant. shellcheck: verify.sh fully clean;
agent-run.sh clean except an intentional single-quoted sh -c (SC2016).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…writable HOME

fugu review REJECTED again (deeper): the synthesis called binding.json
authoritative but never verified its HMAC signature — I5 only checked that .sig
FILES exist. A same-uid agent with $AGENTIC_GIT_HOME can rewrite its binding.json
to point at a fake clean repo (sig stale but present), and the synthesis accepted
the fake worktree. fugu reproduced it.

Root cause: HOME is agent-writable at same-uid (the agent can even read the HMAC
key and re-sign), so NO home-derived state is trustworthy against a malicious
agent — the seatbelt-not-cage limit.

Fix: the supervisor now derives the agents' worktrees from the SHARED PROJECT's
own `git worktree list` (the supervisor owns the project repo; an agent can't
redirect it by editing HOME). Dropped the cosmetic file-leak check (an agent can
create any file in its own worktree — no signal). All the load-bearing
invariants already key off supervisor-owned state (bare origin branches +
provenance, the stand-in checkout's HEAD, the project's HEAD).

README now states the honest boundary explicitly: this verifies the guards for
agents going THROUGH the shim (the threat model); a malicious same-uid agent
that forges its HOME state or bypasses the shim via /usr/bin/git is out of scope
— which is exactly why the supervisor keys off repos it owns, not the agent's home.

Verified: normal run VERIFIED; the binding-forgery no longer redirects the
supervisor (it reads the project's worktree list → the real worktree). verify.sh
shellcheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… stale claims)

fugu review REJECTED (documentation): after moving the load-bearing checks to
supervisor-owned state, the README still advertised invariants the code no longer
has — a nine-item list including "working trees isolated (file-leak)" (dropped as
cosmetic) and "invariant 8 reads exit codes to prove guarded steps behaved" (exit
codes are now only the non-authoritative consistency cross-check). A reader was
told the scenario proves properties it no longer checks — the opposite of the
honest boundary this scenario is about.

Rewrite the invariant list and the "why it's a real test" section to match the
actual supervisor-owned checks: the shared project's own worktree list, origin
branches + per-agent provenance (this is what catches a clobber), the shared
repo's and the stand-in checkout's HEADs unchanged, one non-split-brain key,
per-agent audit attribution, plus the agents' self-report as a consistency
cross-check only. Also fix the intro's "re-derives from git/home/audit" to
"state it owns … never the agent's home".

Docs only; verify.sh unchanged (fugu confirmed the code-side forge fixes at
94589cc).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@suzuke suzuke merged commit bb52335 into main Jul 6, 2026
4 checks passed
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