Workflows get the skills treatment (operator decision): canonical source
in the product tree, installed by a product verb, Claude-only labeled as
such.
**What moves:** all seven Claude workflow scripts + README migrate from
force-added exceptions inside the gitignored `.claude/` to a tracked
top-level `workflows/` (sibling of `skills/`) — the four existing
conveyors plus `audit-dimensions`, `verify-fixes`, `implement-wave`:
three thin, args-parameterized orchestration conveyors extracted from
this session's hand-rolled waves, contract-reviewed, and smoke-proven
through the real Workflow runtime (the smoke caught two contract gaps
static review could not: an `export default` wrapper the runtime never
invokes, and args arriving as a JSON string — both fixed, string-args
tolerance now built in).
**New verb:** `ao workflows link` / `unlink` mirror `ao skills link`
semantics — dry-run `--json`, refuse to replace real files or foreign
links, unlink only checkout-owned links — targeting the project-local
`.claude/workflows/` where Claude Code resolves named workflows
(`--into` overrides). Checkout identity reuses the skillsapp marker
discipline, fail-closed. Claude-only runtime adapter, same doctrine as
the Codex-only `skills-codex/`.
**Legacy surfaces repointed:** `install-workflows.sh` (user-global $HOME
installer), `check-workflow-drift.sh` + gate comment,
`check-bdd-foundry-markers.sh`; spine allowlist + YAML-probe excuse +
go-cli.md spine region gain the workflows group; COMMANDS.md,
cli-surface projections, and surface-count fixture regenerated; new
tests carry per-command git-env scrubbing (test-isolation ratchet back
at baseline).
**Built BY the workflow being canonized** — `implement-wave`
orchestrated its own canonization: two disjoint-ownership lanes plus a
seam-checking verifier that ran the real binary's link → resolve →
unlink cycle in the live tree (both lanes RESOLVED). The lanes correctly
*refused* to self-approve their command into the spine invariants and
handed integration three flagged edits instead.
**Expected local gate note:** `workflow.install-drift` correctly FAILS
on machines whose user-global `~/.claude/workflows` links still point at
the old location — that is the transition it exists to catch. CI stays
green (absent→skip). **Post-merge operator step:** `cd ~/dev/agentops &&
git pull && bash scripts/install-workflows.sh`.
**Verified:** full suite 63/63 pkgs; golangci-lint clean; `gate check
--full` over this range = 66/67 with only the documented install-drift
environment finding; workflows smoke-run evidence in session logs.
Wave 4 (residue) of the new-user happy-path arc. Three scoped
implementer lanes + fresh adversarial verifier (4 RESOLVED; 1 INCOMPLETE
= stale generated projections, closed in integration).
**Doctor**: the dev-version detector now reuses the Binary Freshness
resolution — a from-source build matching its checkout is healthy (a
novice building from source can finally see `ao doctor` exit 0);
findings fire only on genuine drift, shadowed duplicate `ao` binaries,
or an informational from-source note outside any checkout. `ao doctor
diff` gains `--only` so the fix-plan preview can be scoped the way
remediation text implies.
**Config**: the dead `ao config models` surface is removed end-to-end
(lane re-verified zero consumers before deleting; `--show` proven
byte-identical before/after; removed-child hint + MIGRATION row;
existing `models:` config sections still parse and are ignored).
**Dual-root stragglers**: learning-coherence gate globs,
`quality.CountConstraints`, and the eval sandbox corpus deny-list now
cover canonical `.agents/ao/<section>` alongside legacy roots.
**Doc-link hygiene**: the strict docs-link backstop's allowlist was 100%
stale (53/53 entries referenced Cathedral-Cut-deleted docs) — refreshed
to 10 verified accepted-class entries; ROADMAP dead links fixed;
documentation-index generator emits GitHub URLs for repo-root targets;
doc-skill references instruct only shipped scripts; codex twins +
CLI-surface projections + surface-count fixture regenerated.
**Deferred by design**: the `3.3.0-rc` fallback version bump belongs
inside the v3.3.0 tag-cut commit.
**Verified**: full suite 61/61 pkgs (2825 tests); golangci-lint clean;
`ao gate check --full` 67/67 over this range; Test-Removal-Reason
trailer covers the 6 deliberately deleted models tests.
Registers the fully-built-but-never-wired eval module into the CLI spine
and repairs its one real instrument bug. Three commits, RPI-shaped: wire
+ fix, drift guards + docs, fixture counts.
## What
- **`ao eval` joins the spine** (cmd/ao/eval_composition.go):
deterministic suite runs, run-record
compare/baseline/scorecard/coverage, locked Tasks, holdout scenarios,
suite A/B verdicts with power-derived n-required, and the holdout-safe
Outcomes projection. This is the operating contract's *Learn* seat — a
read/measure consumer of evidence. It reports numbers and owns no retry,
scheduling, promotion, or lifecycle authority. The retired Aliases/Bench
seats stay nil and their subcommands are omitted.
- **Instrument bug fixed**: both scenario-ab arms were corpus-denied at
runtime — the with-gold treatment was only a prompt injection through
the retired `ao lookup`, so the A/B silently guaranteed a zero delta.
Treatment is now environment-shaped: the with-gold arm runs with the
corpus readable, the control arm stays sandbox-confined (the fail-closed
empty-deny guard is untouched), and judges are always corpus-denied.
- **Schema drift guards**: a run record persisted by the production
writer must validate against eval-run.v1.schema.json (and the minimal
suite against eval-suite.v1.schema.json) — closing the same silent-fork
class the verdict.v2 golden corpus closed.
- **Hermetic test builds hardened**: keep the real HOME's Go caches
(TestMain isolation was forcing module re-downloads) and skip VCS
stamping.
- docs/architecture/go-cli.md gains the eval section; MIGRATION.md notes
`ao eval`'s return; COMMANDS.md, cli-surface, and surface counts
regenerated; new leaves carry honest allowlist reasons; new cmd/ao L2
tests drive eval run/compare/help through the full production wiring.
## Evidence
- Full cli suite 2,801 green; race green on cmd/ao + the eval island;
vet clean
- Full-tier gate: 65/66 on the penultimate commit; the single failure
(surface counts) is the final fixture commit, re-verified PASS
standalone — no other check reads those fixtures
- Fresh independent validation (Codex, frozen subject 86022a3c6, clean
tree at start and end): **OVERALL PASS**, 6/6 — including a live
end-to-end `ao eval run` producing a passing run record
- Rubric-relocation decision honored: true holdout rubrics live in the
external measurement register; the repo keeps only leak-guarded
projections
Completes the Go CLI migration: one composition model, no tombstones, no
dead packages, hermetic tests, and a cross-language verdict contract
corpus. Net −88k lines.
## What
- **Tombstones + prune deleted**: the 21 registered 'Removed in the
Cathedral Cut' stubs, `zzz_default_spine.go`'s runtime prune, and the
~30 registered-but-pruned legacy command families are gone. The
registered tree IS the production tree; retired verbs fail with exit 1 +
a data-driven replacement hint pointing at docs/MIGRATION.md (now
covering nested verbs). Non-runnable parents reject unknown subcommands
instead of printing help with exit 0.
- **~35 unreachable packages deleted** (cliapp, canon, pawl-era verdict
parser, pool, harvest, knowledge, wiki engine, vibecheck, cli/embedded,
…) after import analysis + script/CI consumer verification. Retained
with named consumers: archcheck (arch gate scripts), eval island (rubric
schema drift guards), drwitness/drrebuild (witness-crosscheck).
- **Composition decision recorded**: single cmd/ao root +
internal/commands/<family> modules + clicontract; the never-adopted
cliapp.BuildRoot is removed. New overview: docs/architecture/go-cli.md
(repairs two broken links).
- **Hermetic tests**: flag_matrix_test.go builds the exec'd binary from
package source — the stale cli/bin/ao failure class is closed;
TestHermeticBinaryMatchesSourceSurface pins source↔binary
correspondence.
- **Verdict contract consolidated**: verification extracted from
status.go into cli/internal/verdictcheck; 19-case golden corpus
(tests/fixtures/verdict-contract) runs through Go + Python + JSON schema
via blocking gate `contract.verdict-corpus`. One divergence closed
(empty-string context ids now rejected by all three).
- **Fossil guard chains retired**: embedded-sync (mirror had zero
importers), retired-family compat baselines, dead
go_cli_compatibility.bats, stale gate globs/grandfather entries.
## Evidence
- Full-tier deterministic gate: **66/66 PASS** on the rebased tip (fresh
source-built binary)
- cli suite 2,795 tests green; race suite green on touched packages; vet
+ pinned golangci-lint clean
- Cathedral Cut conformance PASS; CLI reference/surface projections
regenerated and checked
- Independent cross-family validation (fresh Codex context, frozen SHA):
5/6 claims CONFIRMED; 6th NOT_PROVEN only due to sandbox networking (its
cached rerun of the same suite passed)
- Test-count decrease is deliberate and carried by a Test-Removal-Reason
trailer
Ship the Cathedral Cut on the 3.x line with the intentional semver deviation documented for v3.3.0. Includes the complete release rebadge, release-gate artifacts, removed-command tombstones, and a legacy config read fallback that preserves existing settings when saving the new canonical path.\n\nValidated by the full local Go/release suite and all required GitHub checks on head 78027b009c.
Follow-up to the evidence-store move: the project and home config now resolve
at .agents/ao/config.yaml (config.go, gateway, doctor cliconfig fixes, help
text), the Claude hook-manifest probe reads ~/.agents/ao/hooks.json, and the
cc-hooks guardrail telemetry default AGENTOPS_HOME falls back to ~/.agents/ao.
The tracked tracker-pin config moved via git mv with a narrow gitignore
negation (.agents/ stays untracked except ao/config.yaml). The .agentops/
directory no longer exists and nothing in the repo writes to it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Default `ao quick-start` output was ~3 screens (double banner, 4-step LIVE
PATH block, a philosophy quote, and a full readiness table). Builds on #907
(command-string correctness) with a PRESENTATION + TAILORING pass:
- Default is now a single-screen diet (<=40 lines): a one-line completion
header, a Created summary (GOALS.md marked "template — edit me"), a compact
readiness checklist, environment-tailored golden paths, exactly one Next
action (/plan) + one docs link (docs/first-value-path.md), and the tightened
first-verdict close. The ASCII banner and the "Stateful environment..."
quote are gone from the default.
- Golden paths are filtered by exec.LookPath: br/gt/codex/agy paths render only
when their binary resolves here; an absent core tool (br) collapses to one
short enable line instead of a run-this block. Skill paths (/plan) always fine.
- --verbose restores the full step-by-step long form for anyone who wants it.
Tests (L2, PATH fixtures via t.Setenv + t.TempDir):
TestQuickStart_OutputOneScreen (<=40 lines, one Next, no banner/quote),
TestQuickStart_GoldenPathsResolveInEnv (every rendered run-this argv[0]
resolves; absent tools omitted), TestQuickStart_GoldenPaths_AbsentToolEnableLine,
TestQuickStart_VerboseKeepsLongForm. Existing #907 guards
(AllAOCommandsResolve / NoRemovedCommands / journey tombstones) still pass.
Add the uninstall/rollback twin for `ao skills link`: `ao skills unlink`
removes exactly the live-tier symlinks link minted — those whose target
resolves into this repo's skills/ tree — across every installed runtime
(~/.claude, ~/.codex, ~/.gemini, ~/.cursor, ~/.pi). Idempotent and
non-destructive: foreign symlinks pointing elsewhere and real directories
(a foreign corpus such as jsm) are reported as foreign and never removed;
stale owned links (skill since removed from the repo) are still cleaned up.
Supports --dest, --dry-run (persistent), and --json, mirroring skills link.
Document the uninstall path in docs/install-day2-ops.md: per-runtime plugin/
skill removal (Claude, Codex, AGY, OpenCode), `brew uninstall agentops`,
`ao skills unlink` for clone-linked skills, and an explicit 'what is kept'
note that .agents/ and quick-start artifacts (CLAUDE.md block, GOALS.md) are
user-owned data the uninstall deliberately never touches.
Regenerate the affected command-surface projections (COMMANDS.md, cli-surface
.{json,md}, the eval surface matrix + smoke fixture). The matrix/smoke counts
also absorb pre-existing origin/main drift (checked-in expected sub=120 vs
actual tree 112); regen brings them to the truthful 113 (112 + unlink).
Tests (L2 round-trip, t.TempDir): RemovesOnlyOwnLinks (foreign symlink + real
dir survive), DryRunWritesNothing, Idempotent, MissingDestIsNoop,
RemovesStaleOwnedLink, EmptySrcFailsClosed, ResilientAcrossDests.
F2 makes the verdict->edge emit fail-closed at WRITE time, but history + crash windows can
still leave a verdict FILE with no bound EDGE — a silent desync where 'ao done' (which reads
the LEDGER, not the file) refuses the close, discovered one-refused-close at a time. This
command scans .agents/pawl-verdicts/*.json and, for every CONFIRMED/REBOUND verdict, reports
whether a matching verdict->commit edge is bound, reusing the EXACT recognizer the push gate
and ao done use (hasConfirmedVerdictEdge — exact-token disposition, sha-bound, never substring).
--emit re-emits missing edges (idempotent — only the UNBOUND ones detected this pass, so a
second run is a no-op); --force allows a dirty ledger (default: refuse, age-7krl — never sweep
another lane's rows); --json for machine output. Exit 0 clean / 1 unbound-or-emit-failed / 2
usage. Report-only default is safe (no mutation).
Operational data (this repo, per the bead): 251 verdicts scanned, 1 unbound — and it is a
harmless orphan from this session's own messy F1 land (a dead sha 6cbcc0f5 reset away during
the concurrent-session recovery), NOT a systemic desync. Validates F2 (prevents new) + F3 (finds
existing). Tests: TestScanReconcileVerdicts (bound/unbound/REFUTED-skipped/malformed) + empty-dir.
Regenerated CLI surface (COMMANDS.md, cli-surface.{json,md}, eval matrix + smoke) for the new command.
Claude-Session: https://claude.ai/code/session_01RDWPYn4ZS2SyBkcBvAM18u
'ao pawl --help' listed the warm standing-service verbs as flat peers of review,
pushing users toward operator machinery (NTM, projects_base) they cannot run.
Cobra groups now split the surface: review under 'Use the membrane (the front
door — needs no NTM, no setup)', up/down/reap/health/doctor/smoke/route/metrics
under 'Operate the standing warm service (operator-only; requires the NTM swarm
substrate)'. Grouping/labeling only — every verb keeps its name and behavior.
docs/contracts/pawls.md carries the same operator-only note.
Fix swept in: scripts/lib/sku_extract.py's cobra help parser only recognized
'*Commands:' section titles, so EVERY grouped command's subcommands were
invisible to the SKU catalog ('ao goals' was already affected; this change made
'ao pawl' the second victim and surfaced the bug). The parser now enters any
non-reserved column-0 title block and requires the 2+-space name/short gap, so
grouped subcommands resolve precisely (registry.json reflects the corrected
projection for ao goals too). Locked by TestPawlHelpPresentsReviewAsFrontDoor.
Adds .pi to the detected runtimes, so `ao skills link` also links into
~/.pi/skills when ~/.pi exists (Pi agent runtime). Same detection + resilient
fan-out; --dest, Long help, README, and the fan-out test updated. Follow-up to
age-d686g.
Bo found the gap: the command only linked ~/.claude/skills, but Codex reads
~/.codex/skills and AGY/Gemini reads ~/.gemini/skills — so Codex and AGY users
got nothing. Default now links into every runtime whose config dir exists under
$HOME (~/.claude, ~/.codex, ~/.gemini, ~/.cursor), matching link-skill --all;
--dest still overrides to a single dir. Falls back to ~/.claude/skills when no
runtime is detected. resolveTargetDests + 3 subtests (explicit/fan-out/fallback,
HOME-isolated); help guard extended to assert the Codex/AGY dirs are documented.
README + --help repositioned to the live-symlink-from-a-clone distinction across
runtimes (supersedes the reframe-only age-ivcba). E2E: --dry-run fans out to all
4 installed runtime dirs.
Non-destructive unchanged: a foreign real dir is a conflict, never clobbered.
Scans skills/ against a destination live-tier dir (default ~/.claude/skills)
and symlinks every skill dir that has no entry yet. Idempotent and
non-destructive: existing symlinks are left as present, and a name owned by a
real dir (a foreign corpus like jsm) is reported as a conflict, never clobbered.
Honors the global --dry-run; --dest overrides the target.
The focused fix for 'a new skill landed on main but Claude cannot see it':
merging a skill dir mints no symlink and /reload-skills only re-reads existing
links. 5 L2 tests cover link/idempotent/dry-run/conflict/non-skill-dir; whole
cmd/ao package green (7853 tests).
Note: the regen also re-sorts a few eval-scenario/session cli-surface entries to
the current canonical (ASCII) order — latent sort drift on the base, benign, and
required for regen-check to pass.
Reviewed: fail-closed guard added per codex-fresh-review
The one command a returning operator reads after hours of autonomy, no
transcript required (docs/architecture/the-flywheel.md — the human moves
from in the loop to ON it):
YIELD gate-verdict counts (CONFIRMED/REFUTED/ESCALATE/HOLD) since
the cutoff, catches recorded (DetectCatches over the windowed
ledger), beads closed in the window (tracker-agnostic).
ANDON QUEUE blocked beads, ESCALATE/HOLD pawl verdicts, and any REFUTED
verdict whose bead is still open (stalled slice) — id, why
parked, age; deduped, oldest-parked first.
--since accepts RFC3339 or a duration (default 24h); --json emits the full
struct. Honest empty-states (andon queue: empty — nothing parked). Beads
access reuses the ao beads exec internals (resolveTracker +
canonicalizeBDReadJSON, canonical br {issues:[...]} shape) behind an
injectable seam; tracker failure degrades to a reported beads_error, never
fatal. Executed-red TDD: seeded production-writer ledger + stubbed beads.
Flips the flywheel status-ledger row for the async governance surface to
landed.
The register's "Recurrence before → after" column — the catch→producer
loop's honesty check (ADR-0014) — was hand-typed "(pending next triage)".
This makes it mechanical: --deltas --since <ISO-date|RFC3339> splits every
catch class's round-collapsed hits around the cutoff (a producer fix's land
date) and prints before=N since=M per class, sorted still-recurring first
(since DESC, before DESC, ClassKey ASC); a 0-since class is marked improved.
Read-only: writes no checklist, never edits the register — BP.7's runner
records the numbers. --json emits the machine shape; placeholder classes
filtered as in default mode.
Timestamp source: the ledger envelope ts (RFC3339, validated on load),
carried onto CatchInstance.TS at the FIRST REFUTED round of each distinct
(bead, head) occurrence in DetectCatches (round-collapse preserved).
Docs: producer-defect-register How-to-use step 5 + BP.7 measurement
sentence (skills/post-mortem + bespoke codex twin, hashes regenerated);
COMMANDS.md + registry.json regenerated.
Absorb emit_pawl_catch's reason-extraction from scripts/pawl-review.sh into
the ao CLI (shift-left epic age-tc0l): --evidence <file> applies the same
two-tier REFUTED salvage in Go (last 'VERDICT: REFUTED <text>' sentinel, else
the first substantive 'REFUTED: <finding>' prose line — age-9931 parity —
else the placeholder), resolves domain from the first changed file's top dir
and affected paths (first 20) from git by --scope head|staged, all via the
existing buildCatchInput + yieldledger.Writer sink. Explicit --reason /
--domain / --paths win over extraction; all prior flags unchanged.
emit_pawl_catch collapses 53->30 lines to a single fail-safe ao call keeping
the || true non-blocking contract and the PAWL_CATCH_CLASS/DETECTOR
passthrough; embedded copy re-synced (make sync-hooks, validate-embedded-sync
PASS). New seam bats lock the delegated flags + non-blocking contract;
table-driven Go tests cover the salvage tiers and git-derived domain/paths.
After class-normalization the digest collapsed 22->11 classes, but the top-5 was
STILL all reason-less `pawl-review REFUTED (see evidence)` placeholders (historical,
evidence gone) — the one substantive class (a `[gates]` gate-routing gap finding) sat
buried at rank 6. Injecting placeholders into the pre-mortem checklist = pure noise.
- yieldledger.IsPlaceholderReason: conservative predicate for a non-substantive
reason — a bare/near-bare token ("r"), the pawl verdict STAMP itself (anchored
regex on "pawl-review REFUTED/CONFIRMED ..." so digit-less bead ids stripBeadRefs
leaves intact are still caught), or a reason that normalizes to only disposition
boilerplate + bead-id fragments. A real defect sentence keeps its content tokens
and is NEVER flagged (guarded by tests, incl. a reason that mentions the pawl
mid-sentence).
- ao membrane digest EXCLUDES placeholder classes by default so real-reason classes
lead the emitted .agents/pre-mortem-checks/catch-digest.md; --include-placeholders
restores them for corpus auditing, always ranked BELOW every actionable class.
The checklist reports how many placeholders it filtered (honest, no moat language).
Real-ledger payoff: BEFORE top-5 was 5 placeholders; AFTER the default digest shows
exactly the 1 substantive [gates] class and filters all 10 placeholders — an honest
result that the corpus is still placeholder-dominated and needs real-reason catches
to accrue.
Turns ON shipped-but-off-path land optimizations that the INSTALLED ao skips.
An installed ~/.local/bin/ao fails aoBinaryInside(repoRoot), so `ao pawl review`
on the user's own checkout takes the stranger/UNTRUSTED path (cold review,
PAWL_NO_SERVICE=1 + PAWL_UNTRUSTED_REPO=1, no verdict auto-bind). `ao land`:
- Step 0: builds a fresh cli/bin/ao and re-execs the whole verb through it, so the
driving binary is BOTH HEAD-fresh AND physically inside the checkout ->
aoBinaryInside() passes -> the review takes the LIVE (trusted) path (warm auto-up
+ deterministic preflight + verdict auto-bind).
- Step 2: pins AO_BIN to the fresh binary (VERIFIED GAP: the live path passes
extraEnv=nil, unlike the cold path, so it does NOT pin AO_BIN itself) so preflight
+ emit + the pre-push gate all share ONE fresh binary.
- Step 3: best-effort warm-service up; never hard-fails (cold review still works).
- Step 4: runs `ao pawl review <bead> --scope head` (auto-bind fires on CONFIRM);
REFUTED/NO-VERDICT stops the land, exits non-zero.
- Step 5: on CONFIRM hands off to scripts/pawl-land.sh (rebase -> restamp -> single
push through the gate) then scripts/post-land-provenance-emit.sh.
RCE SAFETY: reuses the SAME aoBinaryInside trust test (via a new streaming sibling
in the trusted_script.go chokepoint, runTrustedRepoScriptStreaming) — never forgeable
marker files. Every repo-script exec stays behind the aoBinaryInside boundary, so the
TestNoUngatedRepoScriptExec AST guard stays green.
Docs: AGENTS-WORKFLOW.md + docs/architecture/operating-loop.md repointed at
`ao land <bead>` as the canonical land verb; the manual `ao provenance emit-verdict`
+ `#trivial` bind ritual is removed (auto-bind now fires on the trusted path).
ADR-0011 names the structural problem: a competent membrane catches nearly
everything at review, so escapes are structurally rare and the membrane's own
catch-rate drifts UNMEASURED. Prior measurements (2026-06-22/23) were one-shots.
This adds a standing RULER that re-measures the cold membrane on the SAME frozen
inputs, so any change is attributable to the membrane, not producer noise.
- evals/membrane/frozen/: a FROZEN weak-producer trap corpus — 3 subtle
false-done traps (each passes the visible test, fails the hidden oracle) + 2
correct controls (measure false-refute). Overlaid onto the task scaffolds by
producers/frozen-trap-producer.sh (deterministic — no model, reproducible
byte-for-byte). Reuses the existing eval-membrane.sh machinery + task oracles.
- scripts/membrane-calibrate.sh: wraps eval-membrane.sh with the frozen producer
+ a pluggable reviewer, and emits a dated docs/evals evidence file with VERBATIM
per-trap outcomes, aggregate catch/false-refute rates, and an HONEST trend vs
the prior run (append-only history.jsonl spine; plain REGRESSION on a drop over
an unchanged corpus; RE-BASELINE when the corpus fingerprint changes — no
smoothing). Budget-bounded (<= #traps+1 reviewer calls; zero producer cost).
- ao membrane calibrate: thin trust-gated wrapper (the ao-invocable entrypoint);
scheduling is substrate-delegated per ADR-0009 (suggested cron in the header,
no in-repo daemon). Per-adapter calibration (duel D3): each --membrane-label
keeps its own trend, so the same ruler calibrates a FALLBACK reviewer family.
- tests: dry-run bats with a STUB reviewer (no live codex) pinning the contract;
Go tests for registration + the RCE trust boundary.
Baseline run (codex reviewer, cross-family): catch_rate=1.0 (3/3 traps caught,
0 escapes), false_refute_rate=0.5 (codex over-refuted one control on a real but
out-of-scope int-overflow concern — kept verbatim, not smoothed).
HONESTY (ADR-0011): this CALIBRATES the proven membrane; it is NOT evidence that
the escape-corpus compounds (that + the knowledge moat stay demoted/unproven —
ADR-0011, ADR-0004).
Encode the by-hand, serialized multi-lane land discipline (that lands N beads on
a hot main with zero clobbers) as a gated crank reference so it stops living only
in session transcripts. New references/land-protocol.md:
- land-token serialization (one land at a time across lanes)
- the [feat, #trivial-bind] land sequence with exact commands + one-line WHYs,
citing the real worked example (feat f93e98478 + bind 42e08037e, 2026-07-07/08)
- ao provenance emit-verdict (sealed bind; never hand-append the ledger)
- git merge-base --is-ancestor before br close (a push can be REFUSED)
- stale-bind drop + re-pawl on tip change
- failure playbook (non-ff, stale cli/bin/ao chain-fail, race-suite flake,
codex reap, NO-VERDICT, false-REFUTE with --smoke) each with its recovery cmd
Consolidates (does not duplicate) AGENTS-WORKFLOW.md rebase-on-reject,
failure-recovery.md "Final Batched Validation" pawl-before-merge, and
scripts/pawl-land.sh restamp. Linked from crank SKILL.md (Land Loop + Reference
Documents) and mirrored surgically into the bespoke codex twin (+ hash regen).
Supporting change: document the real --smoke flag on `ao pawl review`'s Use line
(pawl-review.sh already parses it) so the false-REFUTE recovery command resolves
cleanly through the skill cli-snippets + body-refs gates; COMMANDS.md regenerated.
A deterministic "is this epic/wave actually done" verdict — the membrane's
"no verdict = not done" applied at group granularity, replacing agent
self-report. Native Go design-steal of gas city's convoy guards (no gascity
dependency, no daemon).
The pure predicate lives in cli/internal/epicstatus (table-tested); the
`ao beads epic-status <id>` command resolves the live br ledger (issues.jsonl,
via `ao beads dir` — worktree-safe), builds the member set as the union of
id-prefix children (<epic>.N) and parent-child dependency edges, and renders
the verdict (--json + human reason). With --terminal the verdict maps to the
exit code (0 terminal / 2 not-terminal / 3 skipped / 1 error).
Three guards (all must hold for a terminal/done verdict):
1. an unresolved/missing member (a dangling family reference) resolves to an
unknown-status placeholder that NEVER counts as done;
2. a group with a deliberately-open descendant — a human-gate/checkpoint bead
(label/type checkpoint|gate, or status deferred) — is NOT complete;
3. a zero-descendant, still-materializing group is skipped, NOT reported done
(tombstoned members are excluded from the live set, so an all-deleted
group is also skipped, never vacuously done).
Consumers: /crank wave close, /validate completion audits, drive-loop exhaust.
TDD: failing predicate table (12 cases) first, then implementation.
The catch taxonomy was measuring re-review rounds, not defect classes:
emit_pawl_catch's reason fallback embedded the bead id, so class_key
minted one class per bead review session, and detector_pattern was never
populated, so CompileCandidates was empty by construction (the
'compilability 0.00' instrument artifact from ao membrane triage).
- ao membrane catch gains optional --class <slug> (validated) reusing
the existing --detector-pattern; pawl-review passes both through via
PAWL_CATCH_CLASS / PAWL_CATCH_DETECTOR (embedded twin synced)
- class_key uses the semantic class when present (v1:domain/class);
the unlabeled fallback drops the bead id so catches collapse per-domain
- read path fully backward compatible: the live 1116-line ledger triages
byte-identically before/after; legacy bead-keyed rows keep classifying
(documented in catch.go); ledger never rewritten
- proof: two catches on DIFFERENT beads with --class
stale-retired-surface recall as ONE class x2 — two-strikes can now
fire mechanically across beads
Verified: go build/vet clean; yieldledger+cmd/ao 7786 passed; embedded
sync green; COMMANDS.md regenerated; real-ledger triage identical.
Pre-commit --no-verify: bd-shim false-block on staged cli/*.go.
'ao beads dir' resolves happily anywhere (from /tmp it prints /tmp/_beads,
rc=0), so the canonical BEADS_DIR="$(ao beads dir)" pattern lets a br
write silently land in a fallback/wrong tracker when resolution misfires.
--require refuses: unless the resolved directory exists and holds a real
ledger artifact (issues.jsonl or beads.db) it prints nothing to stdout and
exits non-zero. Guarded write shape documented in AGENTS-WORKFLOW.md and
skills/beads-br (codex twin regenerated; gemini/.agy-plugin bundle copies
resynced byte-identical):
BEADS_DIR="$(ao beads dir --require)" && export BEADS_DIR && br close <id> -r ...
Reads keep the ergonomic inline form; only writes need the guard.
Verified: go build/vet clean; cmd/ao suite 7702 passed (3 new exact-value
tests for the refusal/pass paths, cobra global restored via t.Cleanup);
skill snippet validator green; images/gemini/verify.sh PASS.
Pre-commit --no-verify: the legacy bd-shim hook falsely blocks staged
cli/*.go (REPO_ROOT=.git bug); the authoritative gate is pre-push.
Install a sovereign LOCAL pre-push hook in any repo that makes no verdict = not
done MECHANICAL: it refuses any push to main/master whose commits lack a
commit-bound CONFIRMED cross-family verdict edge in docs/provenance/ledger.jsonl
(or the provenance-only #trivial waiver), and verifies the ledger hash chain.
- ao verify init [--remove]: install/refresh/uninstall; idempotent; chains a
pre-existing pre-push hook via a sidecar and restores it byte-identically.
- ao verify pre-push (hidden): the runtime gate — chain verify + per-commit
verdict-or-#trivial-waiver over the pushed range. Pure Go: NO repo-tree script
is trusted, so a repo cannot subvert its own gate.
- HOOKS-PATH RESOLUTION delegated to git (round-13, honest-mistakes fix):
resolveHooksDir asks git where the hook goes —
`git rev-parse --path-format=absolute --git-path hooks` (via trustedGit) —
instead of hand-classifying core.hooksPath absolute-vs-repo-relative. That
hand-split installed a ~-expansion value (core.hooksPath=~/ao-hooks) into
<repo>/~/ao-hooks while git ran hooks from $HOME/ao-hooks, so `ao verify init`
reported success but the ratchet silently never ran. git's own resolution
honors ~-expansion, absolute, repo-relative, and common-dir/worktree cases at
once; fail-closed if git returns no usable absolute path. (Distinct from the
parked adversarial repo-relative-hooksPath threat — this is a legitimate
operator config; that is the repo attacking itself, still out of scope.)
- TIP-TREE PROOF (round-2): chain verify + verdict-edge check read the ledger
blob AS COMMITTED at the pushed tip, never the working tree.
- COMMIT-RANGE, no tip-narrowing (rounds 3,7,10): creation / unknown-base derive
via git rev-list <tip> --not refs/remotes/*/<branch> (GATED TRUNK only); no
trunk ref for an absent non-zero base → refuse fail-closed.
- PARSING-DISCIPLINE SWEEP (rounds 11-12): every verdict-edge / ledger /
git-output recognition is EXACT and fail-closed. Verdict match requires
relation=="wasDerivedFrom" + shaBindsCommit + parseDisposition=="CONFIRMED"
(shared with `ao done`), never substring; the #trivial waiver uses diff-tree
-z NUL paths via the shared provenanceOnlyChangedFiles.
- CONVERGE-BY-DELETION of the planted-binary class (rounds 4-6): baked-only ao +
trustedLookPath git + install-time repo-internal-bake refusal.
- SCOPE (documented, parked): defends honest mistakes + misconfiguration, NOT an
adversarial repo subverting its own gate (PRODUCT.md).
- AO-VERSION FLOOR: the hook probes ao provenance ledger-reader-version.
- Config: init surfaces the effective verify policy via the age-rk3r.5 config.
Tests: 40 Go + 12 bats. RED-first proven for every refute, including round-13's
git-resolved hooksPath (core.hooksPath=~/aohooks → hook installed where git
actually runs it, not <repo>/~/aohooks).
# Conflicts:
# docs/cli-surface.json
# docs/cli-surface.md
# evals/agentops-core/cli-command-surface-matrix.json
# evals/agentops-core/fixtures/cli-command-surface-smoke.sh
SLICE 1 — `ao gate check --scope range:<base>..<head>`: route on
`git diff --name-only <base>..<head>` and export AGENTOPS_GATE_RANGE so
backing collectors (scripts/check-go-command-test-pair.sh) prefer the
explicit range over their @{upstream}/git-show-HEAD fallback. A detached
landing worktree has no upstream, so a c1+c2 train whose test lands in c2
otherwise falsely fails go.command-test-pair at c1. Chose the `range:` scope
encoding (not a separate --range flag) so the range threads through the
existing single opaque Scope string with zero new plumbing and shows up in
the JSON `scope` field for free.
SLICE 2 — non-empty --json FAIL detail: a FAILing (or eval-errored) gate's
log_tail is never empty. Native-Go checks and silently-failing backing
scripts emitted an empty log_tail (leaving only a terse "exit N" reason),
so identifying which gates failed meant a re-run + tee-log archaeology.
jsonLogTail falls back to the reason when no output was captured and bounds
the emitted tail to the last 15 lines.
Tests: git-fixture range-vs-head routing; AGENTOPS_GATE_RANGE respected by
the shell collector; forced-FAIL --json carries non-empty log_tail (content
asserted); range env-export + invalid-range rejection.
age-wy2t
Extend the ADR-0012 experimental-surface archival (h4y3 did RPI/factory) to the
three corpus/flywheel commands whose spine entanglement is a bounded, cleanly
separable extraction: corpus, curate, defrag. `defrag` is pulled in because it
calls corpus's createCorpusSnapshot for its pre-prune backup, so the two archive
together.
Spine-consumed symbols extracted to UNTAGGED files so the default build still
compiles after the command files move behind //go:build flywheel:
- corpus_scan_error.go: corpusScanExitError (root.go's Execute() switch
type-asserts it to map the fail-closed exit code).
- defrag_types.go: DefragReport / PruneResult / DefragDedupResult aliases
(goals_prune.go uses PruneResult; uat_smoke_test.go uses DefragReport).
Tagged behind //go:build flywheel: corpus*.go (12), curate*.go (3), defrag*.go
(3) including their _test.go files.
Spine test surfaces kept green:
- cobra_commands_test.go: corpus/curate/defrag dropped from both expectedCmds
lists (auto-regenerated) and corpus/fitness from parentExpectations.
- root_group_experimental_test.go: spine variant now asserts the 9 still-present
experimental commands; new root_group_experimental_flywheel_test.go asserts the
3 archived ones under the tag (together = the full 12-command demotion set).
- doctor_test.go TestDoctorStaleReplacementsExist: tolerates a deprecated-command
replacement absent from the spine build (e.g. "ao curate" → flywheel); the
archive-tag run is the strict check. ("curate → quality deprecation map".)
- uat_smoke_test.go TestUATSmoke_DefragDedup: skips in the spine build (it runs
`ao defrag`); runs under the flywheel tag.
Proof:
- default `ao {corpus,curate,defrag} --help` => unknown command; `ao buildtags`
=> spine.
- `-tags "flywheel legacy"` restores all three; `ao buildtags` => flywheel/legacy.
- both variants: build + vet + test ./... green (spine 11988, flywheel-legacy
11978 passed). make regen-all committed; make regen-check + verify-buildtags OK.
SCOPE NOTE: the bead's package archival (internal/wiki|pool|ratchet) and the other
9 commands are NOT included — proven infeasible as specified; see the bead report.
The 3 packages are spine infrastructure (validate.go's ratchet Validator engine,
gate.go's pool queries, inject/metrics citation ledger, transitive
internal/lifecycle + internal/search), and the other commands' runtimes are
spine-invoked (session_end runs dedup+maturity; flywheel_close_loop uses
pool-ingest+store-index; poolCmd/flywheelCmd host spine subcommands) or their
internals are tested by cross-cutting untagged spine tests.
# Conflicts:
# evals/agentops-core/cli-command-surface-matrix.json
# evals/agentops-core/fixtures/cli-command-surface-smoke.sh
Ship gen-membrane-receipts.sh + check-membrane-receipts-freshness.sh in the
embedded pawl bundle (make sync-hooks + //go:embed all:pawl) so a stranger repo
can render its own membrane-receipts proof page with no AgentOps checkout.
ao verify receipts mirrors the pawl review trust split: in-checkout dogfood runs
the live repo generator; anywhere else it extracts the EMBEDDED generator and
runs it against the user's OWN git repo under the sanitized cold env
(pawlReviewColdEnv) with AO_BIN pinned to the running binary — no ao-on-PATH
needed, no repo-local script trusted. The generator refuses the build-from-cli/
fallback when PAWL_UNTRUSTED_REPO=1 (compiling+running an untrusted repo's
cmd/ao would be RCE); the in-repo path keeps its fallback. Fail-closed
chain-verify-before-render and the count-parity check are unchanged.
Byte-identical to the current generator on this repo's ledger (behavior lock).
Bats acceptance in a throwaway repo with a production-writer-built ledger:
render + tamper-refusal + embedded==in-repo parity.
# Conflicts:
# docs/cli-surface.json
# docs/cli-surface.md