check-corpus-freshness.sh, check-compile-health.sh, check-compile-oscillation.sh
used 'find ... -printf %T@ %p | sort | awk' to pick the newest file by mtime.
-printf is GNU-only; BSD/macOS find errors. corpus-freshness (set -uo) silently
false-SKIPs even when snapshots exist; compile-health/oscillation (set -euo
pipefail) crash via pipefail when the overnight-defrag fallback path is hit.
Same class as age-iue5 (#850) but the harder mtime-sort form. No bats coverage
-> not in the failing-12; found by class-sweep.
Fix (after a cross-family pawl refuted the first 'find -exec ls -t {} + | head'
attempt — that is NOT a global sort when find batches across multiple ls calls,
and an added -type f dropped symlinked snapshots): emit '<mtime>\t<path>' per
match via portable stat (BSD 'stat -f %m' / GNU 'stat -c %Y' fallback), then a
single global 'sort -n | tail -n 1' (tail consumes all input, avoiding the
head+pipefail SIGPIPE trap). corpus-freshness keeps the original symlink-
inclusive match (no -type f). Integer-second precision (vs the original's
fractional %T@) is adequate for these seconds-apart dirs.
Verified on real BSD find+stat: global-newest correct across distinct mtimes,
zero-match -> empty, corpus-freshness now PASSES (was false-SKIP), health/
oscillation exit 0, shellcheck clean, no -printf left in scripts/.
build_goals_section used 'find -type d -printf %f\n' to list fitness-baseline
labels. -printf is GNU-only; BSD/macOS find errors ('unknown primary') and
exits non-zero. Under set -euo pipefail the 2>/dev/null-suppressed failure
still propagates (pipefail) and kills the latest_label assignment -> exit 1,
breaking the json digest path and nightly-evolution.bats test 32. Masked in
diagnosis because the interactive shell shims find->bfs; only the script's
real BSD find fails. Fix: -exec basename {} \; (POSIX, faithful to %f).
32/32 nightly-evolution tests green.
Root-cause fix surfaced by the E5 volume harvest (age-lrl9): the producer was
timeout-wrapped but the MEMBRANE review had NO timeout, so a hung 'codex exec'
(observed: a 22-min stall) froze the whole run forever — degrading 4/6 harvest
runs and starving clean volume. Adds --membrane-timeout (default 240s; 0 disables)
+ a run_membrane() helper that wraps the review in gtimeout/timeout when present;
a killed review yields empty output and the task is excluded as degraded via the
existing unparseable-verdict path. Makes the HARNESS robust, not just the
caller-side workaround in run-harvest-series.sh. 3 bats (stalled->degraded fast,
fast->adjudicates, usage documents the flag). Also adds shellcheck-disable for the
2 pre-existing intentional single-quoted DEFAULT_*_CMD lines the changed-file gate
now sees.
Bead: age-9h3d
A maximal-adversarial refuter over LLM prose has an infinite false-alarm tail (proven
dogfooding pawl-review.sh: rounds 1-3 found 8 real defects, round 4+ only cosmetic).
This operationalizes the bounded-land-decision as the convergence PROTOCOL (council C),
NOT a free --bar knob: an adversarial run records lineage (.agents/pawl-review/
<bead>.adversarial.json = the reviewed diff-hash); --converge switches to the calibrated
real-safety bar but writes a verdict ONLY if a prior adversarial run covered the
IDENTICAL diff — no/changed lineage => ADVISORY-ONLY (exit 4). The adversarial pass can
never be skipped (no gate-weakening). 17 bats incl. the full convergence flow.
Bead: age-cwo.8
The pawl's verdict bookkeeping (pawl-verdict.sh) and the review docs (pre-land-refuters)
existed, but RUNNING the cross-family review was a manual codex+prompt+parse+write dance
on every land (~16x this session). This makes it one command: diff -> refuter prompt ->
codex exec (cross-family, fresh-context, read-only; LAW 0: never claude -p) -> parse
VERDICT -> CONFIRMED writes+verifies the commit-bound verdict (exit 0); REFUTED prints
defects, writes nothing (exit 3); fail-closed on no clear verdict. 5 bats with a codex stub.
Dogfooded: this command produced its own landing verdict.
Bead: age-8n7p
A full `ao gate check --full` sweep (the true-CI equivalent the routine
`--fast --scope head` push gate skips) surfaced 6 red gates on main. Two are real
committed-repo defects, now fixed:
1. skill.catalog-drift — skills/catalog.json was stale (72 -> 75 skills + missing
trigger annotations); regenerated via generate-skill-catalog.sh.
2. always.docs-hookless — false-positived on docs/audits/docsweep-2026-06-17/
findings.json, an audit RECORD that quotes a stale "SessionStart hook" pattern in
its `claim` field precisely to FLAG it as stale-as-current (its evidence/fix say
it is retired). Added `audits` to the gate's EXCLUDE_DIRS, consistent with the
existing `code-map/` point-in-time-audit exclusion.
The other 4 red gates are local data-health (`ao defrag`, `ao feedback-loop --drain`)
or session-state (uncommitted-during-sweep), NOT main defects — operator maintenance,
nothing to commit.
Pawl: CONFIRMED. Both gates now green; routine gate 25/25.
Bead: age-ufte
Verification-coverage audit (council-directed: "find missing verdict edges"): the
push-to-main #trivial pawl-waiver in check-pawl-pre-push.sh skipped the cross-family
pawl on the commit MESSAGE alone, with NO diff check — so any change mislabeled
#trivial bypassed the membrane's core claim, "no verdict = not done". (100% of
legitimate #trivial commits touch only docs/provenance/ledger.jsonl.)
Fix: waive ONLY when every changed file is under docs/provenance/ (the sole
established #trivial use); a #trivial commit touching code/scripts/skills/other-docs
has its waiver REFUSED and still faces the pawl (falls through to the normal
requirement). Fail-closed: a failed diff-tree OR an empty file list does NOT waive;
--no-renames exposes a rename FROM a non-provenance path INTO docs/provenance/.
Test: 3 new/updated bats — provenance-only waives; non-provenance #trivial is
refused; a refused #trivial WITH a CONFIRMED verdict falls through and authorizes.
Pawl: CONFIRMED rev2 after REFUTED rev1 caught 3 real holes (diff-tree exit discarded;
rename evasion; the test didn't prove fall-through).
Bead: age-u43w
Allowlist reverse-engineer in the skill-flow connectivity gate, and reconcile the scenario-linkage allowlist (drop deleted beads.feature, add behavior-first-planning / dual-pane-atm / orchestrate / reverse-engineer feature files). Other failing checks are pre-existing main-wide breakage unrelated to this change.
Operational completion of E6: the incremental miner (E6.2) + its e2e (E6.3) were
landed but had never RUN on real logs (anti-cathedral: a miner that never runs
makes no contact). mine-all-sessions.sh drives `ao provenance mine-session` over a
session dir and appends per-inference provenance events to a LOCAL, gitignored
event store (.agents/provenance/mine-events.jsonl), incremental via per-session
--state. Proven on the real corpus: 146 sessions -> 17,883 events, 0 failed;
re-run is incremental (idempotent via --state).
Soundness (cross-family pawl: CONFIRMED rev2 after REFUTED rev1 caught 4 real
defects self-review missed): exit 1 on any session failure (no false-green exit
0); rollback removes a newly-created advanced state on failure so a re-run
re-mines unstored events (no data loss); stale .bak cleared before snapshot;
summary JSON built with jq (escaping); single-writer mkdir lock; deterministic
find|sort ordering.
Test: tests/scripts/mine-all-sessions.bats (6 cases incl. the failure/rollback
data-loss path).
Bead: age-membrane-memory-arch-tz2s.6.4
The self-improvement loop fired only on ONE box: .agents/constraints/index.json is
gitignored, so a clean CI checkout / fresh clone enforced nothing — a constraint
learned locally never hardened the repo for anyone else. EM.2.9 makes the learning
travel.
Cross-family council CONFIRMED Option A:
- ao constraint publish (NEW): exports the ACTIVE constraints to a TRACKED surface
docs/constraints/published.json. DELIBERATE act, not auto-on-activate — a derived
regex that hardens the whole repo for everyone should be a conscious, committed,
reviewable change (mirrors the draft->activate human gate).
- SANITIZED: search.SanitizeForPublish strips the private fields (finding_id +
the .agents/-pointing source_artifact/review_file/file paths), so the tracked
surface carries ONLY the enforceable detector — no private findings/evidence leak.
- The enforce gate now reads the UNION of the local (gitignored) index and the
tracked published surface, merged by id with active-wins. A clean CI checkout
(no .agents/) enforces exactly what was published. Malformed published.json fails
CLOSED, consistent with the local index. Retiring a published rule is a tracked
change to the published file, not a silent local override.
Proven on the shipped binary (probe + done-test step 8 TRAVEL): publish -> hide
.agents/ entirely (simulate a clean clone) -> the published constraint STILL FAILs
the re-introduction. The em-loop done-test now asserts the COMPLETE loop in 8 steps:
fixture -> compile -> activate -> block -> safe -> cite -> load -> travel.
Tests: SanitizeForPublish no-leak (search); publish writes sanitized active-only
(cmd/ao); gate PublishedTravels-no-local-index + MalformedPublished-fail-closed +
merge active-wins (gates). make regen-all updated the command surfaces for the new
`ao constraint publish`.
Bead: age-membrane-memory-arch-tz2s.2.9
Completes the EM-parent claim. EM.TEST proved the BLOCK half (escape -> constraint
-> gate blocks re-introduction). EM.4 proves the auto-loads+cites (COGNITION) half:
the deterministic loader the next in-domain pre-mortem calls — "ao lookup --query
DOMAIN", per skills/pre-mortem/SKILL.md:80 — mechanically SURFACES the escape-
derived check by domain, CITING the escape. The membrane's memory is RETRIEVABLE,
not just written to disk.
The loader already existed (collectFindings over .agents/findings/, relevance-
ranked); EM.4 PROVES it on the shipped binary rather than building new machinery.
Added step 7 (LOAD) to scripts/em-loop-donetest.sh: after derive-checks writes the
finding for an escape in domain D, "ao lookup --query D" returns it citing the
escape bead. The done-test now asserts the FULL claim end-to-end: load + cite + block.
No Go change — the cognition loader was already shipped; this closes the last
named half of the EM acceptance spine that EM.TEST explicitly scoped out.
Bead: age-membrane-memory-arch-tz2s.2.4
Proves the headline product claim is TRUE on the real ao binary, not just in a unit
lane: a false CONFIRMED that a later REFUTED overturns (an ESCAPE) carrying a
mechanical detector becomes a deterministic gate-block of its own re-introduction.
scripts/em-loop-donetest.sh (+ bats wrapper) runs the real organs over an ISOLATED
temp project on the freshly-built ao binary and asserts the full chain:
1. FIXTURE — synthetic mechanical escape seeded via the real CLI
(EM.3, production ledger shape, round-tripped by the real writer).
2. COMPILE — -> a DRAFT constraint in index.json.
3. ACTIVATE — draft->active, AND asserts a DRAFT gates nothing (the activate-guard:
the SAME violation PASSes as draft, FAILs once active — no false-green).
4. BLOCK — re-introduce the pattern -> constraints.enforce FAILs deterministically.
5. SAFE — remove it -> PASS (no false positive).
6. CITE — the derived finding cites the escape (the next pre-mortem's input).
SCOPE: proves the gate-block (ENFORCED) half + the cite artifact; the pre-mortem
actually LOADING it is EM.4 (.2.4), asserted separately — no overclaim.
Building the e2e immediately caught a REAL escape the unit tests + the .2.10 pawl
missed: parsed the detector fields into the body
but emitYieldEvent DROPPED them when building GateVerdictInput (the CLI producer
SEAM, not the writer — the .2.10 unit test set the struct directly and bypassed it).
So on the real binary no constraint compiled. Fixed: thread detector_pattern /
constraint_path_globs / detector_kind body->input. Extended the existing
TestEmitYieldEvent_GateVerdictCarriesDomainAndReason (which existed for the SAME
EM.2.1 body->input drop class) to cover the detector fields. This is the membrane
catching itself again — recorded as an escape-corpus candidate (EM.CORPUS .2.11).
Beads: age-membrane-memory-arch-tz2s.2.3 (EM.3) + age-membrane-memory-arch-tz2s.2.6 (EM.TEST)
Bead: age-membrane-memory-arch-tz2s.2.6
An ESCAPE (a REFUTED gate-verdict overturning a prior CONFIRMED for the same
bead in the same run) is the membrane's decoupled failure signal; recall-by-domain,
derive-checks, and the SPC governor's per-domain special-cause detection route on
its domain, and its reason is 'what was missed'. Today an escape could be recorded
domain-less AND reason-less, silently degrading all of that.
The yield-ledger emit is fail-open by contract (callers guard with || true), so a
naive 'reject if empty' would silently DROP the escape — strictly worse. Three
cross-family councils shaped this (design Option A CONFIRMED; two verification pawls
each REFUTED a real hole, both fixed here):
- AppendGateVerdict (writer chokepoint, EVERY emitter): an overturning-REFUTED with
no domain is stamped DomainUnclassified AND with no reason ReasonUnspecified —
never lost, both gaps made visible. IsOverturningRefuted + ApplyEscapeSentinels
mirror DetectEscapes (same bead, same run, strictly higher attempt).
- Fail-SAFE on a degraded ledger: StampEscapeSentinels stamps a REFUTED's empty
fields when the pre-append ledger can't be read (a corrupt/unreadable existing
ledger — a missing one loads as empty, not an error), so the guarantee survives
even when the overturn can't be verified. (2nd pawl finding.)
- DetectEscapes: escape domain falls back to the REFUTED's tag (the conscious catch)
so a reviewer tagging only the overturn still classifies the escape.
- pawl-verdict.sh write: REQUIRE --reason on REFUTED (the standing-pawl auto-logger
already supplies it). Domain is NOT hard-required at the script — the fail-open
auto-logger can't always resolve one and an exit there would drop the catch; the
writer sentinel guarantees it instead.
- deriveFindingFromEscape: UNCLASSIFIED domain and unspecified reason are visible
debt, never real signal — surfaced by ao membrane recall --domain UNCLASSIFIED.
reconcile-pr's gate-verdict emit IS the pawl-verdict.sh write chokepoint (age-uxva),
so wiring that covers both producers named in the bead.
Tests: 11 Go L2 (both sentinels stamped + preserved, fail-safe on corrupt ledger,
run-scoped predicate, domain fallback) + 3 bats (script REQUIRE) + strengthened
provenance-roundtrip intent.
Bead: age-membrane-memory-arch-tz2s.2.1
The pre-registration (.agents/research/2026-06-16-efc-transfer-preregistration.md §55)
prescribed a read-only script over the yield ledger once N>=50 runs. That threshold is
now MET (the ledger grew from N=3 to 75), so this mechanizes the power gate:
scripts/efc-power-gate.py reports RUNNABLE iff >=50 runs AND >=15 in the DECOUPLED
failure class (escapes = CONFIRMED-then-higher-attempt-REFUTED, NOT disposition which is
disqualified as circular §40-47). It computes NO ΔAUC and manufactures NO verdict
(§58: below the gate, record INCONCLUSIVE and wait).
Current verdict on the real ledger: INCONCLUSIVE — 75 runs (>=50 met) but 0 escapes
(<15). The binding constraint FLIPPED from total-N to failure-class scarcity: the
membrane is effective enough that the experiment has no failures to predict. 5 bats
tests incl the rework-is-not-an-escape (non-circularity) case. Read-only; safe.
age-k2w stays open (still INCONCLUSIVE); this auto-detects when it becomes runnable.
The escape (2026-06-22): a partial commit (a multi-path `git add` that aborted on a
deleted-file pathspec, silently dropping files) builds in the WORKING TREE, so it
passed `ao gate check` 29/29 + a CONFIRMED cross-family pawl, then landed origin/main
build-broken. The pre-push gate validated the worktree, not the committed state.
The derived check (membrane self-improvement, council-decided Option A): when the
worktree differs from HEAD for tracked source, build each pushed commit in an ISOLATED
temp worktree (scripts/verify-pushed-commit-builds.sh), wired into the pre-push hook
after the worktree build. Two-sided fitness: catches the partial-commit class (commit
doesn't build) with ZERO false alarms (it judges the commit in isolation, never fails
because the user kept editing after committing); clean-tree pushes skip it entirely
(worktree == HEAD). Fail-OPEN on infra errors; fail-CLOSED only on a real build
failure; bypass AGENTOPS_PREPUSH_SKIP_COMMIT_BUILD=1. 5 bats tests, shellcheck clean.
Re-scoped by mixed council (Claude + Codex unanimous, Option A): the original
'consolidate 3 scattered breakers' premise was STALE — rpi_loop_supervisor.go was
deleted by ADR-0009/soc-2rtm0. The living breakers run at DIFFERENT timescales by
design (planpawl/decide.go = fast loop; halt-check.sh = evolve outer loop; the SPC
governor = slow-loop setpoint), so merging them would be wrong.
Instead, the bounded coherence wire that makes the slow-loop setpoint LOAD-BEARING:
scripts/evolve/halt-check.sh now consults 'ao governor budget' (env-overridable via
EVOLVE_GOVERNOR_CMD). A burned budget (exit 3 = harden) halts the evolve cycle
(governor_budget_burned). Halt ONLY on exit 3; ship (0) continues; any other
non-zero is a tool error that FAILS OPEN — a broken governor never wedges the loop.
Operator KILL/STOP still outrank it. 4 new bats tests; all 17 green.
control-loop-model.md §5 updated: the governor is BUILT and is the canonical
slow-loop owner; the '3 breakers' framing is obsolete.
Closes age-wy3.3
The consumer side was built (reconcile-pr.sh derive_yield_tokens reads
AO_YIELD_TRANSCRIPT) but nothing SET it, so bead-close yield-usage events
carried the hardcoded 0. ptts's premise — 'ao orchestrate is the bead-close
producer' — was stale: ao orchestrate is an instrument lane (preflight/route/
status/shape/verify) with no bead-close step, and a transcript only exists at
session END. Re-scoped per a cross-family design council: the SETTER lives at
the bead-close path.
- scripts/reconcile-pr.sh: add --yield-transcript <jsonl> — validates a readable
file, exports AO_YIELD_TRANSCRIPT (abs path) so the pre-built derive path emits
REAL tokens at close. Explicit pass-in only (no ambient auto-discovery → can't
grab a wrong/stale transcript). Stale 'orchestrator sets it' comment fixed.
Test seams: AO_YIELD_LEDGER_ROOT (isolation) + AO_YIELD_EMIT_SYNC (deterministic).
- tests/scripts/reconcile-pr.bats: a confirmed close with --yield-transcript emits
a usage event whose tokens_in/out match ao yield tokens (non-zero, not 0).
Deferred (per council): transcript auto-discovery, ao orchestrate close/spawn
wrappers, br hooks, NTM pane-to-transcript indexing, multi-pane attribution.
Closes age-ptts
Completes i4m's last open part. The local lever was already active (pre-push.local
emits deterministic catches; bootstrap-wired in session_bootstrap.go; proven by
emit-deterministic-catch.bats) — but hooks DON'T fire in CI, so CI deterministic
gate FAILs were dropped. Now go-gate-shadow, on a gate FAIL, emits the same
REFUTED/mode=deterministic gate-verdict into the (gitignored, ephemeral) CI yield
ledger and uploads it as the ci-deterministic-catch artifact. A maintainer folds
it into their LOCAL gauge with scripts/ingest-ci-deterministic-catches.sh — NO
commit-back (the gauge ledger is gitignored/local), idempotent, deterministic-tier
only. L2 bats proves artifact->ingest->gauge counts (catch_rate=1) + idempotency.
- .github/workflows/validate.yml (emit + upload on go-gate-shadow FAIL, non-blocking)
- scripts/ingest-ci-deterministic-catches.sh (artifact -> local gauge, idempotent)
- tests/scripts/ingest-ci-deterministic-catches.bats (L2 emit->ingest->gauge)
Closes age-activate-deterministic-catch-default-on-i4m
scripts/check-membrane-parity.sh fails when the demo-path `ao` is missing a
membrane subcommand OR a local flag that current source declares, so the e2e
membrane demo (age-membrane-memory-arch-tz2s.2.6) can never run against a stale
binary and emit a false proof. The required surface is derived from a fresh
source build (never hardcoded); flag parity is parsed structurally from cobra's
flag column (robust to flags named in description prose).
- scripts/check-membrane-parity.sh + 'make membrane-parity' documented-path target
- tests/scripts/check-membrane-parity.bats (6 cases: subcommand + flag parity,
description-token robustness, not-executable, harness-error; set -u/bash-3.2 safe)
Cross-family pawl caught 2 real false-pass defects (name-only parity;
description-token miscount), both fixed + locked as regression tests.
Refs age-membrane-memory-arch-tz2s.2.5
Two membrane-reliability fixes to eval-membrane.sh:
1. SMOKE probe: the membrane binary can exist yet be non-functional (agy installed
but auth-down returns empty), silently degrading EVERY task to no-verdict — a
whole eval run wasted. Probe the membrane once up front and fail fast with an
actionable message (re-auth / pass --membrane-cmd) instead.
2. LINE-ANCHORED verdict parsing (smoke + runtime): the verdict grep is now
^VERDICT: ... so a membrane that echoes/logs the reviewer prompt (whose format
line literally reads "line 1: VERDICT: ACK ... OR VERDICT: REFUTE") cannot be
mis-parsed as a real verdict — it must emit the verdict on its own line.
Verified: dead/empty/echo membranes fail fast or degrade; real ACK/REFUTE parse
correctly. Cross-family review (Claude+Codex) CONFIRMED over 3 rounds.
Refs age-cwo.2
Root cause of the escape lane's unparseable-verdict degradations: agy/gemini (the
default membrane) is AUTH-DOWN (interactive OAuth, returns empty) — every membrane
review came back empty -> degraded -> no escape could be captured. Make the
membrane pluggable (--membrane-cmd / --membrane-label, symmetric to the producer)
so codex can be the reviewer: it returns a parseable VERDICT: ACK/REFUTE (verified
live: REFUTE on a mutating impl) and is cross-family with a non-codex producer
(e.g. bushido's Qwen). Default unchanged (agy). The operator owns keeping producer
and membrane in different families (documented; both labels recorded honestly).
Dropped the hardcoded scorecard "cross_family":true — a false assertion under a
pluggable membrane, and nothing reads the scorecard's cross_family (the matches
are the separate yield-ledger field). Replaced with the producer+verifier labels.
Bounded cross-family review (Claude+Codex) CONFIRMED; 8 smokes incl. shellcheck.
Refs age-cwo.2
(1) An unparseable membrane verdict no longer defaults to REFUTE — it marks the
task DEGRADED (excluded). Defaulting REFUTE silently HID escapes (a real
ACK-on-false-done miscounted as a catch); excluding is honest, never fabricate a
verdict the harness could not read. WHY: extraction is now non-fatal under set -e
so a verdict without a WHY line emits the scorecard instead of aborting.
(2) Two subtle trap tasks — the obvious rushed answer passes the visible test and
reads as correct but fails a deterministic oracle:
- hard-deep-merge: shallow `for k,v := range src { dst[k]=v }` drops nested keys.
- hard-utf8-truncate: naive `s[:n]` splits a multi-byte rune.
Both validated (wrong -> oracle fail + visible pass; correct -> pass). Sharper
traps to generate real escapes without the (currently-down) bushido weak producer.
Bounded cross-family review (Claude+Codex) CONFIRMED after one fix round.
Refs age-cwo.1
The membrane eval's producer was hardcoded to frontier `codex exec`, which aces
the tasks and yields zero escapes. Add --producer-cmd / --producer-label (default
unchanged) so a WEAK producer can be swapped in to ship the false-dones that
generate real escapes (an escape = oracle-FAIL + membrane-ACK; already
classified). Escapes stay in the quarantined scorecard, never the production
yield ledger (council guardrail). Substrate for age-cwo.1 / unblocks age-1gl.
Default run is byte-identical (frontier codex producer, "producer":"codex").
Producer label is JSON-escaped; a nonzero producer is degraded-excluded.
Bounded cross-family review (Claude+Codex) CONFIRMED.
Refs age-cwo.1
pawl-verdict-jq-schema-fallback.bats ran `pawl-verdict.sh write` from the repo
root, so its best-effort side-effects leaked into the developer's real ledgers:
- `ao provenance emit-verdict` appended fixture edges (age-anc-jq@cafef00) into
the TRACKED docs/provenance/ledger.jsonl, dirtying the tree + hash-chain;
- `ao yield emit gate-verdict` appended fake gate-verdicts into the (gitignored)
.agents/yield/yield-ledger.jsonl, which the membrane reads.
Fix:
- The test git-inits its TMP and cds into it, so the provenance emit
(resolveLedgerPath walks up to $TMP/.git) resolves into the temp repo.
- pawl-verdict.sh forces `cd REPO_ROOT` for the yield emit (so its dedup scan
reads the same ledger ao writes), which cwd-isolation alone can't redirect.
Add a YIELD_ROOT that honors AGENTOPS_REPO_ROOT (the same override the sibling
check-pawl-pre-push.sh already uses); the test exports it to $TMP. REPO_ROOT
stays script-relative for SCHEMA/assets. Production (env unset) is identical:
YIELD_ROOT == REPO_ROOT.
- Regression guard asserts BOTH real ledgers are byte-identical after a write,
and (when `ao` is present) that the edges landed in the temp repo. Robust to a
thin CI env where `ao` isn't built (emits no-op). Verified ao-present AND
ao-absent; sibling pawl-verdict suites unaffected.
Empirically this was the only pawl/reconcile bats suite that polluted the
tracked ledger; the yield leak was a second, cross-family-caught side-effect.
Closes age-obae
DECISION: the yield ledger (.agents/yield/yield-ledger.jsonl, yieldledger-event.v1)
is already THE canonical membrane event log — escape-detection + ao membrane
derive-checks/recall read from it. The gap was EMISSION: gate-verdict events were
emitted only on the merge path (reconcile-pr.sh) + the deterministic tier; the
PANEL/refuter verdict path (pawl-verdict.sh write — pawl.sh route, hand-rolled
codex exec) emitted a provenance edge but NO yield event. So panel catches that
never reach a merge (incl. this whole session's) were invisible to the log.
Fix: pawl-verdict.sh `write` now emits a gate-verdict on every disposition,
beside the provenance emit — the single chokepoint for panel verdicts:
- head_sha = the REVIEWED head; rebind (restamp to landed head) is NOT a review
and does not re-emit (no double-count);
- families NORMALIZED via the canonical roster before cross_family (codex+gpt
don't read as cross-family);
- best-effort emit-time idempotency on (bead,head,attempt,disposition,reason)
— dedup at emit time, never read-modify-write the O_APPEND ledger;
- optional --domain/--reason carry the what-was-missed signal derive-checks mines;
- runs `ao yield emit` with cwd=REPO_ROOT so the ledger matches the verdict file.
reconcile-pr.sh's now-redundant gate-verdict emit removed (single emitter).
Also fixes a latent jq bug (index(.author_context_id) evaluated inside the
refuters array → silent emit failure under `|| true`) that the removed
reconcile-pr copy shared — i.e. the merge-path emit was likely silently failing
too, explaining the ledger's absence of panel/cross-family events.
L2 test (tests/integration/test-pawl-verdict-yield-emit.sh, 9 checks): a no-merge
REFUTED cross-family verdict lands one gate-verdict (REFUTED, cross_family, reason);
re-run no dup; CONFIRMED emits; codex+gpt collapse to one family; log stays gitignored.
Bead: age-uxva
check-pawl-pre-push.sh waived the cross-family pawl whenever '#trivial'
appeared anywhere in the full commit body (%B). A normal feature commit
whose body merely mentioned the token in an explanatory sentence was
silently waived — a fail-open bypassing the push-to-main verdict
requirement (age-lyev's own fix commit was waived this way).
Waive ONLY when '#trivial' is an explicit marker:
- a TRAILING tag at the end of the subject line (the established
convention, e.g. "chore(...): ... #trivial"), or
- a standalone '#trivial' trailer line in the body.
A mid-subject or body-prose mention no longer waives the gate. (A
cross-family REFUTE caught that an earlier subject anchor still waived
mid-subject prose like "fix(pawl): prevent #trivial from bypassing".)
Closes age-w2ny
post-land-provenance-emit.sh committed ledger growth to the CANONICAL checkout
and deferred the push ("push again to land ledger"); on a hot trunk that push
raced and stranded orphan commits (e.g. 3f6347baf) — the primary driver of the
canonical checkout drifting tens of commits behind origin.
Rewrite: emit + commit + push from a DISPOSABLE worktree off origin/main.
- canonical checkout is never committed to or left dirty (no orphan, no churn);
- bounded retry loop RE-EMITS onto the fresh trunk on a push race (the ledger
is a prev_hash chain, NOT union-merged — rebase-replay would break it;
re-emit re-chains onto the current tail and dedups by edge identity);
- commit marked #trivial so check-pawl-pre-push.sh waives the provenance-only
commit instead of demanding a bead verdict (the 2nd strand cause);
- cwd=$WT for ALL ao calls (landed + verdict halves share one ledger);
- trap EXIT/INT/TERM + startup stale-worktree sweep (no leaks);
- non-blocking (exit 0); on exhausted retries nothing is stranded.
L2 race test (tests/integration/test-post-land-provenance-emit-race.sh) simulates
a concurrent push landing first and asserts: our edge still lands, canonical stays
byte-clean, no orphan, #trivial present, the chain re-chains onto the competitor
tail (proving re-emit not replay), no leaked worktrees.
Bead: age-lyev
A fresh-context cross-family review (Codex) REFUTED the E4 producer-truth
diff and caught three real defects self-review missed — the membrane
working as designed:
1. OVERCOUNT (severe): real Claude Code transcripts write one model
response as several rows (thinking/text/tool_use) that ALL repeat the
same usage block under one message.id. Summing every row inflated
tokens 2-3x (real fixture: 26781956 vs correct 10946330). Fix: capture
message.id (fallback requestId) in the parser; new types.SumUsage dedups
by response id; forge_tier1 + deriveTranscriptTokens both use it.
2. reconcile HOLD/REFUTED path ignored AO_YIELD_TRANSCRIPT -> rejected
spend silently recorded 0 (broke L/R loss accounting). Fix: shared
derive_yield_tokens() helper used by BOTH the accepted and rejected
emit paths.
3. malformed transcript returned success 0 0 (SkipMalformed) instead of a
visible fail-open. Fix: deriveTranscriptTokens errors when content
yields zero messages, so reconcile keeps the honest default.
Plus: dropped misleading omitempty on value-type StreamEvent.Usage;
real-fixture regression test locking the deduped total (10946330/228);
dedup + malformed + message.id capture tests using real persisted shapes
(the synthetic one-row fixtures hid the overcount — go.md fixture-fidelity).
Full build/vet/test green; reconcile bash + shellcheck clean.
Refs: age-membrane-memory-arch-tz2s.3.1, age-membrane-memory-arch-tz2s.3.2
E4.2. The only bead-tied yield emitter (reconcile-pr.sh) read tokens
from AO_YIELD_TOKENS_* env that nothing sets, so every usage event
carried 0. The only session-writer is the historical mining path
(forge_tier1), which has no bead context — wiring it to silver would
pollute per-bead work-yield gauges. So the correct bronze->silver seam
is a derivation: sum the real tokens from the bead's session transcript.
- ao yield tokens --transcript <path> [--json|--pair]: parse a session
transcript and sum the real tokens_in (full input footprint incl.
cache) / tokens_out via the E4.1 parser capture. The bronze->silver
derivation primitive.
- reconcile-pr.sh emit_yield_usage: when AO_YIELD_TRANSCRIPT is set and
tokens weren't supplied explicitly, derive real tokens via 'ao yield
tokens --pair' instead of the 0 default. Fail-open (any error keeps 0).
- regen: COMMANDS.md + cli-surface for the new command.
The producer that sets AO_YIELD_TRANSCRIPT is the orchestrator (ao
orchestrate, age-tlj6); until it lands this is a latent-but-correct seam
that fires whenever a transcript is passed. TDD: core + cobra-cmd tests,
live-binary smoke (default/json/pair/missing) + reconcile bash block
verified end-to-end. Full cmd/ao green; all drift gates green.
Refs: age-membrane-memory-arch-tz2s.3.2
age-0js4 asked codex-sync to auto-refresh existing twins' references on source
change. Scoping (the bead's required first step) + a cross-family duel found the
premise unsafe for bespoke twins: they are HAND-MAINTAINED in full — body AND
references. Categorizing every bespoke reference vs transform(source): 74
in-sync, 21 stale, but DOZENS are genuine Codex-condensed hand-rewrites (e.g.
research/references/data-flow-from-entry-points.md: 85 source lines deleted, 56
added) and 85 source refs are intentionally absent. A blanket auto-refresh would
clobber the hand-authored copies.
Resolution (codex duel: constructive->B+ manifest, adversarial cost/value->C):
for a P2 with a sibling DETECTION bead (age-odv), the proportionate call is to
CODIFY the contract, not build a ~240-file tracked/bespoke classification regime.
codex-sync correctly skips bespoke twins (incl --force); 'nothing to generate' is
correct, not a bug; refreshing a bespoke twin is a deliberate human edit;
accidental drift is the divergence gate's job (age-odv).
Documents the contract in AGENTS-CODEX.md + the codex-sync.sh header so the
confusion that spawned this bead can't recur. Doc/comment only — no logic change.
Closes age-0js4.
Wires push-serial.sh's within-host advisory lock into pre-push.local: concurrent
local pushes now serialize their gate runs instead of racing — two agents gating
+ touching shared generated surfaces (registry.json, runtime ledgers) and the
shared race build simultaneously could corrupt them. mkdir-atomic lock (flock is
absent on macOS), released by the existing cleanup_prepush_stdin EXIT trap.
Fail-closed: refuse the push if the lock can't be acquired within
AGENTOPS_PUSH_LOCK_TIMEOUT (300s). Cross-host serialization stays git's own
atomic non-fast-forward rejection + rebase-retry. Completes the concurrency
triad: preflight READS the hazard (slice 1), worktree isolation gives thread-local
state (slice 2), this serializes the shared-main write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three gaps that let stale/broken pre-push gates run and froze worktree pushes:
1. install-pre-push-gate.sh derived repo_root via `git rev-parse --show-toplevel`,
which fails from a worktree under a polluted shared config — unrunnable exactly
where the concurrency-safe workflow lives. Now derives it from script location.
2. A landed gate fix was inert until manual re-install, and nothing did it. The
generated chainer now SELF-HEALS pre-push.local from TRUNK (origin/main) before
gating — trust the trunk gate, never the pushed branch's. Installer REPLACES a
stale managed block on reinstall (Python; awk -v can't take a multi-line value).
3. core.bare HEAL — the root cause of this session's worktree-hostility: a test
flips core.bare=true in the SHARED config and never resets it, so every git op
(main checkout AND worktrees) fails "must be run in a work tree", silently
freezing worktree commits/pushes. Repo is never bare → installer forces false.
(The leaking test is a separate bead.)
Verified: installer runs from a worktree; chainer self-heals + `sh -n` clean +
beads section preserved; core.bare heal restores git ops.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The push-to-main full race gate ran `go test ./... -race` inside the pre-push
hook with git's discovery env (GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE/...) still
set. When the push ORIGINATES FROM A LINKED WORKTREE those point at the worktree
gitdir, so ~10 git-aware test packages (TestGetRepoRoot, TestRemoveWorktree,
TestRepairSharedCoreWorktreeConfig, TestParseTimeline_IgnoresPollutedGitDiscoveryEnv,
internal/{rpi,eval,goals,lifecycle,mine,paths,vibecheck,adapters/*}) — which build
fixture repos in temp dirs and shell out to git — had their git ops hit the REAL
repo (exit 128). The gate refused EVERY worktree-origin push, directly hostile to
the worktree-isolation concurrency-safety workflow.
Fix: unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX GIT_OBJECT_DIRECTORY
GIT_COMMON_DIR GIT_NAMESPACE in the race-suite subshell. Idempotent (harmless for
main-checkout pushes). Proven: failing pkgs PASS with the scrub under polluted env,
FAIL without it; existing TestParseTimeline_IgnoresPollutedGitDiscoveryEnv is the
standing regression guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gate-reliability fixes from driving the push=CI epic.
1. Self-arm detection (the bead): the pre-push gate is the release authority but
lives in the untracked .git/hooks/, so a fresh clone could push gate-less. ao
session bootstrap now reports gate=active/inactive/unavailable and warns on
inactive (nudging `bash scripts/install-pre-push-gate.sh`). It is DETECT-ONLY
and NEVER executes a working-tree script — auto-running one is an arbitrary-
code-execution vector (any tree identity is spoofable by a hostile repo the
agent cd'd into). Hardened across four cross-family reviews: spoofed-identity
never executes; regular-file-only + size-capped reads can't be hung by a FIFO;
gate_hook declared in the closed schema.
2. Deterministic push gate: the full-suite step (age-push-equals-ci-0ua.1) ran
`-shuffle=on` (random), which can trip a latent isolation bug and leave the
dev's repo corrupted — exactly what age-cmdao-core-bare-pollution-ek8v does at
shuffle=2 (sets the real repo's core.bare=true persistently). Switched to a
fixed `-shuffle=1` (deterministic, currently green, never trips it). Multi-
order coverage is nightly CI's job; restore -shuffle=on once the trunk is
shuffle-clean (tracked in ek8v).
Verified: gate=active live + --robot JSON (schema-valid); FIFO no-hang; spoofed-
identity no-exec; cli/cmd/ao green under -shuffle=1.
Closes age-push-equals-ci-0ua.3
check-pawl-pre-push.sh extract_bead_from_commit took the FIRST age-/ag- token in
the message, so a bead cited in PROSE as context won over the real closing
trailer — the pawl looked up the wrong, absent verdict and fail-closed a valid
push. Hit live twice this session (the first was a trailing-period bug).
Fixed trailer-BOUND, hardened across two cross-family refutes:
- a trailer is a LINE that is essentially just "Keyword <bead>": it must START
with Closes/Close/Fixes/Fixed/Fix/Resolves/Resolve/Refs/Ref/Bead and END at
the bead (only trailing punctuation/space after). So neither a keyword
mid-sentence ("this fixes ag-old here") nor a sentence that begins with a
keyword ("Fixed ag-old during investigation.") is mistaken for a trailer.
- last trailer line wins (trailers are appended); parenthesized and bare-token
forms remain ordered fallbacks; trailing-separator strip + nocasematch
save/restore preserved.
- verified 8 cases incl. both refuter edges, two-trailer-last-wins, sub-id
age-3va.1, trailing period, Refs: colon form, prose fallback, no-bead.
Refs age-push-equals-ci-0ua.3
The pre-push gate runs `ao gate check --fast` = CHANGED-scope tests only, so a
latent red in an UNTOUCHED package is invisible — exactly how cmd/ao shipped red
for 281 commits. Push-to-main is the SOLE routine CI authority (GitHub Actions
is a tag/PR/manual backstop, removed from the push trigger after the quota-SPOF
self-DoS), so it must run the WHOLE suite, not just the diff.
pre-push.local now detects a push to main/master (via the pre-push stdin it
already captures for the pawl gate) and runs `go test ./... -race -shuffle=on`
as a blocking step. Measured ~77s for all 99 packages (-race parallelizes;
cmd/ao is the long pole, so the full suite is only ~+5s over testing cmd/ao
alone). The in-loop `ao gate check --fast --scope head` path is unchanged for
fast iteration. Emergency bypass: AGENTOPS_PREPUSH_SKIP_FULL_RACE=1.
Verified: a red planted in an untouched package (internal/paths) is caught by
the full suite; main/master ref detection is exact; the full suite ran and
passed live on this very push.
Closes age-push-equals-ci-0ua.1
parity_only twins are GENERATED by codex-sync and verified by its byte-exact
drift gate; re-checking content rules on generated artifacts is the whack-a-mole.
lint-codex-native, runtime-sections, api-conformance, audit-parity, and
generated-artifacts now skip parity twins (treatment != bespoke) and check
hand-authored bespoke twins + the override layer only. A generated artifact is
verified by regenerate-and-diff, not by re-checking content rules.
Verified: bespoke Claude-isms still caught; parity twins with accurate cross-runtime
content (cass/cc-hooks) no longer trip the gates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--check now verifies each parity twin EXACTLY matches generator output
(presence + registration + byte-identical SKILL.md + prompt.md + mirrored
references), not just presence. This is the single drift gate that lets the
content validators skip parity twins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>