The executable core of the plan-pawl duel gate (docs/contracts/pawls.md): a pure,
no-model decider that turns one round of model-family judge verdicts over a
discovery PLAN into PASS / REDO / BLOCKED, applying the quorum (no-FAIL, >=2 distinct
roster families) rule and the pawls.md circuit-breaker governance verbatim.
- internal/planpawl: Decide() — quorum + breaker precedence (judgment-flag /
oscillation / max-attempts > FAIL > mechanical-WARN auto-apply > quorum > PASS).
- ao plan-pawl decide: --verdict family:disp[:warnclass] / --dir; exit code IS the
decision (0 PASS, 3 REDO, 4 BLOCKED, 2 usage). Pane spawn + re-judge loop stay in
the skill (dual-pane-atm); this is the windshield.
- Gherkin (plan-pawl.feature) + 13 unit + 7 L2 command tests; live-smoked.
Closes age-plan-pawl-9yib.2
Slices 3+4 of the membrane's event-sourced memory (rebased onto the rpi-removal
base; CLI surface regenerated fresh — code identical to the CONFIRMED review).
- Slice 3: deriveFindingFromEscape surfaces the escape's Domain + Missed in the
finding frontmatter (escape_domain / escape_missed, independent optionals) and
body, so a derived check is a domain-tagged "look out for this here" artifact.
- Slice 4: `ao membrane recall --domain <d> [--json]` lists past escapes in a
domain (EscapesByDomain) — the consumption side of the self-improving membrane.
Uses repoRootOrCwd (no subdir fail-open); --json emits [] not null.
- regen: COMMANDS.md + cli-command-surface for the new command.
Cross-family pawl CONFIRMED (codex, v2 — 3 fail-opens caught + fixed: subdir
fail-open, json null, domain/missed independence). cmd/ao green under -shuffle=1.
Closes age-membrane-memory-j9c6
Adds `ao worktree create` on the extracted cli/internal/worktree package: creates
a detached per-session worktree off the current commit and prints its path, so a
fanned-out agent gets thread-local working state (no shared mutable tree where a
concurrent reset wipes uncommitted work). Unhides the `ao worktree` group (now a
live-path verb, not just gc). L2 test creates a real git fixture + asserts a
linked worktree (.git file) — the property `ao orchestrate preflight` checks.
Regenerated cli-surface/COMMANDS/registry for the new command.
The live-path counterpart to slice 2's extraction: the worktree lib is now both
decoupled from the rpi engine AND exposed as a first-class command.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An escape — a gate-verdict that CONFIRMED a bead which a later, higher-attempt
verdict then REFUTED — is the label that makes the membrane harder to fool. This
closes the escape→check half of the self-improving loop (epic age-cwo):
- yieldledger.DetectEscapes: pure read over the ledger; pairs the earliest
CONFIRMED with the earliest later REFUTED per bead (v1 one-per-bead; the
escape_rate gauge age-6ty owns finer accounting).
- ao membrane derive-checks --run <id>: detects escapes, derives a finding (the
fresh-context re-verification check that would have caught it), and compiles it
into a pre-mortem membrane check via the existing FindingCompilerPort. Writes
.agents/findings/<id>.md + .agents/pre-mortem-checks/<id>.md. Deterministic id
(escape bead + confirmed sha) → idempotent re-runs; --dry-run, --force.
The bead's "reuse hooks/finding-compiler.sh" premise was stale (that script
doesn't exist); reuses the Go finding-compiler port instead.
Tests: L1 escape detection (run-scoped, control cases) + L2 command (writes,
idempotency, dry-run, required-run) — fixtures built via the production writer.
Live-smoke verified end-to-end against the built binary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add --control-only for applied-OOD ceiling checks, CI validate headroom hook, and workflow coverage wiring so ceiling violations fail before live campaign spend.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add ao eval scenario-moat aggregation with fail-closed rejection of
moat_eligible=false plumbing scorecards before any moat verdict is rendered.
Co-authored-by: Cursor <cursoragent@cursor.com>
The orchestration-shape record (age-gwa) and ValidateShape (age-nep) landed but
had no live consumer: writeExecutionPacketSeed runs only in the retired rpi_*
engine, and the live skill-driven /discovery (dag.md) hand-compiles the packet
without ever stamping orchestration_decision. ValidateShape had zero callers.
(Reachability gap, post-mortem age-gud / .agents/learnings/2026-06-16-reachability-not-unit-green.md.)
Wire it with a focused command the live path invokes:
- `ao rpi stamp-shape` reads .agents/rpi/execution-packet.json, gathers
observable ground truth (am robot agents --active count + per-lane reservation
write-sets grouped from `am robot reservations`), the unattended/durability
signal, and the packet's own proposed shape, runs orchestration.ValidateShape,
and stamps a well-formed orchestration_decision (chosen_shape, predicates_fired
always present, rationale, ts) back onto the packet — preserving every other
field. am failures degrade to the single-agent floor; never hangs (4s timeout)
or errors the live path.
- /discovery STEP 6 (skills + skills-codex twin) now invokes it after the packet
write — ValidateShape's first live caller.
Acceptance: running stamp-shape on a live packet produces a validated
orchestration_decision; --unattended fires durability:unattended and overrides a
single-agent proposal to atm-only (live smoke verified). 8 new tests cover
am-output parsing, write-set grouping, round-trip field preservation,
predicates_fired-always-present, and the contention-override wire.
Closes-scenario: age-aqu#s-wire
Bounded-context: BC6-Orchestration
Evidence: cli/cmd/ao/rpi_stamp_shape_test.go
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New RunScenarioAB adapter (cli/internal/eval/scenario_ab.go) runs a scenario.v1
holdout WITH vs WITHOUT the gold pull through injectable runner+judge seams,
emits a ScenarioDeltaScorecard, and applies a DETERMINISTIC gate over the
machine-readable (cross-family) judge output: fail-loud on delta<=0 (the
ADR-0002 spray returning), on the treatment missing the satisfaction threshold,
or on summed token cost over budget. The ao eval scenario-ab command wires a
thin codex-exec default (Law 0) behind the seam; tests inject fakes (no live
models, no hangs). Adds scenario.Load (the production reader). Live proof is KF-C.
Closes-scenario: age-kf-s1-close-loop-0ly.2#kf-b-runscenarioab
Bounded-context: BC4-evaluation
Evidence: .agents/plans/2026-06-16-knowledge-field-ab-runner.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Slice D from IMPROVE-DISCOVERY (re-baselined to a source switch, not a
subsystem rebuild — and targeting the CANONICAL retrieval command: ao inject is
deprecated, ao lookup is its successor).
- ao lookup --gold reads .ao/wiki/{learnings,patterns,findings} via the existing
scorers (collectLocalLearnings/rankLearnings, collectPatternsFromDir,
collectFindingsFromDir); no global/canon merge (gold is already curated).
- goldDocFiles excludes OKF reserved catalog files (index.md/log.md) so they're
never surfaced as knowledge.
- citation recording unchanged (--cite retrieved/applied still works → feeds the
existing reward loop, the seam Slice B will use).
Proven by TestGoldDocFiles_ExcludesReserved + TestCollectGoldKnowledge_SkipsCatalogFiles
+ live e2e (ao lookup --gold --query ratchet returns gold docs, no catalog noise).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restores TestCobraConformance: the wiki-okf-gold work added 'ao wiki gold',
--gold on wiki index/search, and --gold on compile; the generated CLI
reference now matches the live Cobra tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
S3 of epic ag-k7tq9 (corpus private/public separation). Formalize the
promote-time, field-level seam from the cross-family council verdict: the
corpus is lossless and private-by-default; only an abstracted lesson crosses
into the public wiki, gated by two fail-closed frontmatter fields.
- schemas/learning.v1.schema.json: +sensitivity (enum unknown/private/public,
default unknown) +publishable (bool, default false) — allowlist ceiling
- docs/contracts/corpus-learning-seam.md: field-boundary SOT (lesson crosses;
evidence/provenance/source_session never do); cites the council verdict
- ao corpus classify: dry-run-by-default annotator; operates on the YAML fence
textually (never parses the body) so one junk record can't abort; idempotent;
skips meta docs (CORPUS-POLICY.md, README.md, ...)
- L1 + L2 tests for the annotator, the dir walk, and the command
Applied to the private corpus (boshu2/agentops-corpus): 342 learnings annotated
with safe defaults, 1 meta doc skipped, idempotent. Boundary verified — 0
.agents/learnings changes in the public repo.
Closes-scenario: ag-2srq1#field-level-seam
Bounded-context: BC1-Corpus
Evidence: docs/contracts/corpus-learning-seam.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
S1 of epic ag-2jp7l (self-healing context loop), the read-side complement to S0.
After /clear, a lane (or a peer that just cleared this pane) runs 'ao rehydrate'
to restore the working thread from the latest handoff artifact — the primitive
S3 (peer-control) will call after clearing a peer.
Composes S0's artifact (does not duplicate): reads the most-recent
.agents/handoff/*.json, renders a dense re-bootstrap brief (goal, active bead +
'br show', reservations to re-acquire, next action, recent commits), points at
'ao session bootstrap' for full orientation, and marks the handoff consumed (the
consumed/consumed_at hook = the mine→measure→improve compounding feed). --peek
reads without consuming; --json emits the raw artifact.
Pure helpers (pickLatestHandoff, renderRehydrateBrief) unit-tested; the brief
omits fields the handoff lacks (never invents a bead/next-action). DOGFOODED:
ran against the real handoff written earlier this session — rendered a complete,
usable brief (charter + comms topology + next action), closing handoff→rehydrate
end-to-end on real data.
Closes-scenario: ag-7k2g2#rehydrate-readside
Bounded-context: BC5-runtime
Evidence: cli/cmd/ao/rehydrate_test.go
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only command that reports the navigator's current position by reading
the provenance ledger for landed bead→commit edges (wasGeneratedBy). Degrades
gracefully on an empty/genesis-only ledger. Agent-ergonomic --json output.
Closes-scenario: ag-cuhdj#position-reports-recent-landed-arc
Closes-scenario: ag-cuhdj#position-degrades-gracefully-empty-ledger
Bounded-context: BC1-corpus
Evidence: cli/cmd/ao/provenance_position_test.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ao provenance emit-verdict --file <verdict.json> parses bead_id, head_sha,
disposition from a pawl-verdict artifact and appends a schema-valid,
hash-chained edge (verdict --wasDerivedFrom--> commit) to the provenance
ledger. Verdict node id = <bead_id>@<sha[:7]>, trust_tier=inferred.
Wired non-blocking into scripts/pawl-verdict.sh write (after atomic rename,
|| true). Idempotent, honest, same discipline as emit-landed (ag-62jrm).
Tests: registration + L2 store (ledger grows, chain verifies, re-emit
is idempotent) + pure parser + pure edge builder.
Closes: ag-cm8nd
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GOALS milestone 1 (position signal) was 'floor poured, unfed': ag-8jf97 built
docs/provenance/ledger.jsonl + ao provenance add/verify + the tamper gate, but
nothing auto-emitted — only the genesis row existed. This plugs the sensor in.
New: ao provenance emit-landed. For the commit(s) being landed, it appends a
schema-valid, hash-chained 'bead --wasGeneratedBy--> commit' edge per bead the
commit cites (Closes/Closes-scenario trailer or the parenthesized PR-title
convention). Honest trust tier 'inferred' (a deterministic read of the commit,
never a self-grade); idempotent (re-emit is a no-op). The bead-close mapping is
honest: in push-to-main, a bead closes when its arc lands on main, so emit-on-
landed-commit IS emit-on-close.
Wired automated + human-free at the land path: the pre-push gate (both the live
Go-gate dispatcher scripts/hooks/pre-push.local and the bash escape-hatch
scripts/pre-push-gate.sh) runs scripts/emit-landed-provenance.sh AFTER a PASS,
which emits for origin/main..HEAD and commits the ledger growth as a trailing
sensor commit (one-push lag). Non-blocking by construction (always exits 0,
slice-0 graceful-degrade), skippable via AGENTOPS_PROVENANCE_EMIT_SKIP=1, and
self-terminating (the provenance commit cites no bead → no further emission).
Smoke-caught + fixed: the conventional-commit scope 'feat(cc-hooks):' was
mis-read as a bead id; the title-parens matcher now requires start/whitespace
before '(' so scopes are excluded.
Tests: pure extractor (6 cases incl. scope-exclusion + no-false-positive),
edge-shape, L2 ledger-grows-and-stays-intact through the real store, wrapper
bats (skip/happy/no-op/non-blocking). Gate green 23/23; full cli suite green;
pre-push-gate self-test 40/40. CLI surface regenerated.
Closes-scenario: ag-62jrm#emits-edge-on-land
Closes-scenario: ag-62jrm#ledger-grows-beyond-genesis
Bounded-context: BC1-corpus
Evidence: cli/cmd/ao/provenance_emit_test.go
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Q first-pass-yield numerator now requires the accept to be authorized by the
attempt-1 gate-verdict specifically: a bead counts as clean-first-pass ONLY IF
its attempt==1 gate-verdict is CONFIRMED AND an accept event references that
attempt-1 verdict's head_sha (same bead). A bead REFUTED on attempt 1 and only
later CONFIRMED — whose accept is bound to a later attempt's head_sha — no longer
inflates Q. Denominator unchanged (distinct attempted beads, difficulty-weighted).
Fixes the codex check-7 BLOCKING bug (loose accept↔verdict match) and adds the
missed test cases: late-confirm-not-clean, clean-first-pass positive, and the
loose-match regression guard. E test now also exercises HOLD (E counts ESCALATE
and HOLD). Re-dogfood still prints Q=0 for the dynamo run (both beads attempt-1
REFUTED).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Append-only JSONL yield ledger (yieldledger-event.v1): a durable,
bead-keyed operational event stream (accept | gate-verdict | usage) so
an agent factory's per-bead yield (A, R, A/R, Q, E, L) is computable
from DATA, not prose. Read by ag-qzinh (the gauges).
Rework addressing cross-family codex REFUTE of the prior commit:
- B1 Writer is now concurrent-append-safe: O_APPEND JSONL (one event
per line), no read-modify-write, no shared tmp — fungible lanes can
merge concurrently without lost/collided events.
- B2 codec enforces the closed schema (DisallowUnknownFields on the
envelope AND every typed body) — an unknown field is a REJECT.
- B3 reconcile-pr.sh emits a gate-verdict for ALL dispositions
(CONFIRMED/REFUTED/ESCALATE/HOLD) right after the gate verdict,
before exit-5 — Q's denominator + E are now computable.
- B4 reconcile-pr.sh emits a usage event at close (R/A-R/L source).
- B5 accept emit stays strictly post-MERGED; post-decision emits
backgrounded so a slow emit can never delay the merge/close path.
- B6 tests for the real risks: concurrent-append (-race), closed-schema
rejection, REFUTED/HOLD round-trip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs/provenance/ledger.jsonl was declared the append-only SOT in CLAUDE.md
("ledger wins on disagreement") but the file was never created and nothing
verified it in place — a doctrine-level lying instrument. This pours the
real slab:
- Seed docs/provenance/ledger.jsonl with the genesis event
(ag-8jf97 bead --wasGeneratedBy--> landing branch, trust_tier=authored).
- Add `ao provenance verify`: verifies the COMMITTED chain in place (no
re-sort/re-chain, unlike `export --verify`), so a tampered field, forged
hash, or reordered row is caught and the offending FILE LINE is named.
provenancegraph.Store.VerifyFile() is the line-accurate verifier.
- Wire the gate: scripts/validate-provenance-ledger.sh --gate requires the
committed ledger to exist, be schema-valid per line, and be an intact hash
chain; registered as a blocking T1 CI step in validate.yml.
- Tests (windshield-correctness): L1 deterministic hashing (existing), L2
intact-chain pass, L2 TAMPER (payload-flip / forged-hash / reorder all FAIL
naming the line), missing-file-is-empty-intact, append-creates-genesis;
plus bats coverage of the committed ledger + --gate + tamper path.
Hash discipline follows the schema + existing cli/internal/rpi/ledger.go
(genesis prev_hash = "", hash = sha256(payload_hash+"\n"+prev_hash)) — the
brief's 64-zero/concat variant was superseded per the contracts-over-narrative
precedence rule.
Closes-scenario: ag-8jf97#provenance-ledger-tamper-evident
Bounded-context: BC4-Factory
Evidence: docs/provenance/ledger.jsonl
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cross-vendor pre-land refuters (fresh Claude + read-only Codex, 2026-06-13)
found real residual on the landed converge feature. Fixes:
P1: converge.go RunE now exercises the kernel (resolve LAW-0 transport, build
the non-mutating author_neq_validator judge packet, derive the bounded-loop
config) and emits a real plan instead of a vacuous print — the primitives are
reachable from the command, not dead code. (+kill-dir flag.)
P2 (security): CanonicalizeContextID (trim + casefold + unicode/whitespace
collapse via strings.Fields) is applied before context dedup in BOTH
CheckSignificantActionDetailed and tickCouncilGate, so a single agent cannot
forge >=2 'distinct contexts' via 'ctx-1'/'CTX-1'/'ctx-1 ' variants.
P3: belt-and-suspenders author exclusion — an ACK whose AgentID==ActorID is
dropped even when ActorContextID is empty (author can't count its own ACK).
#8: convergeEvaluateRound takes an optional minContexts (variadic, preserves the
frozen single-arg acceptance call) so it cannot silently disagree with
convergeRunBounded's cfg.MinContexts.
All 30 frozen acceptance scenarios still green; new L1 tests for canonicalization
collapse, AgentID author-exclusion, RunE-exercises-kernel, evaluateRound floor.
(Refuter P0 'not on main' was a stale-checkout artifact — all slices are on
origin/main; dismissed.)
Closes-scenario: ag-converge-hardening-wof1r#refuter-residual
Bounded-context: BC5-Runtime
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Five phases: validate (phantom/critical/carve-out/--into refusals), remove
trees (skills/, skills-codex/, skills-codex-overrides/, images/*/skills/),
non-lossy text-targeted dispositions ledger flip (active row -> historical,
comments preserved), ordered regen via injectable runner, and a table-driven
ripple scan that exits non-zero while unresolved references remain.
--dry-run plans everything with zero mutations; --json emits a
machine-readable report. No git commands inside the command — the operator
lands; mutations are git-recoverable.
Closes-scenario: ag-48ybr#skills-retire
Bounded-context: BC4-factory
Evidence: cli/cmd/ao/skills_retire_test.go
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each image-health check now runs under a per-check timeout budget
(default 30s, --check-timeout to override). A check exceeding its
budget is killed and reported as FAIL with timed_out/slow flags and
its duration; remaining checks still run. Summary counts slow checks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What
Adds `ao skills resolve` — a MECE audit of the `skills/` corpus, ported
from the `control-plane/bin/skill-resolve` prototype
(cp-skill-resolver-mece-dry).
- **Mutually Exclusive (ME):** clusters skills by name-family stem +
description-token Jaccard, surfacing overlapping/near-duplicate skills
as **merge candidates** (the prune queue, cp-dkf).
- **Collectively Exhaustive (CE):** flags thin / description-less
`SKILL.md` files as coverage-quality gaps.
Read-only; mutates nothing.
```
ao skills resolve # MECE table
ao skills resolve --json # machine-readable prune queue
ao skills resolve --strict # CI dedup gate: exits 1 on any ME overlap
```
## Scope decision
This ports the **MECE half** only. The deployment-DRY half (which live
`~/.claude/skills` symlink backs each name, shadows, `--fix`) is an
operator-runtime concern and stays in `control-plane/bin/skill-resolve`.
Two tools, two scopes: product-authoring in `ao`, deployment in
control-plane.
## Implementation
- New `cli/internal/skillsresolve` package, reusing
`skillshealth.ParseFrontmatter` (no reinvention) + cobra wiring in
`cli/cmd/ao/skills.go`.
- Package tests (`resolve_test.go`) + command-level tests
(`skills_resolve_test.go`: registration, `--json` schema, `--strict`
exit).
## Live run
171 skills, 18 ME overlaps, 0 CE gaps — mirrors the prototype
(beads-br↔bv 1.0; mcp-plugins / risk-audit / test families).
## Drive-by fix (separate commit)
`fix(skills): wire security-suite into SKILL-TIERS.md` —
`security-suite` shipped via the image-bundle merge but was never
tiered, leaving `wiring-closure` red on `main`. The HEAD-based
pre-push/CI gate blocks any push while it's red, so it's repaired here.
Not part of the feature.
## Verification
go build ✓ · go vet ✓ · go test (resolve pkg 2/2, command 3/3) ✓ · gofmt
✓ · full pre-push gate green.
The bushido continuous-validation backstop: watch main -> full gate on each new
commit -> classify deterministic-vs-flaky (re-run N=3; the 18-30% flake rate
makes naive escalation noise) -> on deterministic blocking FAIL: poison beacon
(.refinery-poison + git note) + fix-bead (bd create) + alert. NEVER reverts
(P2.5 quorum-gated revert deferred to ag-k99u). Backstop-not-gatekeeper: down =
merges still succeed; resumes from .refinery-state. Injectable ports (git/bd/
gates adapters); core tested with fakes (deterministic escalates, flaky doesn't,
green clears, never-reverts). ao refinery once|run; systemd unit + runbook for
bushido. go test green. ag-qidx.
GA6: pre-push.local routes through 'ao gate check --fast' when AGENTOPS_GATE_GO=1,
with the from-source bootstrap (go build ./... first — block on the build error
if broken — then build a fresh ao and exec it; pre-mortem BLOCKER-3, no stale
binary). Default stays the bash gate: the Go registry is a 12-check subset until
Phase B reaches parity, and flipping the default early would regress coverage on
the only pre-merge wall. Adds explicit --fast flag. Proven: AGENTOPS_GATE_GO=1
push path = 12/12 PASS via from-source ao. ag-qidx.
Routes the old ao cron self-adjust CronCreate renderer/history writer behind the mto-fleet boundary while preserving a machine-readable compatibility shim. Updates reduction accounting to KEEP 453 / RELOCATE 143, regenerates COMMANDS.md and registry.json, and keeps rpi_* deferred. Local targeted Go tests, regen checks, command-surface check, diff check, and fast pre-push passed; GitHub Actions passed/skipped, Cursor Bugbot was pending and bypassed under operator instruction.
Retires the archived pool migrate-legacy surface while preserving legacy capture ingestion through pool ingest. Partial cp-cd9 slice; RELOCATE seam remains.
## Summary
- add `ao tick` as a typed Go port of the control-plane tick oracle
- preserve BR/git boundaries for next/status/claim/reopen/close and port
verdict/council/guard/smoke behavior
- update generated command inventory, registry, CLI docs, and
command-surface fixtures
## Validation
- cd cli && go test ./cmd/ao -run TestTick -count=1
- cd cli && go test ./cmd/ao -count=1
- cd cli && go vet ./...
- cd cli && go test ./... -count=1 -short
- bash scripts/check-cmdao-surface-parity.sh
- bash scripts/regen-all.sh --check
- git diff --check
- PRE_PUSH_SKIP_MKDOCS=1 bash scripts/pre-push-gate.sh --fast
- built /tmp/ao-cp7k6 and compared against
/Users/bo/dev/control-plane/tick.sh for verdict-gate, council-gate,
next; `ao tick smoke` returned SMOKE PASS
Note: fast pre-push gate still reports existing warn-only
executable-spec drift for scenarios --lint and trace --orphans.
## What
Adds `--manifest-out <dir>` (+ `--run-id`) to `ao eval outcomes ingest`.
When set, ingest **also** writes `<dir>/<run-id>/manifest.json`
conforming to `schemas/eval-run.v1.schema.json`, so the existing verdict
pipeline (`docs/contracts/eval-verdict-pipeline.md`) can pick it up and
feed the Knowledge Flywheel — closing the **Outcomes→Flywheel loop** for
the eval-run.v1 surface. Bead: **ag-fy6e**.
## How
- New file `cli/cmd/ao/eval_outcomes_manifest.go` (diversifies off
`eval_outcomes_ingest.go`); ingest.go only wires the thin flag.
- Reuses the existing `eval.RunRecord` struct rather than hand-rolling
the schema shape.
- Band mapping: `PASS→{status:pass, verdict:pass}`, `FAIL→{fail, fail}`,
`WARN→{inconclusive, advisory}`.
- `dimension_scores` filtered to the 7 schema-permitted keys (foreign
criterion dropped; `minProperties:1` guarded by synthesizing
`correctness=aggregate`).
- Out-of-process grade → honest schema-valid stubs for
`git`/`runtime`/`environment` + a `notes` provenance entry.
## Deviation from bead text (source-of-truth precedence)
The bead said `status=complete`, but that value belongs to the **other**
SCHEMA.md run-manifest. eval-run.v1's `status` enum is
`pass/fail/error/skipped/inconclusive` — no `complete` member — so this
follows the schema (per CLAUDE.md precedence: executable contracts >
narrative).
## Scope boundary (honest)
This delivers **only** the eval-run.v1 emission named in the bead's
ACCEPTANCE CRITERIA. The downstream `eval-verdict-compiler.sh` /
SCHEMA.md run-manifest path (rc3 struct verdict + holdout-sensitive
`ground_truth_ref/hash`) remains **BLOCKED** on the external compiler
contract + a holdout-safety decision, per the bead's own 2026-05-30
SCOUT CORRECTION. eval-run.v1 carries no holdout-sensitive fields, so
that blocker does not apply to this slice. Bead left open for the
compiler-path follow-up.
## Verification
- `go build ./... && go vet ./cmd/ao/` clean
- 77 eval/outcomes tests pass (`go test ./cmd/ao/ -run 'Eval|Outcomes'`)
- Real CLI run writes a manifest that validates against
`eval-run.v1.schema.json` via python `jsonschema`
- `cli/docs/COMMANDS.md` regenerated for the two new flags
Closes-scenario: ag-fy6e#manifest-out-eval-run
Bounded-context: BC2-Validation
Evidence: cli/cmd/ao/eval_outcomes_manifest_test.go
## What
`ao defrag` now takes an **automatic, recoverable corpus snapshot**
before it applies a destructive `--prune` (anything that deletes from
`.agents/`). It reuses the same tar.gz machinery as `ao corpus
snapshot`, so an over-aggressive cleanup is always rehydratable via `ao
corpus restore`.
## Why (ag-32xk — moat durability)
The corpus moat is switching-cost / data-gravity, not tech — and it is
one accidental delete away from losing its receipts. Directive #11
history: routine maintenance wiped `.agents/` subdirs, counts went to
~0. A moat you can lose to routine cleanup doesn't feel like a moat.
This closes the most common loss path (the destructive defrag) first.
## Behavior
- **Apply-mode prune** → snapshot first, then prune.
- **Safe-by-default**: a snapshot failure *aborts* the prune — we refuse
to delete files we couldn't first back up.
- **No-op** for: dry-runs (nothing deleted), dedup-only runs (flags,
never deletes), and `--no-snapshot` (new escape hatch).
## Changes
- `corpus_snapshot.go`: extract `createCorpusSnapshot()` as the shared
engine behind both the `ao corpus snapshot` command and the defrag
auto-snapshot (no behavior change to the command).
- `defrag.go`: `autoSnapshotBeforeDefrag()` + `--no-snapshot` flag +
help text.
- `defrag_autosnapshot_test.go`: 6 tests (apply-prune snapshots;
dry-run/no-snapshot/dedup-only skip; missing-corpus aborts; L2
`runDefrag` leaves a recoverable tarball).
- `cli/docs/COMMANDS.md`: regenerated for the new flag (passes
`generate-cli-reference.sh --check`).
## Scope
Slice of ag-32xk. **Remaining (bead stays open):** `ao curate`
auto-snapshot + a canon/promoted-entry delete guard.
## Verification
- `cd cli && go build ./... && go vet ./cmd/ao/` — clean
- `go test ./cmd/ao/` — 9315 pass
- `scripts/pre-push-gate.sh --fast` — passed (CLI docs parity green)
Closes-scenario: ag-32xk#defrag-autosnapshot
Bounded-context: BC1-Corpus
Evidence: cli/cmd/ao/defrag_autosnapshot_test.go