Wave 7, run through a full `/rpi` on itself per the operator's
directive.
**The feature:** `validator: {kind: 'spawned' | 'command', command}` on
the rpi workflow. With `command`, the fresh Validate context becomes a
**broker** for an external judge (any vendor CLI — the command is opaque
caller input): identical evidence packet to the spawned judge, raw
stdout/stderr + the judge charter persisted under `.agents/ao/` and
referenced in `evidence_refs`, ruling transcribed verbatim. Three
load-bearing honesty rules: **no verdict laundering** (ambiguous ruling
→ NOT_PROVEN), **no silent fallback** (dead command → NOT_PROVEN, never
a quiet spawned-judge downgrade), **three distinct identities** (author,
external validator, broker attester). Spawned mode proven byte-identical
to baseline via prompt extraction.
**Built by the loop it extends:** Plan snapshotted the intent
(`660684c4…`), Implement proved the freshness wall in both modes with a
sentinel harness, a fresh validator persisted PASS `verdict.v2`
`bd733619…` over the exact four-file subject.
**The smoke story is the headline.** The first live cross-vendor run —
`codex exec --sandbox read-only` as judge — returned an honest **FAIL**:
the implement stage had gathered a git-status receipt from the wrong
working directory, and the external judge refused the inconsistent
evidence rather than rubber-stamping. That FAIL surfaced a real
prompt-precision gap; cwd pinning is now load-bearing in every stage
prompt (contract comment cites the incident). The revised invocation
passed: `verdict.v2` `1551d3be…` with **the external judge's own session
UUID as `validator_context_id`**, the broker's id as freshness attester,
and both raw transcripts in evidence — verified distinct three ways in
the persisted bytes.
**Gates:** 66/67 over this range; the one FAIL is the known
`workflow.install-drift` local-estate finding (post-merge reinstall
documented on #945). CI safe via absent→skip.
Workflows
Reusable orchestration conveyors for the Claude Code Workflow tool. Workflows
are a Claude-only runtime adapter — the same doctrine as skills-codex/
(Codex-only): canonical source lives here, and a runtime link step installs it
where the one runtime that consumes it resolves names.
Three generic conveyor shapes:
| Workflow | Shape | Use when |
|---|---|---|
audit-dimensions |
pipeline: finder → skeptic, per dimension | auditing a subject across independent lenses |
verify-fixes |
parallel adversarial verifiers, one per group | refuting "it's fixed" claims after a change |
implement-wave |
parallel disjoint-scope lanes → one fresh verifier | executing a wave of bead-shaped work items |
rpi |
pipeline: plan → implement → fresh validate | one intent through the core loop to a durable verdict |
Four repo-doctrine conveyors also live here: bdd-foundry (behavior-first
planning → acceptance-gated beads), operating-loop (one capability through
the seven-move loop end to end), ship-beads (drive a list of beads to
confirmed-merged), and bead-crank (deprecated alias delegating to
ship-beads). Each documents itself in its meta header.
Install
From the canonical checkout:
ao workflows link
Links land in the project-local .claude/workflows/ directory, where the
Claude Code harness resolves named workflows. The directory is gitignored;
only the runtime links live there — workflows/ is the tracked source of
truth. ao workflows link mirrors ao skills link semantics: idempotent,
refuses to replace foreign links or real files, and ao workflows unlink
removes only links pointing back into this checkout.
Session-snapshot caveat: Claude Code snapshots the named-workflow registry at session start. Newly minted links appear in the next session, not the one already running.
Doctrine: thin conveyors
These scripts are thin conveyors. All task semantics — the subject, the charters, the briefs, the acceptance criteria — arrive via args. The script contributes only orchestration shape, guardrail scaffolding (RED-first, disjoint ownership, no-stash, adversarial verification, destructive-command-guard awareness), and result plumbing. If a prompt inside a script ever encodes knowledge about a specific repo, defect, or session, that is a bug in the script. Agents operate in the session working directory; pass args.root only if you must point them elsewhere. Malformed args throw immediately with the expected shape — a thrown workflow is better than a silently wrong fleet.
The bead is the reusable artifact, not the orchestration. implement-wave lanes are deliberately bead-shaped — {key, scope, brief, acceptance} — because acceptance is the contract the verifier judges against, exactly how a bead carries acceptance into Validate. Write good beads; the conveyor is interchangeable.
Durable evidence
Workflow results live in the chat that ran them. When verdicts must outlive the chat, say so in the verify-stage brief (verify.brief in implement-wave, or the group items' wording in verify-fixes) and instruct the verifier to persist through the product's own Validate skill (verdict.v2). The workflow itself never owns lifecycle — no retry, no closure, no landing.
audit-dimensions
Fan one audit subject across caller-defined dimensions. Each dimension gets a read-only finder (findings must cite re-openable evidence), then a skeptic re-opens every citation and returns CONFIRMED | REFUTED | DOWNGRADED per finding. Only non-refuted findings survive, with corrected severities.
Args: { subject: string, dimensions: [{ key, charter }], bar?: string, root?: string, maxFindingsPerDimension?: number }
Returns: { dimensions: [{ key, summary, findings, refuted }] } — a dimension whose auditor died comes back with empty findings and an error field, never silently dropped.
Workflow({ name: 'audit-dimensions', args: {
subject: 'the v2.1 release candidate on the current branch',
dimensions: [
{ key: 'docs', charter: 'Check user-facing docs match actual CLI behavior.' },
{ key: 'errors', charter: 'Check error paths fail loudly, never silently swallow.' },
],
bar: 'blocker = ships broken to users; minor = cosmetic',
maxFindingsPerDimension: 5,
}})
verify-fixes
Adversarial verification of claimed fixes. One verifier per group tries to break every claim with fresh evidence; a claim earns RESOLVED only when it survives. INCOMPLETE covers partial fixes and anything the verifier could not actually check; REGRESSED covers new breakage. Side observations land in residuals.
Args: { context: string, root?: string, groups: [{ key, items: [string] }] }
Returns: { groups: [{ key, verdicts: [{ item, verdict, evidence }], residuals }] } — a group whose verifier died comes back with every item INCOMPLETE and an error field: a dead verifier is unverified work, never silent success.
Workflow({ name: 'verify-fixes', args: {
context: 'PR #42 on this repo claims to fix flag parsing in cli/parse.go',
groups: [
{ key: 'parsing', items: [
'--json and --robot are no longer mutually destructive',
'unknown flags produce a non-zero exit with a hint',
]},
],
}})
implement-wave
One wave of parallel implementer lanes with strictly disjoint file ownership, then a single fresh adversarial verifier judging every lane against its acceptance. Lane scaffolding enforces: RED reproduced before editing (pre-fix binaries built first), GREEN proven after, no git stash on the shared tree, out-of-scope needs reported in constraints instead of edited, destructive-command guards respected. A failed lane is surfaced to the verifier rather than dropped.
Args: { context: string, conventions?: string, root?: string, lanes: [{ key, scope: [string], brief, acceptance }], verify?: { brief } }
Returns: { implementers: [{ key, summary, red_repro, green_proof, files_changed, constraints }], verification: { verdicts, residuals } } — a lane whose agent died comes back with an error field and is still handed to the verifier.
Workflow({ name: 'implement-wave', args: {
context: 'repo at the session working directory, branch fix/wave-1; Go CLI',
conventions: 'gofmt; table-driven tests; wrap errors with %w',
lanes: [
{ key: 'ab-101',
scope: ['cli/internal/parse/**'],
brief: 'Make flag aliases case-insensitive.',
acceptance: 'go test ./cli/internal/parse/... passes including a new case-insensitivity test that fails before the change.' },
],
verify: { brief: 'Persist each lane verdict via the Validate skill (verdict.v2).' },
}})
rpi
One caller intent through the core loop, once: Plan shapes one active behavior
and snapshots the exact intent bytes under SHA-256 identity (validate tooling's
snapshot-intent), Implement runs one bounded RED→GREEN experiment strictly
inside the write scope, then a separately spawned Validate context re-verifies
the intent digest, computes the subject manifest over the changed paths, judges
every acceptance criterion with fresh evidence, and persists verdict.v2 via
store-verdict with distinct author/validator context ids and a freshness
attestation. The script is the wall: the validator receives only the intent
identity, acceptance, write scope, changed paths, check receipts, and author
context id — never the implementer's narrative. Any dead stage degrades the
result to NOT_PROVEN with an error naming the stage.
Doctrine: one pass, no retry loop, no revision path, no lifecycle ownership — the authoring context structurally cannot issue its own binding PASS.
Cross-vendor validation: with validator: { kind: 'command', command: '<judge>' }
the spawned fresh Validate context becomes a broker for an external judge — it
learns the command's invocation shape from its --help, passes it the validator
charter plus the exact evidence packet the spawned judge receives today (intent
digest + snapshot path, pinned acceptance, pinned write scope, changed paths,
check receipts, author context id — nothing more; the freshness wall is
unchanged), captures raw stdout+stderr under the run's .agents/ao/ evidence
area, and persists verdict.v2 exactly as today. Three honesty rules are
load-bearing: no verdict laundering — the persisted verdict is exactly the
external judge's ruling, and output without an unambiguous
PASS/FAIL/NOT_PROVEN ruling becomes NOT_PROVEN with the parse problem named
in evidence, never an interpreted verdict; no silent fallback — an absent,
non-executable, or output-less command makes the run NOT_PROVEN naming the
command failure, never falling back to the spawned judge; vendor-agnostic —
the script and prompts name no vendor, the command is opaque caller input. The
broker records its own context id in the attestation as the attester, distinct
from both the author id and the external validator id (the judge's run/session
identity when its output provides one, else the SHA-256 of the raw transcript
file), with the transcript path recorded in evidence_refs and the criteria
transcribed from the external ruling.
Args: { intent: string, root?: string, writeScope?: [string], acceptance?: string, validator?: { kind: 'spawned' | 'command', command?: string } } — writeScope/acceptance are caller-fixed when given, otherwise Plan derives them; validator absent or { kind: 'spawned' } is today's behavior (the spawned fresh judge), { kind: 'command', command } brokers to the external judge command.
Returns: { verdict: PASS|FAIL|NOT_PROVEN, verdictPath, intentDigest, changedPaths, filesSummary, criteria: [{ criterion, result, evidence }] } (plus error when a stage died; filesSummary is the implementer's caller-facing note — it never reaches the validator).
Workflow({ name: 'rpi', args: {
intent: 'ao doctor should exit non-zero and name the missing hook when the pre-push hook is absent',
writeScope: ['cli/internal/doctor/**'],
}})