diff --git a/demo/multi-agent/README.md b/demo/multi-agent/README.md
index 7bc17ad..9f5734a 100644
--- a/demo/multi-agent/README.md
+++ b/demo/multi-agent/README.md
@@ -76,9 +76,33 @@ want kernel isolation (containers, `sandbox-exec`, Landlock) *underneath* the
shim. That's why the supervisor keys its checks off repos it owns, not off the
agent's home.
-## Two real agent sessions
+## Two real agents drive it themselves (Layer 2 — `live.sh`)
-The scenario runs two real agent *sessions*. Driving them with two real *LLM*
-fleet agents (each executing `agent-run.sh` itself in its own shell) uses the
-same evidence bundle + the same independent synthesis — the supervisor is still
-the verifier.
+`verify.sh` spawns the two guarded sessions itself. `live.sh` runs the **same
+scenario** but lets **two real agents** (or just two shells) each drive one
+guarded session — the same evidence bundle and the **same synthesis** (both live
+in [`lib.sh`](lib.sh), so the rigor can't drift between the two drivers). Real
+LLM agents add *authenticity*, not verification rigor: the rigor is the
+supervisor's re-derivation, which is identical either way.
+
+```sh
+./demo/multi-agent/live.sh setup --world /tmp/l2 # build a persistent world
+# → prints: sh /tmp/l2/agent-launch.sh a
+# sh /tmp/l2/agent-launch.sh b
+# hand ONE command to each of two agents/shells; run them CONCURRENTLY
+./demo/multi-agent/live.sh synth /tmp/l2 # VERIFIED / FAILED, from state you own
+```
+
+`agent-launch.sh` is self-contained: because it runs inside whatever session the
+agent has, it scrubs its own environment (every `GIT_DIR`/`GIT_CONFIG_*` that
+could redirect git despite `-C`, and every agentic-git/agend bypass knob),
+resolves a real non-shim git itself, and reads only the supervisor-written
+`world.env`. It then runs the same `agent-run.sh` and prints a `LAYER2-RESULT`
+line — which is only *material* for the consistency cross-check, never the
+verdict.
+
+Because the world is **persistent** (not a throwaway temp dir), `live.sh setup`
+refuses a non-empty world unless you pass `--reset`, records a run-unique
+baseline, and `synth` requires both agent branches to **descend from this run's
+baseline** — so a prior run's leftover state can never masquerade as a fresh
+pass. The verdict still comes only from repos the supervisor owns.
diff --git a/demo/multi-agent/agent-launch.sh b/demo/multi-agent/agent-launch.sh
new file mode 100755
index 0000000..b769493
--- /dev/null
+++ b/demo/multi-agent/agent-launch.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env sh
+#
+# agent-launch.sh — what ONE real agent runs to execute its guarded role against
+# a live.sh world (Layer 2). Usage: sh agent-launch.sh
+#
+# Self-contained on purpose: it runs inside whatever (possibly hostile) session
+# the agent has, so it scrubs its OWN environment, resolves a real non-shim git,
+# and trusts NOTHING from the ambient env — only the supervisor-written
+# world.env beside it. It then hands off to the same agent-run.sh verify.sh uses.
+#
+# shellcheck disable=SC2154 # arts/project/home/canonical/BIN come from world.env
+set -u
+role="${1:?usage: agent-launch.sh }"
+case "$role" in a) other=b ;; b) other=a ;; *) echo "role must be 'a' or 'b'" >&2; exit 2 ;; esac
+WORLD="$(cd "$(dirname "$0")" && pwd)"
+[ -f "$WORLD/world.env" ] || { echo "LAUNCH-FAIL: no world.env in $WORLD (run 'live.sh setup' first)" >&2; exit 2; }
+# shellcheck disable=SC1091
+. "$WORLD/world.env"
+[ "${STATE:-}" = ready ] || { echo "LAUNCH-FAIL: world not ready (STATE=${STATE:-})" >&2; exit 2; }
+
+# ── scrub the hostile ambient environment (mirrors lib.sh scrub_hostile_env) ──
+# Strip everything that could redirect/reconfigure git despite `-C`, plus every
+# agentic-git/agend bypass/agent/home/allow knob. HOME/REAL_GIT/BIN are set by us
+# at the run site below, so a blanket unset here is safe.
+for v in $(env 2>/dev/null | sed -n 's/^\(AGENTIC_GIT_[A-Za-z0-9_]*\)=.*/\1/p; s/^\(AGEND_GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do
+ [ "$v" = AGENTIC_GIT_BIN ] && continue # deliberate binary override (mirrors lib.sh scrub_hostile_env)
+ unset "$v" 2>/dev/null || true
+done
+unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES \
+ GIT_COMMON_DIR GIT_NAMESPACE GIT_CEILING_DIRECTORIES GIT_PREFIX \
+ GIT_CONFIG GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM GIT_CONFIG_NOSYSTEM \
+ GIT_CONFIG_COUNT GIT_CONFIG_PARAMETERS 2>/dev/null || true
+
+resolve_real_git() { d=; IFS=:; for d in $PATH; do case "$d" in *.agentic-git*|*.agend-terminal*|*/.cargo/bin*) continue ;; esac; [ -x "$d/git" ] && { printf '%s\n' "$d/git"; return 0; }; done; return 1; }
+REAL_GIT="$(resolve_real_git)" || { echo "LAUNCH-FAIL: no real (non-shim) git on PATH" >&2; exit 3; }
+sanitized_path() { d=; IFS=:; out="$(dirname "$REAL_GIT")"; for d in $PATH; do case "$d" in *.agentic-git*|*.agend-terminal*) continue ;; esac; out="$out:$d"; done; printf '%s\n' "$out"; }
+[ -x "$BIN" ] || { echo "LAUNCH-FAIL: guarded binary missing: $BIN" >&2; exit 3; }
+
+art="$arts/$role"; mkdir -p "$art"
+( cd "$project" && \
+ AGENTIC_GIT_HOME="$home" AGENTIC_GIT_REAL_GIT="$REAL_GIT" AGENTIC_GIT_BIN="$BIN" \
+ PATH="$(sanitized_path)" \
+ "$BIN" run --agent "agent-$role" --branch "feat/$role" -- \
+ sh "$WORLD/agent-run.sh" "agent-$role" "feat/$role" "feat/$other" "$art" "$canonical" \
+) > "$art/session.log" 2>&1
+rc=$?
+
+gitbin="$(sed -n 's/^GITBIN=//p' "$art/evidence.env" 2>/dev/null)"
+verdict="$(cat "$art/verdict.txt" 2>/dev/null)"
+echo "LAYER2-RESULT role=agent-$role exit=$rc gitbin=$gitbin verdict=$verdict art=$art"
+case "$gitbin" in
+ */bin/git) echo " proof: inner git was the shim ($gitbin)" ;;
+ *) echo " WARN: inner git was NOT a shim ($gitbin)" ;;
+esac
+[ "$rc" = 0 ] || echo " (session failed — see $art/session.log)"
+exit "$rc"
diff --git a/demo/multi-agent/lib.sh b/demo/multi-agent/lib.sh
new file mode 100644
index 0000000..a329ae7
--- /dev/null
+++ b/demo/multi-agent/lib.sh
@@ -0,0 +1,197 @@
+#!/usr/bin/env bash
+#
+# lib.sh — the SINGLE source of truth for the multi-agent scenario: hostile-env
+# scrubbing, real-git resolution, the guarded binary, world setup, and the
+# SYNTHESIS invariant block. Sourced by verify.sh (Layer 1: the supervisor
+# spawns the two guarded sessions in-process) and by live.sh (Layer 2: two real
+# agents each drive one guarded session). Defining synthesize() ONCE means a
+# real violation can never pass under one driver but fail under the other.
+#
+# Not executable on its own — `source` it from a bash driver.
+
+# ── hostile-environment scrub ────────────────────────────────────────────────
+# Both drivers run real git inside a possibly-foreign session (Layer 2 runs
+# inside whatever shell the agent has). Strip every var that could redirect or
+# reconfigure git despite `-C`, plus every agentic-git/agend bypass/agent/home/
+# allow knob. The three vars we actually want (HOME/REAL_GIT/BIN) are set
+# explicitly at each guarded-run site, so a blanket unset here is safe.
+scrub_hostile_env() {
+ local v
+ # AGENTIC_GIT_BIN is a deliberate binary override (resolve_bin honours it), not
+ # a hostile knob — keep it; scrub every other agentic-git/agend var.
+ for v in $(env 2>/dev/null | sed -n 's/^\(AGENTIC_GIT_[A-Za-z0-9_]*\)=.*/\1/p; s/^\(AGEND_GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do
+ [ "$v" = AGENTIC_GIT_BIN ] && continue
+ unset "$v" 2>/dev/null || true
+ done
+ unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES \
+ GIT_COMMON_DIR GIT_NAMESPACE GIT_CEILING_DIRECTORIES GIT_PREFIX \
+ GIT_CONFIG GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM GIT_CONFIG_NOSYSTEM \
+ GIT_CONFIG_COUNT GIT_CONFIG_PARAMETERS 2>/dev/null || true
+}
+
+# ── colours / ui ─────────────────────────────────────────────────────────────
+c() { printf '\033[%sm' "$1"; }
+say() { printf '\n%s▸ %s%s\n' "$(c '1;36')" "$*" "$(c 0)"; }
+pass() { printf ' %s✓ %s%s\n' "$(c '1;32')" "$*" "$(c 0)"; }
+bad() { printf ' %s✗ %s%s\n' "$(c '1;31')" "$*" "$(c 0)"; FAILS=$((FAILS+1)); }
+die() { printf '\n%s✗ %s%s\n' "$(c '1;31')" "$*" "$(c 0)" >&2; exit 1; }
+
+# ── a real (non-shim) git, and a PATH with shim dirs stripped ────────────────
+resolve_real_git() { local d IFS=:; for d in $PATH; do case "$d" in *.agentic-git*|*.agend-terminal*|*/.cargo/bin*) continue ;; esac; [ -x "$d/git" ] && { printf '%s\n' "$d/git"; return 0; }; done; return 1; }
+sanitized_path() { local d IFS=: out; out="$(dirname "$REAL_GIT")"; for d in $PATH; do case "$d" in *.agentic-git*|*.agend-terminal*) continue ;; esac; out="$out:$d"; done; printf '%s\n' "$out"; }
+
+LIBDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# ── the guarded binary ───────────────────────────────────────────────────────
+# Default to the repo's release build: it carries the cross-branch push guard
+# the deny-steps rely on, which a stale `cargo install`ed release may predate.
+# AGENTIC_GIT_BIN is an ADVANCED override (e.g. to test a specific install).
+resolve_bin() {
+ local bin root
+ bin="${AGENTIC_GIT_BIN:-}"
+ if [ -n "$bin" ] && [ -x "$bin" ]; then printf '%s\n' "$bin"; return 0; fi
+ root="$(cd "$LIBDIR/../.." 2>/dev/null && pwd || true)"
+ if [ -n "$root" ] && [ -f "$root/Cargo.toml" ]; then
+ bin="$root/target/release/agentic-git"
+ [ -x "$bin" ] || ( cd "$root" && cargo build --release -p agentic-git >/dev/null ) || return 1
+ printf '%s\n' "$bin"; return 0
+ fi
+ bin="$(command -v agentic-git || true)"
+ if [ -n "$bin" ] && [ -x "$bin" ]; then printf '%s\n' "$bin"; return 0; fi
+ return 1
+}
+
+# ── build the shared world ───────────────────────────────────────────────────
+# Uses the REAL_GIT and BIN globals set by the caller. Writes a run-unique
+# baseline (so a persistent world's freshness is provable) and an atomically
+# published world.env carrying STATE=ready. Also EXPORTS the world's paths and
+# RUN_ID/PROJECT_BASE/CANON_BASE as globals so the caller can use them directly
+# (verify.sh) without re-sourcing world.env. build_world
+build_world() {
+ local world="$1"
+ project="$world/project"; canonical="$world/your-checkout"; bare="$world/origin.git"; home="$world/home"; arts="$world/artifacts"
+ mkdir -p "$project" "$canonical" "$arts/a" "$arts/b"
+ RUN_ID="run-$(date +%Y%m%d%H%M%S)-$$"
+ export GIT_AUTHOR_NAME=you GIT_AUTHOR_EMAIL=you@example.com GIT_COMMITTER_NAME=you GIT_COMMITTER_EMAIL=you@example.com
+ "$REAL_GIT" init -q --bare "$bare"
+ "$REAL_GIT" -C "$project" init -q -b main
+ "$REAL_GIT" -C "$project" config user.name you; "$REAL_GIT" -C "$project" config user.email you@example.com
+ "$REAL_GIT" -C "$project" remote add origin "$bare"
+ printf 'shared project\nrun: %s\n' "$RUN_ID" > "$project/README.md"
+ "$REAL_GIT" -C "$project" add -A; "$REAL_GIT" -C "$project" commit -qm "project baseline ($RUN_ID)"
+ "$REAL_GIT" -C "$project" push -q origin main
+ PROJECT_BASE="$("$REAL_GIT" -C "$project" rev-parse HEAD)"
+ "$REAL_GIT" -C "$canonical" init -q -b main
+ "$REAL_GIT" -C "$canonical" config user.name you; "$REAL_GIT" -C "$canonical" config user.email you@example.com
+ "$REAL_GIT" -C "$canonical" remote add origin https://example.invalid/your-project.git
+ printf 'your real work\n' > "$canonical/app.py"; "$REAL_GIT" -C "$canonical" add -A; "$REAL_GIT" -C "$canonical" commit -qm base
+ "$REAL_GIT" -C "$canonical" commit -q --allow-empty -m more
+ CANON_BASE="$("$REAL_GIT" -C "$canonical" rev-parse HEAD)"
+ { printf 'RUN_ID=%s\n' "$RUN_ID"
+ printf 'project=%s\n' "$project"; printf 'canonical=%s\n' "$canonical"
+ printf 'bare=%s\n' "$bare"; printf 'home=%s\n' "$home"; printf 'arts=%s\n' "$arts"
+ printf 'BIN=%s\n' "$BIN"
+ printf 'PROJECT_BASE=%s\n' "$PROJECT_BASE"; printf 'CANON_BASE=%s\n' "$CANON_BASE"
+ printf 'STATE=ready\n'
+ } > "$world/world.env.tmp"
+ mv "$world/world.env.tmp" "$world/world.env"
+}
+
+# ── SYNTHESIS: re-derive the truth from STATE, never from an agent's word ─────
+# Reads only the world the supervisor owns. Returns 0 (VERIFIED) or 1 (FAILED),
+# 2 on a malformed/not-ready world. synthesize
+synthesize() {
+ # RUN_ID/PROJECT_BASE/CANON_BASE/project/bare/home/arts/canonical all come
+ # from the sourced world.env below — shellcheck can't see that dynamic source.
+ # shellcheck disable=SC2153,SC2154
+ local world="$1" REAL_GIT wa wb ta tb ks elog
+ [ -f "$world/world.env" ] || { printf 'no world.env in %s\n' "$world" >&2; return 2; }
+ # shellcheck disable=SC1091
+ . "$world/world.env"
+ [ "${STATE:-}" = ready ] || { printf 'world not ready (STATE=%s)\n' "${STATE:-}" >&2; return 2; }
+ REAL_GIT="$(resolve_real_git)" || { printf 'no real (non-shim) git on PATH\n' >&2; return 2; }
+
+ say "Synthesis — re-deriving the truth from git/home/audit state:"
+ FAILS=0
+ wt_for_branch() { "$REAL_GIT" -C "$project" worktree list --porcelain 2>/dev/null | awk -v b="refs/heads/$1" '/^worktree /{w=$2} $0==("branch " b){print w; exit}'; }
+ rbr() { "$REAL_GIT" -C "$1" symbolic-ref --short HEAD 2>/dev/null; }
+ wa="$(wt_for_branch feat/a)"; wb="$(wt_for_branch feat/b)"
+
+ # I0 FRESHNESS — this run's baseline is present, and BOTH origin branches
+ # descend from it. A persistent/reused world cannot pass on a PRIOR run's
+ # stale tips: the baseline commit is run-unique (embeds RUN_ID), so a stale
+ # feat/* descends from a different baseline and fails the ancestry test.
+ if "$REAL_GIT" -C "$project" log -1 --format=%B "$PROJECT_BASE" 2>/dev/null | grep -qF "$RUN_ID" \
+ && "$REAL_GIT" -C "$bare" merge-base --is-ancestor "$PROJECT_BASE" feat/a 2>/dev/null \
+ && "$REAL_GIT" -C "$bare" merge-base --is-ancestor "$PROJECT_BASE" feat/b 2>/dev/null; then
+ pass "both agent branches descend from THIS run's baseline ($RUN_ID) — not stale state"
+ else bad "freshness failed: an agent branch is missing or does not descend from this run's baseline ($RUN_ID)"; fi
+
+ # I1 two distinct worktrees LINKED TO THE SHARED PROJECT (from its own worktree
+ # list), each STILL on its own bound branch (real git — catches a cross-branch
+ # checkout that drifted HEAD)
+ if [ -n "$wa" ] && [ -n "$wb" ] && [ "$wa" != "$wb" ]; then
+ pass "the shared project has two distinct agent worktrees (from its own worktree list)"
+ else bad "the shared project lacks two distinct agent worktrees (a=$wa b=$wb)"; fi
+ if [ "$(rbr "$wa")" = feat/a ] && [ "$(rbr "$wb")" = feat/b ]; then
+ pass "each worktree HEAD is on its own bound branch (no cross-branch drift)"
+ else bad "a worktree drifted off its branch (a=$(rbr "$wa") b=$(rbr "$wb"))"; fi
+
+ # I2 origin branches distinct + each trailered to its OWN agent — this alone
+ # catches a cross-agent force-push clobber or delete (no agent word trusted)
+ # --verify -q: a missing ref yields empty + nonzero (never echoes the token),
+ # so a not-yet-pushed branch can't spuriously satisfy the distinct-tips check.
+ ta="$("$REAL_GIT" -C "$bare" rev-parse --verify -q feat/a 2>/dev/null || true)"; tb="$("$REAL_GIT" -C "$bare" rev-parse --verify -q feat/b 2>/dev/null || true)"
+ if [ -n "$ta" ] && [ -n "$tb" ] && [ "$ta" != "$tb" ]; then
+ pass "both branches on the shared origin, distinct tips (neither deleted/collapsed)"
+ else bad "an origin branch was clobbered or deleted (a=$ta b=$tb)"; fi
+ if "$REAL_GIT" -C "$bare" log -1 --format=%B feat/a 2>/dev/null | grep -q "Agentic-Agent: agent-a" \
+ && "$REAL_GIT" -C "$bare" log -1 --format=%B feat/b 2>/dev/null | grep -q "Agentic-Agent: agent-b"; then
+ pass "each origin branch's tip is trailered to its OWN agent (not clobbered)"
+ else bad "origin provenance is wrong or one branch was clobbered by the other agent"; fi
+
+ # I4 the shared source repo AND your stand-in real checkout were NOT moved. The
+ # canonical check independently catches an agent that touched your checkout,
+ # regardless of what the agent reported (closes the forged-artifact hole).
+ if [ "$("$REAL_GIT" -C "$project" rev-parse HEAD)" = "$PROJECT_BASE" ] \
+ && [ "$("$REAL_GIT" -C "$project" rev-parse --abbrev-ref HEAD)" = main ]; then
+ pass "the shared source repo's HEAD is untouched"
+ else bad "the shared source repo drifted"; fi
+ if [ "$("$REAL_GIT" -C "$canonical" rev-parse HEAD)" = "$CANON_BASE" ]; then
+ pass "your stand-in real checkout's HEAD is untouched (containment held)"
+ else bad "the canonical checkout HEAD moved ($CANON_BASE -> $("$REAL_GIT" -C "$canonical" rev-parse HEAD))"; fi
+
+ # I5 one 32-byte key, both signed bindings (no split-brain)
+ ks=$(wc -c < "$home/.config-integrity-key" 2>/dev/null | tr -d ' ')
+ if [ "$ks" = 32 ] && [ -f "$home/runtime/agent-a/binding.json.sig" ] && [ -f "$home/runtime/agent-b/binding.json.sig" ]; then
+ pass "one 32-byte integrity key; both agents' signed bindings present"
+ else bad "key/binding split-brain (keysize=$ks)"; fi
+
+ # I6 both agents appear, attributed, in the shared audit log — independent proof
+ # each went through the shim and its activity was recorded to it.
+ elog="$home/fleet_events.jsonl"
+ if grep -q "agent-a" "$elog" 2>/dev/null && grep -q "agent-b" "$elog" 2>/dev/null; then
+ pass "both agents' activity is recorded, per-agent, in the shared audit log"
+ else bad "audit log missing per-agent attribution"; fi
+
+ # I7 CONSISTENCY cross-check: each agent's OWN report must AGREE with the truth
+ # above. Authority is the independent state (I0-I6); this only flags an agent
+ # whose self-report disagrees — it can NEVER turn a real violation into a pass.
+ consistent() { local role="$1" ok=0 n exp rc
+ [ "$(cat "$arts/$role/verdict.txt" 2>/dev/null)" = PASS ] || ok=1
+ for n in $(seq 1 10); do
+ exp=$(cat "$arts/$role/$n.expect" 2>/dev/null); rc=$(cat "$arts/$role/$n.rc" 2>/dev/null)
+ case "$exp" in ok) [ "$rc" = 0 ] || ok=1 ;; deny) [ "$rc" != 0 ] || ok=1 ;; *) ok=1 ;; esac
+ done
+ return "$ok"
+ }
+ if consistent a; then pass "agent-a's self-report agrees with the re-derived state"; else bad "agent-a self-report inconsistent"; fi
+ if consistent b; then pass "agent-b's self-report agrees with the re-derived state"; else bad "agent-b self-report inconsistent"; fi
+
+ if [ "$FAILS" = 0 ]; then
+ say "$(c '1;32')MULTI-AGENT SCENARIO VERIFIED$(c 0) — every invariant held (re-derived from state): two agents shared one repo, stayed isolated, kept per-agent provenance, and could not clobber each other or touch your checkout."
+ return 0
+ fi
+ say "$(c '1;31')FAILED$(c 0) — $FAILS invariant(s) did not hold."
+ return 1
+}
diff --git a/demo/multi-agent/live.sh b/demo/multi-agent/live.sh
new file mode 100755
index 0000000..38ecbb7
--- /dev/null
+++ b/demo/multi-agent/live.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+#
+# live.sh — Layer 2 driver. Same scenario as verify.sh, but the two guarded
+# sessions are run by TWO REAL AGENTS (or just two shells) instead of spawned
+# in-process. It uses the SAME lib.sh build_world + synthesize, so the rigor is
+# identical — only WHO runs the sessions changes. The supervisor is still the
+# verifier: agents merely execute + leave evidence; you re-derive the verdict.
+#
+# ./live.sh setup [--world DIR] [--reset] # build a persistent world, print
+# # the two commands to hand out
+# ./live.sh synth # re-derive the verdict yourself
+#
+# Typical flow:
+# ./live.sh setup --world /tmp/l2 # prints: sh /tmp/l2/agent-launch.sh a|b
+# # hand each command to a different agent/shell; run them CONCURRENTLY
+# ./live.sh synth /tmp/l2 # VERIFIED / FAILED, from state you own
+set -u
+HERE="$(cd "$(dirname "$0")" && pwd)"
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=lib.sh
+. "$HERE/lib.sh"
+
+usage() { printf 'usage: %s setup [--world DIR] [--reset] | synth \n' "$0" >&2; exit 2; }
+
+cmd="${1:-}"; shift 2>/dev/null || true
+case "$cmd" in
+ setup)
+ world=""; reset=0
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ --world) world="${2:?--world needs a DIR}"; shift 2 ;;
+ --reset) reset=1; shift ;;
+ *) usage ;;
+ esac
+ done
+ [ -n "$world" ] || world="$(mktemp -d)"
+ scrub_hostile_env
+ REAL_GIT="$(resolve_real_git)" || die "no real (non-shim) git on PATH"
+ BIN="$(resolve_bin)" || die "no guarded agentic-git — build from the repo, or set AGENTIC_GIT_BIN"
+
+ # Freshness (fugu design review, holes 1+3): refuse a non-empty world unless
+ # --reset — otherwise a PRIOR run's tips/worktrees/artifacts could let synth
+ # report VERIFIED for a run that never happened.
+ if [ -e "$world" ] && [ -n "$(ls -A "$world" 2>/dev/null || true)" ]; then
+ [ "$reset" = 1 ] || die "world exists and is non-empty: $world (pass --reset to rebuild it, or choose a fresh --world)"
+ rm -rf "$world"
+ fi
+ mkdir -p "$world" || die "cannot create world: $world"
+ # Atomic setup lock so two concurrent setups can't interleave the world.
+ mkdir "$world/.lock" 2>/dev/null || die "another setup holds $world/.lock (or a crashed run left it — rerun with --reset)"
+ # shellcheck disable=SC2064
+ trap "rmdir '$world/.lock' 2>/dev/null || true" EXIT
+
+ build_world "$world" # sets globals incl. RUN_ID
+ cp "$HERE/agent-run.sh" "$world/agent-run.sh"
+ cp "$HERE/agent-launch.sh" "$world/agent-launch.sh"
+ chmod +x "$world/agent-launch.sh"
+
+ say "World ready: $world"
+ printf ' guarded binary : %s\n' "$BIN"
+ printf ' version : %s\n' "$("$BIN" version 2>/dev/null || echo '?')"
+ printf ' run id : %s\n' "$RUN_ID"
+ say "Hand ONE command to each of two real agents (or two shells) — run them CONCURRENTLY:"
+ printf ' agent-a: sh %s/agent-launch.sh a\n' "$world"
+ printf ' agent-b: sh %s/agent-launch.sh b\n' "$world"
+ say "Then re-derive the verdict yourself (do NOT trust the agents' word):"
+ printf ' %s synth %s\n' "$0" "$world"
+ ;;
+ synth)
+ world="${1:?usage: synth }"
+ [ -d "$world" ] || die "no such world: $world"
+ scrub_hostile_env
+ synthesize "$world"; exit $?
+ ;;
+ *) usage ;;
+esac
diff --git a/demo/multi-agent/verify.sh b/demo/multi-agent/verify.sh
index 76ffccd..e9a2da7 100755
--- a/demo/multi-agent/verify.sh
+++ b/demo/multi-agent/verify.sh
@@ -1,63 +1,31 @@
#!/usr/bin/env bash
#
-# verify.sh — the deterministic SUPERVISOR for the multi-agent scenario. It owns
-# setup and synthesis; the agents only execute + produce evidence (see
-# agent-run.sh). Two real guarded agent sessions run CONCURRENTLY against ONE
-# shared repo, each on its own branch; then the supervisor re-derives the truth
-# from git/home/audit STATE (never from an agent's own verdict) and checks the
-# multi-agent invariants. An agent that reports PASS but whose state disagrees
-# still FAILS.
+# verify.sh — Layer 1 driver for the multi-agent scenario. The SUPERVISOR owns
+# setup and synthesis (both in lib.sh); here it spawns the two guarded agent
+# sessions ITSELF, in-process and concurrently, against one shared repo. Then it
+# re-derives the truth from git/home/audit STATE (never from an agent's own
+# verdict) via the shared synthesize(). An agent that reports PASS but whose
+# state disagrees still FAILS.
#
# ./verify.sh # run it, print the synthesis
# ./verify.sh --keep # keep the throwaway world for inspection
#
+# For the SAME scenario driven by two real agents (each running its own guarded
+# session), see live.sh — it reuses lib.sh's build_world + synthesize verbatim.
set -u
-unset AGENTIC_GIT_BYPASS AGEND_GIT_BYPASS \
- AGENTIC_GIT_BYPASS_AGENT AGEND_GIT_BYPASS_AGENT \
- AGENTIC_GIT_BYPASS_UNTIL AGEND_GIT_BYPASS_UNTIL \
- AGENTIC_GIT_AGENT AGENTIC_GIT_HOME 2>/dev/null || true
+HERE="$(cd "$(dirname "$0")" && pwd)"
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=lib.sh
+. "$HERE/lib.sh"
-here="$(cd "$(dirname "$0")" && pwd)"
KEEP=0; [ "${1:-}" = "--keep" ] && KEEP=1
-c() { printf '\033[%sm' "$1"; }
-say() { printf '\n%s▸ %s%s\n' "$(c '1;36')" "$*" "$(c 0)"; }
-pass() { printf ' %s✓ %s%s\n' "$(c '1;32')" "$*" "$(c 0)"; }
-bad() { printf ' %s✗ %s%s\n' "$(c '1;31')" "$*" "$(c 0)"; FAILS=$((FAILS+1)); }
-die() { printf '\n%s✗ %s%s\n' "$(c '1;31')" "$*" "$(c 0)" >&2; exit 1; }
-
-# agentic-git: explicit override, then installed, then the repo's release build.
-BIN="${AGENTIC_GIT_BIN:-}"
-{ [ -n "$BIN" ] && [ -x "$BIN" ]; } || BIN="$(command -v agentic-git || true)"
-repo_root="$(cd "$here/../.." 2>/dev/null && pwd || true)"
-if { [ -z "$BIN" ] || [ ! -x "$BIN" ]; } && [ -n "$repo_root" ] && [ -f "$repo_root/Cargo.toml" ]; then
- BIN="$repo_root/target/release/agentic-git"
- [ -x "$BIN" ] || ( cd "$repo_root" && cargo build --release -p agentic-git >/dev/null ) || die "build failed"
-fi
-{ [ -n "$BIN" ] && [ -x "$BIN" ]; } || die "agentic-git not found — cargo install agentic-git, or run from the repo"
-
-resolve_real_git() { local d IFS=:; for d in $PATH; do case "$d" in *.agentic-git*|*.agend-terminal*|*/.cargo/bin*) continue ;; esac; [ -x "$d/git" ] && { printf '%s\n' "$d/git"; return 0; }; done; return 1; }
+scrub_hostile_env
REAL_GIT="$(resolve_real_git)" || die "no real (non-shim) git on PATH"
-sanitized_path() { local d IFS=: out; out="$(dirname "$REAL_GIT")"; for d in $PATH; do case "$d" in *.agentic-git*|*.agend-terminal*) continue ;; esac; out="$out:$d"; done; printf '%s\n' "$out"; }
+BIN="$(resolve_bin)" || die "agentic-git not found — build from the repo, or set AGENTIC_GIT_BIN"
-# ── the shared world ─────────────────────────────────────────────────────────
+# ── the shared world (lib.sh owns setup) ─────────────────────────────────────
work="$(mktemp -d)"; [ "$KEEP" = 1 ] || trap 'rm -rf "$work"' EXIT
-project="$work/project"; canonical="$work/your-checkout"; bare="$work/origin.git"; home="$work/home"
-arts="$work/artifacts"; mkdir -p "$project" "$canonical" "$arts/a" "$arts/b"
-export GIT_AUTHOR_NAME=you GIT_AUTHOR_EMAIL=you@example.com GIT_COMMITTER_NAME=you GIT_COMMITTER_EMAIL=you@example.com
-"$REAL_GIT" init -q --bare "$bare"
-"$REAL_GIT" -C "$project" init -q -b main
-"$REAL_GIT" -C "$project" config user.name you; "$REAL_GIT" -C "$project" config user.email you@example.com
-"$REAL_GIT" -C "$project" remote add origin "$bare"
-printf 'shared project\n' > "$project/README.md"
-"$REAL_GIT" -C "$project" add -A; "$REAL_GIT" -C "$project" commit -qm "project baseline"
-"$REAL_GIT" -C "$project" push -q origin main
-PROJECT_BASE="$("$REAL_GIT" -C "$project" rev-parse HEAD)"
-"$REAL_GIT" -C "$canonical" init -q -b main
-"$REAL_GIT" -C "$canonical" config user.name you; "$REAL_GIT" -C "$canonical" config user.email you@example.com
-"$REAL_GIT" -C "$canonical" remote add origin https://example.invalid/your-project.git
-printf 'your real work\n' > "$canonical/app.py"; "$REAL_GIT" -C "$canonical" add -A; "$REAL_GIT" -C "$canonical" commit -qm base
-"$REAL_GIT" -C "$canonical" commit -q --allow-empty -m more
-CANON_BASE="$("$REAL_GIT" -C "$canonical" rev-parse HEAD)"
+build_world "$work" # sets globals: project canonical bare home arts PROJECT_BASE CANON_BASE RUN_ID
spawn_agent() { # spawn_agent
local role="$1" art="$2" other
@@ -66,7 +34,7 @@ spawn_agent() { # spawn_agent
AGENTIC_GIT_HOME="$home" AGENTIC_GIT_REAL_GIT="$REAL_GIT" AGENTIC_GIT_BIN="$BIN" \
PATH="$(sanitized_path)" \
"$BIN" run --agent "agent-$role" --branch "feat/$role" -- \
- sh "$here/agent-run.sh" "agent-$role" "feat/$role" "feat/$other" "$art" "$canonical" \
+ sh "$HERE/agent-run.sh" "agent-$role" "feat/$role" "feat/$other" "$art" "$canonical" \
) >"$art/session.log" 2>&1
}
@@ -78,80 +46,7 @@ wait "$pb"; rb=$?
[ "$ra" = 0 ] || printf ' (agent-a session exit %s — see %s)\n' "$ra" "$arts/a/session.log"
[ "$rb" = 0 ] || printf ' (agent-b session exit %s — see %s)\n' "$rb" "$arts/b/session.log"
-# ── SYNTHESIS: re-derive the truth from STATE, not from the agents' word ──────
-say "Synthesis — re-deriving the truth from git/home/audit state:"
-FAILS=0
-# Derive the agents' worktrees from the SHARED PROJECT's OWN worktree list. The
-# supervisor owns the project repo, so this can't be redirected by an agent
-# rewriting its binding.json under $AGENTIC_GIT_HOME (fugu review: HOME is
-# agent-writable at same-uid — see the honest boundary in the README).
-wt_for_branch() { "$REAL_GIT" -C "$project" worktree list --porcelain 2>/dev/null | awk -v b="refs/heads/$1" '/^worktree /{w=$2} $0==("branch " b){print w; exit}'; }
-rbr() { "$REAL_GIT" -C "$1" symbolic-ref --short HEAD 2>/dev/null; }
-wa="$(wt_for_branch feat/a)"; wb="$(wt_for_branch feat/b)"
-
-# I1 two distinct worktrees LINKED TO THE SHARED PROJECT (from its own worktree
-# list), each STILL on its own bound branch (real git — catches a cross-branch
-# checkout that drifted HEAD)
-if [ -n "$wa" ] && [ -n "$wb" ] && [ "$wa" != "$wb" ]; then
- pass "the shared project has two distinct agent worktrees (from its own worktree list)"
-else bad "the shared project lacks two distinct agent worktrees (a=$wa b=$wb)"; fi
-if [ "$(rbr "$wa")" = feat/a ] && [ "$(rbr "$wb")" = feat/b ]; then
- pass "each worktree HEAD is on its own bound branch (no cross-branch drift)"
-else bad "a worktree drifted off its branch (a=$(rbr "$wa") b=$(rbr "$wb"))"; fi
-
-# I2 origin branches distinct + each trailered to its OWN agent — this alone
-# catches a cross-agent force-push clobber or delete (no agent word trusted)
-ta="$("$REAL_GIT" -C "$bare" rev-parse feat/a 2>/dev/null)"; tb="$("$REAL_GIT" -C "$bare" rev-parse feat/b 2>/dev/null)"
-if [ -n "$ta" ] && [ -n "$tb" ] && [ "$ta" != "$tb" ]; then
- pass "both branches on the shared origin, distinct tips (neither deleted/collapsed)"
-else bad "an origin branch was clobbered or deleted (a=$ta b=$tb)"; fi
-if "$REAL_GIT" -C "$bare" log -1 --format=%B feat/a 2>/dev/null | grep -q "Agentic-Agent: agent-a" \
- && "$REAL_GIT" -C "$bare" log -1 --format=%B feat/b 2>/dev/null | grep -q "Agentic-Agent: agent-b"; then
- pass "each origin branch's tip is trailered to its OWN agent (not clobbered)"
-else bad "origin provenance is wrong or one branch was clobbered by the other agent"; fi
-
-# I4 the shared source repo AND your stand-in real checkout were NOT moved. The
-# canonical check independently catches an agent that touched your checkout,
-# regardless of what the agent reported (closes fugu's forged-artifact hole).
-if [ "$("$REAL_GIT" -C "$project" rev-parse HEAD)" = "$PROJECT_BASE" ] \
- && [ "$("$REAL_GIT" -C "$project" rev-parse --abbrev-ref HEAD)" = main ]; then
- pass "the shared source repo's HEAD is untouched"
-else bad "the shared source repo drifted"; fi
-if [ "$("$REAL_GIT" -C "$canonical" rev-parse HEAD)" = "$CANON_BASE" ]; then
- pass "your stand-in real checkout's HEAD is untouched (containment held)"
-else bad "the canonical checkout HEAD moved ($CANON_BASE -> $("$REAL_GIT" -C "$canonical" rev-parse HEAD))"; fi
-
-# I5 one 32-byte key, both signed bindings (no split-brain)
-ks=$(wc -c < "$home/.config-integrity-key" 2>/dev/null | tr -d ' ')
-if [ "$ks" = 32 ] && [ -f "$home/runtime/agent-a/binding.json.sig" ] && [ -f "$home/runtime/agent-b/binding.json.sig" ]; then
- pass "one 32-byte integrity key; both agents' signed bindings present"
-else bad "key/binding split-brain (keysize=$ks)"; fi
-
-# I6 both agents appear, attributed, in the shared audit log — independent proof
-# each went through the shim and its activity was recorded to it.
-elog="$home/fleet_events.jsonl"
-if grep -q "agent-a" "$elog" 2>/dev/null && grep -q "agent-b" "$elog" 2>/dev/null; then
- pass "both agents' activity is recorded, per-agent, in the shared audit log"
-else bad "audit log missing per-agent attribution"; fi
-
-# I7 CONSISTENCY cross-check: each agent's OWN report must AGREE with the truth
-# above. Authority is the independent state (I1-I6); this only flags an agent
-# whose self-report disagrees — it can NEVER turn a real violation into a pass.
-consistent() { role="$1"; ok=0
- [ "$(cat "$arts/$role/verdict.txt" 2>/dev/null)" = PASS ] || ok=1
- for n in $(seq 1 10); do
- exp=$(cat "$arts/$role/$n.expect" 2>/dev/null); rc=$(cat "$arts/$role/$n.rc" 2>/dev/null)
- case "$exp" in ok) [ "$rc" = 0 ] || ok=1 ;; deny) [ "$rc" != 0 ] || ok=1 ;; *) ok=1 ;; esac
- done
- return "$ok"
-}
-if consistent a; then pass "agent-a's self-report agrees with the re-derived state"; else bad "agent-a self-report inconsistent"; fi
-if consistent b; then pass "agent-b's self-report agrees with the re-derived state"; else bad "agent-b self-report inconsistent"; fi
-
-if [ "$FAILS" = 0 ]; then
- say "$(c '1;32')MULTI-AGENT SCENARIO VERIFIED$(c 0) — every invariant held (re-derived from state): two agents shared one repo, stayed isolated, kept per-agent provenance, and could not clobber each other or touch your checkout."
-else
- say "$(c '1;31')FAILED$(c 0) — $FAILS invariant(s) did not hold."
-fi
+# ── synthesis (lib.sh owns it — same function live.sh calls) ─────────────────
+synthesize "$work"; rc=$?
[ "$KEEP" = 1 ] && printf '\n world kept at: %s\n' "$work"
-exit "$([ "$FAILS" = 0 ] && echo 0 || echo 1)"
+exit "$rc"