Canonical pack source: packs/agentops-membrane/ in the AgentOps repo (git-tracked). Imported into a city via pack.toml (pinned tree-URL, or local
path while iterating). Operator loop that drives it:
using-gc.
Constraints
- Treat
packs/agentops-membrane/as the canonical source and installed city copies as projections, because parallel edits create an untraceable close-door fork. - Fail closed: no canonical, nonce-bound, cross-family terminal verdict means the quest stays open, because agent-written synthesis is not proof.
- Never auto-merge or auto-push and never let a reviewer mutate the builder worktree, because author/judge separation and human merge authority are load-bearing boundaries.
Why it exists (the one structural gap)
Stock gc orchestrates superbly but has no fail-closed close: its
mol-review-quorum formula fans review out cross-family and even ships a
deterministic Go finalizer (reviewquorum.Finalize) — which has zero
production callers. The quorum verdict is whatever the synthesis agent
writes; an agent can synthesize "pass" over two failing lanes. That is
self-grading — exactly what AgentOps exists to prevent (no verdict = not
done). The pack replaces agent-written synthesis with a deterministic,
evidence-bound verdict computed by scripts the control dispatcher runs.
What the pack ships
| Piece | Purpose |
|---|---|
formulas/membrane-quest.toml |
v2 workflow: worktree build with [steps.check] → close-gate.sh, five ordinary attempts plus one helper-guided recovery proof (max_attempts = 6), check gc.work_dir pinned to the city root, ralph-gate-bead protection in the recovery snippet |
membrane/close-gate.sh |
the close door (run by the dispatcher, never an agent) |
membrane/finalize.jq + finalize.sh |
deterministic decision: canonical pawl-verdict.v1 only for terminal semantics; gc-review-attempt.v1 for transport degradation |
membrane/scaffold-quest.sh + quests/_template/ |
move-1 intake: default-FAIL CONTRACT.md, red test.sh |
agents/{planner,builder,verifier,agy-verifier,opus-verifier,breaker-helper} |
work/review roles plus a plan-only one-shot breaker advisor, RBAC via harness config |
doctor/law0-print-args, doctor/membrane-health |
blocking doctor checks |
orders/membrane-canary.toml |
scheduled structural smoke via the pack's e2e.sh entrypoint |
tests/{finalize,intake}.bats |
pins the rollup parity + scaffold contract (bash+jq only) |
RESIDUAL-GAPS.md |
the two honest zero-nudge gaps + mitigations |
The close-door flow, end to end
- Intake:
scaffold-quest.sh <slug>writesCONTRACT.md(default-FAIL acceptance ruler) +test.sh(executable red harness) on the quest repo'smain. The gate later reads the contract frommainONLY (git show main:CONTRACT.md); the builder never reads CONTRACT.md — it readstest.sh. - Build: the builder (claude family, own worktree, keeps write) makes
test.shgreen on a branch. - Check step fires (control dispatcher,
[steps.check]):close-gate.shruns deterministic pre-gates (branch exists, non-empty diff vs main, contract present) → mints a per-round nonce → routes ONLY the diff + contract to ≥2 cross-family reviewer lanes viagc session submit(the only verb that reliably drains an idle pane) → collectsreview-quorum.lane.v1JSONs → hands to the finalizer. - Finalize (
finalize.jq) computes the decision. Precedence: hard > transient > findings > pass, hardened beyond the upstream rollup:- Nonce anti-replay: a lane verdict without the exact round nonce is rejected as stale.
- Cross-family floor: < 2 distinct provider families can NEVER
CONFIRM (
fewer_than_two_families). - Degradation-aware: transient lane loss (provider unavailable,
timeout) ⇒ DEGRADED — never converted into a false REFUTE. Honest
attempt contract (corrected 2026-07-06): on native graph.v2, gc's
ralph dispatcher never reads
gc.failure_class— every nonzero check exit consumes one ofmax_attempts(6), DEGRADED included; the class stamp is evidence, not budget control. - Artifact separation: CONFIRMED/REFUTED write a closed-schema canonical
verdict whose refuter evidence is copied to contained nonempty files.
DEGRADED removes the round verdict path and writes a nonsemantic
gc-review-attempt.v1; it cannot overwrite the latest terminal verdict.
- Outcome: exit 0 CONFIRMED → the dispatcher closes the ralph gate
bead with the ENGINE fingerprint (final
gc.attempt_logentryoutcome=pass) and the workflow finalizes pass; any nonzero exit → builder respawned with findings, one of the five ordinary attempts consumed. A fifth failure creates one disposable session and dispatches ONE-HELPER to its unique ID. UNSTUCK gets attempt six and must re-earn CONFIRMED; ESCALATE terminates before a sixth review. Exhaustion leaves the quest bead OPEN — never a spurious close. The membrane never merges or pushes; a human merges the branch.
Two structural protections (added after the 2026-07-06 canary catches):
- The ralph gate bead is engine-owned. It shares the build step's title;
an agent closing it with
gc.outcome=passused to fail the door OPEN. The formula's recovery snippet filtersgc.kind=ralph, the prompt forbids touching it, and the fork's finalizer accepts a closed-with-pass gate ONLY with the engine attempt_log fingerprint (patch(dispatch)4aa582dcc). - The check's working dir is pinned (
"gc.work_dir" = "."on the step): without it the check inherits gc's per-bead-worktree path this formula never creates, the dispatcher chdir fails, and the check silently never runs (visible only in the ralph bead'sgc.attempt_log— the check path emits nogc events; setGC_WORKFLOW_TRACEfor the dispatch trace).
RBAC — author ≠ judge, enforced as config
Builder = claude family (opus), write access, only in its worktree.
Verifiers = codex/gpt (LANE1) and antigravity/gemini via agy (LANE2),
permission_mode = "plan" (read-only), fresh-context, receive only
diff + contract. Never same-family self-grade: quorum is builder-family vs
two other families. City wiring makes both lanes always-on [[named_session]]
entries so submit-delivery is deterministic.
Failover (agy outage) — an explicitly DEGRADED-DIVERSITY mode: the
default contract above excludes the BUILDER family from the reviewer set
(claude judged by {codex, gemini}). agents/opus-verifier deliberately
trades that one property away when the third family is unavailable: it keeps
fresh-context author≠judge (a different SESSION carrying none of the builder
context) and the ≥2-distinct-reviewer-families floor ({codex, claude}), but
one reviewer now shares the builder FAMILY. Strictly weaker than the default;
strictly stronger than the alternatives (single-family review, or burning the
attempt budget on DEGRADE) — the same tradeoff the repo pawl service makes
running opus+codex panels against claude authors. Temporary posture: restore
the agy lane when its auth heals. Opt in per city:
[workspace.env] MEMBRANE_LANE2_TARGET = "agentops-membrane.opus-verifier" +
MEMBRANE_LANE2_FAMILY = "claude", add its [[named_session]], bounce the
supervisor (the check runs in the supervisor process — workspace env reaches
lanes via session spawn, but the LANE2 selection is read by close-gate from
the check process env, so the supervisor must carry it: plist env or restart
after config). Restore the agy lane when its auth heals.
pawl-verdict.v1 — reading a verdict
Artifacts land at <city>/membrane/<quest>/runs/<workflow-root>/:
pawl-verdict-round-N.json (+ latest copied to pawl-verdict.json),
lane-<family>-round-N.json, nonce-round-N.txt.
Terminal artifact example (actual required shape emitted by finalize.sh):
{
"schema_version": "pawl-verdict.v1",
"bead_id": "quest-bead",
"pr": 0,
"head_sha": "<reviewed-sha>",
"disposition": "CONFIRMED",
"generated_at": "2026-07-11T00:00:00Z",
"mode": "multi-model",
"author_context_id": "builder-session",
"attempt": 3,
"refuters": [
{"family": "gpt", "reviewer": "lane-1", "context_id": "lane-1", "verdict": "CONFIRMED", "evidence": "<city>/membrane/<quest>/runs/<workflow-root>/evidence-round-3/lane-1.json"},
{"family": "gemini", "reviewer": "lane-2", "context_id": "lane-2", "verdict": "CONFIRMED", "evidence": "<city>/membrane/<quest>/runs/<workflow-root>/evidence-round-3/lane-2.json"}
]
}
Checks when reading: required terminal fields; disposition; ≥2 distinct
refuters[].family on any CONFIRMED. Nonce anti-replay happens before terminal
emission: finalize.sh compares each input lane's agentops_nonce with the
round nonce. Terminal refuters do not invent a nonce_echo field. On REFUTED
read the copied evidence-round-<N>/lane-<index>.json findings (a placement/path finding may be a diff-frame
mismatch — a contract bug, not a build bug; fix the contract frame instead
of burning redo attempts).
Output Specification
- Path: write evidence to
<city>/membrane/<quest>/runs/<workflow-root>/; source changes belong only underpacks/agentops-membrane/. - Filename: terminal output uses
pawl-verdict-round-<N>.jsonpluspawl-verdict.json; raw inputs uselane-<family>-round-<N>.jsonandnonce-round-<N>.txt; terminal refuter evidence is copied toevidence-round-<N>/lane-<index>.json; degradation writesreview-attempt-round-<N>.json. - Format: terminal verdicts follow the closed
pawl-verdict.v1JSON schema; degradation writes nonsemanticgc-review-attempt.v1JSON and cannot replace the latest terminal verdict. - Validation command: run
bats packs/agentops-membrane/tests/{finalize,intake,close-gate}.bats; for an installed city also runbash skills/gc-membrane/scripts/e2e.sh --city <city>. - Downstream handoff: the gc dispatcher consumes disposition and exit code to close or retry the ralph gate; a human consumes the confirmed branch and evidence before merge.
Quality Checklist
- Every CONFIRMED round was derived from lane
agentops_noncevalues matching the exact round nonce and has at least two distinct reviewer families. - REFUTED findings remain in contained lane artifacts, while DEGRADED remains transport evidence rather than semantic judgment.
- Failed review auto-redoes; the fifth failure creates, uses, and closes exactly one fresh-context helper session before the sixth recovery proof or terminal escalation. The membrane never merges or pushes.
Self-verification
doctor/membrane-health(blocking): door present + executable, formula resolves, trinity present, ≥2 provider families configured.doctor/law0-print-args(blocking): every claude/antigravity provider carries an explicit emptyprint_args(the builtin defaults are headless print sinks — LAW 0).orders/membrane-canary: native cooldown order running the pack'se2e.shentrypoint (both doctor checks green +gc lint) — structural smoke, surfaced throughgc events/dashboard.
Residual gaps (honest, not papered over)
Two session/provider-boundary stalls stand between "installed" and
zero-touch — the idle-pane drain (only gc session submit recovers; a
keepalive order re-submits on cooldown) and the codex/agy first-run trust
modal (pre-trust via city-scoped CODEX_HOME / one interactive run). Both
documented with mitigations in the pack's RESIDUAL-GAPS.md and as
first-class moves in the troubleshooting ladder of
using-gc.
The durable fixes are upstream (headless verifier lane / submit-consume
guarantee) — the pack does not fake a root fix.
Non-goals
No auto-merge/auto-push. No wrapping of gc's native surface. No new
stall-detection framework. No gating on gc costs. No runtime=gc
re-coupling and no gc subcommand under the ao CLI — the pack is gc-native
content that lives BESIDE ao/NTM.
Scenarios
Scenario: Fail-closed verdict on a hard finding
Given a quest round where one reviewer lane returns a hard finding with a valid nonce echo
When finalize.sh computes the verdict
Then the disposition is REFUTED with exit 2 and the quest bead stays open
Scenario: Transient lane loss never false-refutes
Given a quest round where one reviewer lane is provider_unavailable
When finalize.sh computes the verdict
Then the disposition is DEGRADED with exit 3 and the quest is not falsely REFUTED
Scenario: DEGRADED still consumes a redo attempt (budget pinned to the formula)
Given membrane-quest.toml sets max_attempts = 6 and the ralph dispatcher never reads gc.failure_class
When a check exits nonzero as DEGRADED (exit 3), exactly as for a REFUTED (exit 2)
Then one of max_attempts is consumed — a transient-loss flake burns budget like a real refute
Scenario: Retry exhaustion executes the helper rung
Given five ordinary attempts have failed
When close-gate.sh handles the fifth non-CONFIRMED result
Then it creates and closes one disposable helper; UNSTUCK gets one recovery proof while ESCALATE dispatches no review
Scenario: One family can never confirm
Given a quest round where all lane verdicts come from a single provider family
When finalize.sh computes the verdict
Then the result is fewer_than_two_families and the disposition is not CONFIRMED
Proof it works — updated (live evidence, 2026-07-05 + 2026-07-07)
2026-07-07 (native graph.v2, canary city, zero nudges): hello3 ran the
full loop — rounds 1–2 REFUTED hard by real cross-family findings (gpt:
contract_failure, gemini: ask_not_met), builder self-redo from findings,
round 3 CONFIRMED ({gpt, gemini}, nonce-bound, head-bound), ralph closed
by the engine fingerprint, workflow finalized pass, human merged. The door's
first two native runs also CAUGHT two real defects in itself (the fail-open
and the check-never-ran above) — the dogfood harness working as designed.
Artifacts: /Users/bo/gc/canary-city/membrane/hello3/.
Prior proof (live evidence, 2026-07-05)
Three real quests through the door on the native reference city:
REFUTED→redo→CONFIRMED (hello); DEGRADED on transient agy-lane loss
without a false refute (csv-stats); cross-family hard REFUTE where the
gpt lane caught 3 real blocking gaps the gemini lane passed
(install-gc-city) — the quest correctly stayed open. Artifacts:
<city>/membrane/{hello,csv-stats,install-gc-city}/pawl-verdict*.json.