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.
flywheel compare direct-test coverage moved from cmd/ao into internal/flywheelapp;
mirror the provenance/skills carve-out allowlist pattern and regen cli-surface.
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
## Summary
Closes the findings from the v3.3.0 release-viability audit of main.
**Surface truth (High):** `ao constraint`, `ao goals trace`, `ao session
memory`, and `ao skills edit` are production tombstones but
docs/cli-surface.md documented them as `public-tested`/`covered` (the
test binary keeps constraint live, and the generated doc reflected the
test build). Added `deprecated` allowlist rows, dropped the stale
constraint-subcommand rows, regrouped constraint with its tombstone
peers in the default spine, regenerated both surface projections, and
added `TestCathedralCutTombstonesSurvivePruning` pinning the invariant
that every Cathedral Cut verb keeps an executable tombstone.
**Installer hardening (Medium):** `install-bd.sh` now fails closed when
no published checksum exists for a release asset (explicit `--no-verify`
overrides), and the curl|bash path verifies the fetched
`installer-common.sh` against a pinned SHA-256 before sourcing it
(drift-guarded by a new install-bd.bats test).
**Docs (Medium/Low):** UPGRADING.md no longer links the MIGRATION-3.0.md
file the Cathedral Cut deleted; CHANGELOG 3.3.0 `### Removed` now names
`yield`, `constraint`, and the 3.2 `verify` front door and links the
MIGRATION.md map.
**Gate scripts (Low):** flywheel/provenance gate scripts build `ao` into
`mktemp -d` scratch dirs instead of fixed world-known /tmp paths
(symlink/TOCTOU on shared hosts).
**Bonus (surfaced by the push gate):** `scripts/lib/preamble.sh`
mis-resolved REPO_ROOT under git hook env (GIT_DIR without
GIT_WORK_TREE), which made `go.cli-architecture` refuse every
worktree-origin push touching `cli/cmd/ao/**`. Fixed by scrubbing the
hook-injected discovery env around the rev-parse.
## Validation
- `go build && go vet && go test ./...`: 5,085 tests green,
golangci-lint clean
- `ao gate check --fast`: 27/27 pass, including under simulated hook env
- bats install-bd suite 6/6 incl. new pin drift test; shellcheck clean
on all touched scripts
- markdownlint clean; doc-release gate passes; tombstone exit codes
verified on the built binary
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.
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
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
buildAndonQueue gains a fourth pull-style source: a packet under
.agents/goal-design/ still in draft or validated is flagged kind=stale-packet
when a CONFIRMED provenance verdict edge names its slug or a driver candidate
bead resolves to a closed tracker bead. Fail-toward-silence by design: closed
or superseded packets and evidence-free drafts are never flagged, and bare
git-log slug mentions are deliberately not an evidence arm (self-triggering).
No daemon (ADR-0009) — recomputed at report time.
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.
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).
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.
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
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
Archive the two remaining self-contained RPI/factory command clusters behind
//go:build legacy:
- orchestrate (orchestrate.go + _common/_preflight/_route/_shape/_status/_tools/
_verify + tests). The internal/orchestration LIBRARY stays untagged — spine
stamp_shape.go still uses it; only the command files are archived.
- loop (loop.go + loop_* subcommand cluster + tests). The internal/evolve
library it imports stays untagged.
With orchestrate now archived, the doctor's stale-command fixer can no longer
point at 'ao orchestrate': repoint DeprecatedCommands["ao work rpi"] to the
surviving spine loop driver 'ao converge' (stale_refs.go + fix_skills_test.go).
Default 'go build ./...' now omits codex/orchestrate/loop/tick/autodev; the
sibling spine commands (ready/close/verdict-gate/council-gate/guard-status/
install-guards/chaos-test/converge/gate) remain. make build-flywheel /
AGENTOPS_LEGACY=1 make build restore the archived set. regen-all swept all
surfaces; both build variants build+vet+test clean (spine 12018, legacy 12033);
regen-check ALL GREEN.
Archive the RPI/factory commands whose spine entanglement was decoupled first:
- codex lifecycle (codex.go, codex_schema.go + tests) — spine symbols already
extracted to untagged codex_artifacts.go / codex_task_schema.go.
- autodev (autodev.go + tests).
- tick: only the 'ao tick' command surface moves to tagged tick_cmd_legacy.go;
the tick ENGINE (tickRuntime, newTickRuntime, tickPassthrough, tickSmoke,
tickVerdictIdentity, tickExitCouncil) stays UNTAGGED in tick.go because the
spine consumes it (ao claim, ao converge, ao eval chaos, converge_canary), as
do the sibling spine commands ready/close/verdict-gate/council-gate/
guard-status/install-guards/chaos-test.
Cross-cutting test wiring:
- testutil_archived_globals{,_legacy}_test.go: build-tag-split delegate that
saves/restores/resets the codex+autodev cobra-flag globals only in the tagged
build, so the untagged (spine) test helpers (resetGlobalFlags, executeCommand)
never reference archived symbols.
- cobra_commands_test.go / tick_test.go: drop archived names from spine
expected-command lists; tick subcommand coverage moves to tick_cmd_legacy_test.go.
- flywheel_promoted_body_dedupe_legacy_test.go: the one codex-command test split
out of the otherwise-spine close-loop test file.
Default 'go build ./...' omits these commands; make build-flywheel /
AGENTOPS_LEGACY=1 make build restore them. regen-all swept the surfaces. Both
build variants build+vet+test clean; regen-check green.
ao done <bead-id> [--sha] [--force-no-verdict] [--json]: closes via the br CLI
on PATH with the close reason stamped [verdict:<sha7>:<disposition>]. CONFIRMED
ledger verdict -> CONFIRMED stamp; verdict-less provenance-only commit (the
pawl #trivial waiver class, fail-closed diff-tree) -> waived-trivial; otherwise
REFUSE naming ao verify / ao pawl review, with --force-no-verdict writing an
explicit greppable UNVERIFIED stamp. BEADS_DIR resolution reuses the
beadsTrackerCommandContext env path (no hardcoded ledger paths).
scripts/check-verdict-close-rate.sh: warn-only gate (threshold 0 baseline,
--strict --threshold N for the later ratchet) measuring the [verdict:...]
stamp fraction of the last N closes in the br issues.jsonl (last-wins per id);
skips cleanly when jq/BEADS_DIR/ao/ledger are absent.
Tests: 13 Go cmd-level tests (stub br on PATH + production-writer ledger
fixtures + t.TempDir git repos) + 14 bats over fixture JSONL. Narrow CLI-ref
regen (generate-cli-reference.sh) included to keep TestCobraConformance green.
The scenario-satisfaction layer's consumer (goalsfitness aggregator +
ao goals measure --scenarios-only) and contract (scenario-results.v1
schema/loader/writer) shipped long ago, but nothing ever wrote
.agents/rpi/scenario-results.json — a dead instrument reading
unknown/0%-evaluated for every directive. This lands the missing
producer, resolving the bead's decision rule to Option A (wire it,
writer-only, no council-judge dependency in v1).
ao eval scenario evaluate [--all|--directive <id>] [--json] [--timeout]:
- GATE-SHAPED scenarios (acceptance_vectors carrying a mechanical
"check" command; "gate:<id>" resolves through the GOALS.md Gates
table) run each check via goals.MeasureOne (sanitized bash, per-check
timeout, exit-77 skip convention). Score = fraction of checks passed;
verdict = score vs the scenario's own satisfaction_threshold, exactly
matching the aggregator's countSatisfied comparison.
- JUDGMENT-SHAPED scenarios (no mechanical check) are recorded as
verdict "skip" with attestation-needed evidence — the nearest
ValidVerdict for "cannot mechanically evaluate"; never a fabricated
pass (the anti-pattern GOALS.md's pre-production section forbids).
- A check that could not run (timeout, unresolvable gate ref) yields
skip, and missing/retired scenario links write NOTHING, so zero
evidence stays VerdictUnknown downstream.
- Results persist through the production scenarioresults.Writer.Append
(latest-judged_at supersede per scenario_id; iteration = prior + 1).
L2 tests prove the full producer->consumer round trip: the command
writes the artifact, the production loader accepts it strict, and the
real runScenariosOnly/EvaluateSatisfaction path reads back nonzero
evaluated counts (pass, fail, judgment/unknown, timeout, unresolvable
gate ref, supersede-on-rerun, threshold-equality lanes).
Nightly cadence wiring deliberately deferred to a follow-up commit.
Known: cli/docs/COMMANDS.md conformance regen deferred to landing.
Fold state, memory, rehydrate, handoff under `ao session` (zero-breakage via
hidden deprecated back-compat aliases for the three with external callers;
state gets none). `ao session bootstrap` is byte-identical — session_bootstrap.go
untouched.
Reparent rootCmd.AddCommand → sessionCmd.AddCommand and DELETE the GroupID lines
(stateCmd "knowledge", memoryCmd "config", handoffCmd "workflow") — cobra panics
at Execute if a child's GroupID isn't in the parent's groups, and sessionCmd
defines none. `ao --help` verified NO-PANIC.
Hidden aliases (Hidden + Deprecated→stderr, so COMMANDS.md is a pure move but
`ao handoff` / `ao memory sync --quiet` / `ao rehydrate` still execute) share the
same RunE + flag helpers; --quiet registered on both the session child and the
alias. cobra_commands_test parent map + a new TestSessionFoldedCommandsResolve;
stale_refs deprecation map. regen-all swept the surfaces (cli_commands 81->77).
go test = 7963 pass; surface-parity/smoke/cli-skills-map/regen-check all green.
Fold the top-level eval commands under `ao eval`, zero-breakage via dual-register
(old spellings kept Hidden, new eval subcommand shares the same RunE):
- retrieval-bench -> ao eval bench
- chaos-test -> ao eval chaos
- session-outcome -> ao eval session-outcome
- scenario subtree reparented under ao eval (add/init/list/validate follow)
`ao eval scenario-ab` preserved (ADR-0004 revival path).
Repointed inline `ao scenario` body-refs in skills/goals + eval-outcomes/flywheel
to `ao eval scenario` (+ mirrored the goals/eval-outcomes Codex twins + regen hashes)
so the FULL-tier skill.body-refs gate stays green. stale_refs.go deprecation map
updated; new eval_fold_surface_test.go (go.command-test-pair). regen-all swept the
command surfaces (cli_commands 84->81 as the 3 hidden leaves leave the public catalog).
go test ./... = 12046 passed.
Commands-only archive of the cleanly-separable RPI/factory commands behind
//go:build legacy: turn (turn_verify.go), harness (harness_cmd.go), operator
(operator_cmd.go + operator_adapter.go). Default `ao` surface drops them;
AGENTOPS_LEGACY=1 make build / make build-flywheel restore them; all four
build+test variants pass. regen-all swept the surfaces.
Scope note (mirrors .13): a leaf-audit found only turn/harness/operator archive
cleanly commands-only. The rest are spine-entangled: codex (context_ranked_intel
+ membrane use codexArtifactRef), evolve, orchestrate (multi-file cluster), loop
(cluster), tick (converge_canary uses tickExitCouncil), autodev (shared
testutil_test.go resets autodevFile/autodevForce). These need the same
spine-decoupling as the corpus/flywheel clusters — folded into a legacy spin-out.
Tag harvest.go + its tests with //go:build flywheel — a clean leaf (no spine
symbol consumer). Default surface drops it; make build-flywheel restores it;
all four build+test variants pass. regen-all swept the surfaces.
Scope note: an empirical leaf-audit found that of the corpus/flywheel command
list, only refinery/mind/harvest archive cleanly commands-only. The rest (wiki,
mine, dedup, defrag, corpus, compile, curate, pool, maturity, flywheel, ratchet,
store, temper) export symbols consumed by SPINE code (root.go error handling,
membrane.go, pawl.go, session-end maintenance, the deprecation map, uat smoke) —
they need the same spine-decoupling as the internal packages, so they are folded
into the age-nzwo spin-out rather than archived here.
Commands-only archive (operator decision: full package archival deferred to a
spin-out because spine inject/gate/eval import internal/wiki|pool|ratchet).
Tag refinery.go + mind.go + their tests with //go:build flywheel: the default
`ao` surface drops them; `make build-flywheel` restores them. regen-all swept
the command surfaces (cobra expectedCmds, COMMANDS.md, cli-surface, eval matrix).
Archive infrastructure (applies to all batches): the spine-conformance tests
(TestCobraExpectedCmdsMatchRegistration, TestCobraConformance,
TestBuildtags_DefaultBuildIsSpine) now skip when len(archiveBuildTags)>0 — the
generated surfaces document the default build, and the flywheel/legacy build is a
documented superset, so both `go test ./...` and `go test -tags flywheel ./...` pass.
ao cron was a no-op compatibility shim: its own help said cron self-adjust
'moved behind the MTO/factory boundary; AO keeps only this compatibility shim'
(the scheduler was removed in ADR-0009). The one unambiguous delete in this
epic. No live callers (only historical snapshots + generated surfaces, now
regenerated clean). Removed cron.go, cron_self_adjust.go, and its test;
regen-all swept the command surfaces; cobra children-map entry cleaned.
Delete recall.go + memory_ingest_claude.go (+tests): cm (cass-memory) + cass already
do cross-agent curated+history recall, and cm beat ao recall 6-0 (docs/memory-v1.md
SUPERSEDED banner). ingest-claude was the MEMORY.md-index-pollution regression. Regen
drops both from the CLI command surface.
SCOPE RE-PLANNED from the bead (agile, build-evidence-driven): the audit's 'inject leaves
are pure, no external consumer' was WRONG — inject.go holds shared constants/utils (~9
survivors) AND wiki.go's runWikiInject routes to runInject. So retiring the ao INJECT
command is a separate P0-like decouple (relocate shared utils + repoint wiki) tracked as
a follow-up bead, not bundled here. This phase ships the clean, on-thesis win: recall +
ingest-claude gone. Build+vet+162 cmd/ao tests + regen-check green.
Ingests ~/.claude/projects/*/memory/*.md (390 curated facts stranded in Claude-only
silos) into ao's machine-tier corpus (~/.agents/learnings/) so 'ao recall' covers
them. Content PRESERVED verbatim (no LLM extraction); each wrapped as an ao-learning
tagged tier=machine + source=claude-memory + origin_path, with the source mtime as
the decay date. Idempotent — dest filename derived from source path (claude-memory--
prefix makes provenance visible in recall's cited path), so re-run refreshes in place,
never duplicates. --dry-run + --source/--dest flags. Missing ~/.claude is a clean no-op.
Proves the .5 acceptance end-to-end: a fact living ONLY in a Claude silo
(pawl-run-foreground-not-background) was a recall MISS before; after ingest it surfaces
[machine] tagged claude-memory. Closes the biggest miss-log gap from the .7 validation.
ao recall is the single memory front door all agents (Claude/Codex/Gemini) share:
queries the per-project repo .agents/ CURATED corpus (PROJECT tier, resolved from
the repo root, never $HOME) + the per-machine ~/.agents/ hub (MACHINE tier) over
ao's EXISTING lexical + freshness-decay(exp(-ageWeeks*0.17)) + maturity scorer.
Over-fetches per tier then merges/de-dupes/ranks GLOBALLY and caps to --limit (so
--limit N is the global best N, not per-tier-truncated). Each hit is tier-tagged at
collection time and cited {tier,path,score,snippet}. Curated-only (raw transcripts
are ao search). Lexical-only — NO dense/embeddings/CGO (dense DEFERRED per miss-log).
--limit validated (no negative-bound panic). 10 unit tests incl. repo-root walk-up,
$HOME-guard, and limit validation.
Contract docs/memory-v1.md ratified: ao=SOT; .agents/+~/.agents/ are the stores;
cm + the Claude per-project silos become ingest-then-retire inputs. Non-goals held.
VALIDATION: evals/memory/recall-acceptance.sh is a REPORT-ONLY topic-recall SMOKE
(21/25 topics surfaced in top-5, echo-free; rg baseline 0/5 — recall wins on ranked
tier-cited retrieval). It deliberately CERTIFIES NOTHING. The rigorous KNOWN-ITEM
ship gate (memory-v1.md done-criteria #3: the RIGHT cited memory, >=20/25) is bead
.8 — OPEN — earned as the corpus is enriched (.5: ingest the Claude silos). .6
(contract) + .7 (front door) land here; .8/.4/.5/.2/.3 remain the tracked follow-on.
S4 of the smart-membrane PROVE-FIRST plan (epic age-zpj5) — the honest recurrence+compilability
instrument that gates the DEFERRED compiler. Its realistic verdict today is INSUFFICIENT-DATA (the
corpus is far below the power floor) — the PROVE-FIRST point: do NOT build the compiler on faith.
- yieldledger/triage.go (pure, tested): TriageDecide (pre-registered numeric rule — INSUFFICIENT-DATA
if <15 stored-reason classes OR axis2-coverage<100%; elif axis1<0.20 MEMORY-ONLY; elif axis2<0.33
CURATED; else GO; unassessed in the Axis-2 denominator so a small assessed subset cannot inflate);
AssessCompilability (all-instances TP-replay — a detector is compilable only if it HITS every stored
bad instance AND zero-FP on clean HEAD; an overfit/no-op detector is not_compilable); TriageCorpus
(recurrence over DetectCatches class_keys; reason-less REFUTEDs are the UNCLASSIFIED floor — counted,
NEVER synthesized into a class).
- Catch gains per-instance data (Instances: head_sha + paths) so the all-instances TP-replay can replay.
- ao membrane triage command: reads the corpus, runs the git-content TP-replay assessment per recurring
detector class, applies the rule, prints the decision + axes + counts (or --json).
- Tests (a-f): below-floor -> INSUFFICIENT-DATA; straddle fixtures -> exact branch (MEMORY-ONLY/CURATED/GO);
unassessed-in-denominator -> coverage<1.0 -> INSUFFICIENT-DATA; no-fabrication floor; no-op + overfit
detectors -> not_compilable; command-level INSUFFICIENT-DATA. yieldledger 78 pass; cmd/ao 7795 pass; vet clean.
regen-all updated COMMANDS.md / cli-surface / surface-matrix / codex twins for the new command.