initial: pi-setup workspace with skill + extension plan

This commit is contained in:
张宗平
2026-06-10 17:30:43 +08:00
commit 49e581247b
158 changed files with 28777 additions and 0 deletions
+503
View File
@@ -0,0 +1,503 @@
---
name: evolve
description: Run autonomous improvement loops.
practices:
- lean-startup
- dora-metrics
- agile-manifesto
hexagonal_role: supporting
consumes:
- rpi
- goals
- post-mortem
- compile
produces:
- git-changes
- goals-fitness-delta
context_rel:
- kind: customer-of
with: rpi
skill_api_version: 1
user-invocable: true
context:
window: fork
intent:
mode: task
sections:
exclude:
- HISTORY
intel_scope: full
metadata:
tier: execution
dependencies:
- rpi
- post-mortem
- compile
triggers:
- evolve
- improve everything
- autonomous improvement
- run until done
- postmortem and continue
- analyze repo and keep going
output_contract: code changes, GOALS.md fitness deltas
---
# /evolve — Goal-Driven Compounding Loop
> **Cross-vendor analog:** Anthropic Managed Agents Outcomes (May 2026). Both close the loop "agent runs → grader scores against a rubric → agent retries"; AgentOps does it locally against any model.
> Measure what's wrong. Fix the worst thing. Measure again. Compound.
**The loop runs as this skill (skills-are-the-runtime).** `evolve` selects work
and invokes complete `/rpi --auto` cycles — that *is* the loop. `evolve` (and
`ao rpi loop --supervisor`) are terminal-native **wrapper commands** for humans or
non-skill runtimes, not the default expression of the loop; they reuse the same v2
RPI loop engine. (The substrate dispatches the whole `evolve` skill loop as one
unit; it never drives the loop's insides. The `evolve`/`ao rpi` CLI wrappers are
being retired — ag-iowf.)
**Operator cadence:** post-mortem finished work, analyze the current repo state,
select or create the next highest-value work item, let `rpi` handle research,
planning, pre-mortem, implementation, and validation, then harvest follow-ups
and repeat until a kill switch, max-cycle cap, regression breaker, or real
dormancy stops the run.
Always-on autonomous loop over `rpi`. Work selection order:
1. **Harvested `.agents/rpi/next-work.jsonl` work** (freshest concrete follow-up)
2. **Open ready beads work** (`bd ready`)
3. **Failing goals and directive gaps** (`ao goals measure`)
4. **Testing improvements** (missing/thin coverage, missing regression tests)
5. **Validation tightening and bug-hunt passes** (gates, audits, bug sweeps)
6. **Complexity / TODO / FIXME / drift / dead code / stale docs / stale research mining**
7. **Concrete feature suggestions** derived from repo purpose when no sharper work exists
**Work generators** that feed the selection ladder (auto-invoked, skip with `--no-lifecycle`):
- `Skill(skill="test", args="coverage")` → files with <40% coverage become queue items (Step 3.4)
- `Skill(skill="refactor", args="--sweep all --dry-run")` → functions with CC > 20 become queue items (Step 3.6)
- `Skill(skill="deps", args="audit")` → deps with CVSS >= 7.0 or 2+ major versions behind become queue items (Step 3.5)
- `Skill(skill="perf", args="profile --quick")` → perf findings become queue items when hot paths detected (Step 3.5)
**Dormancy is last resort.** Empty current queues mean "run the generator layers", not "stop". Only go dormant after the queue layers and generator layers come up empty across multiple consecutive passes.
**Live skill edit immune system:** if an evolve cycle edits
`skills/<slug>/SKILL.md`, run
`ao skills edit seal --skill <slug> --actor "${AGENT_NAME:-agent}"` before the
cycle hands off. The seal creates the rollback commit and records the
`Skill-Edit` trailers used by the daily digest. Critical skills listed in
`docs/contracts/critical-skills.txt` reject unattended edits; use
`--allow-critical` only when Bo is supervising that critical edit.
```bash
/evolve # Run until kill switch, max-cycles, or real dormancy
/evolve --max-cycles=5 # Cap at 5 cycles
/evolve --dry-run # Show what would be worked on, don't execute
/evolve --beads-only # Skip goals measurement, work beads backlog only
/evolve --quality # Quality-first mode: prioritize post-mortem findings
/evolve --quality --max-cycles=10 # Quality mode with cycle cap
/evolve --compile # Mine → Defrag warmup before first cycle
/evolve --compile --max-cycles=5 # Warm knowledge base then run 5 cycles
/evolve --test-first # Default strict-quality /rpi execution path
/evolve --no-test-first # Explicit opt-out from test-first mode
```
## Delineation vs Nightly Knowledge Compounding
| Lane | Runs | Mutates code? | Mutates corpus? | Outer loop? | Budget |
|------|------|---------------|-----------------|-------------|--------|
| `$curate --mode=dream` | nightly, private local | **No** | **Yes (heavy)** | **Yes (convergence)** | wall-clock + plateau |
| `evolve` | daytime, operator-driven | Yes (via `rpi`) | Yes (light) | Yes | cycle cap |
**The old dream skill is retired**; out-of-session compounding moved to Gas City and the current skill surface is `$curate --mode=dream`. `/evolve` owns the live daytime code-compounding lane. Both still share the fitness-measurement substrate via `corpus.Compute` / `ao goals measure`.
## Flags
| Flag | Default | Description |
|------|---------|-------------|
| `--max-cycles=N` | unlimited | Stop after `N` completed cycles |
| `--dry-run` | off | Show planned cycle actions without executing |
| `--beads-only` | off | Skip goal measurement and run backlog-only selection |
| `--skip-baseline` | off | Skip first-run baseline snapshot |
| `--quality` | off | Prioritize harvested post-mortem findings |
| `--compile` | off | Run `ao mine` + `ao defrag` warmup before cycle 1 |
| `--test-first` | on | Pass strict-quality defaults through to `rpi` |
| `--no-test-first` | off | Explicitly disable test-first passthrough to `rpi` |
| `--no-lifecycle` | off | Skip lifecycle work generators in Steps 3.4-3.6 (/test, /deps, /perf, /refactor). Falls back to manual scanning. |
| `--mode=burst\|loop` | burst | Operator-loop; STOP refused. [loop-mode.md](references/loop-mode.md). |
## Execution Steps
**YOU MUST EXECUTE THIS WORKFLOW. Do not just describe it.**
**FULLY AUTONOMOUS.** Read `references/autonomous-execution.md`. Every `rpi` uses `--auto`. Do NOT ask the user anything. Each cycle = complete 3-phase `rpi` run.
For broad AgentOps 3.0 domain evolution across skills, CLI, hooks, docs, tests,
beads, and knowledge, first read
[references/domain-evolution-bootstrap.md](references/domain-evolution-bootstrap.md).
It supplies the BDD/DDD/Hexagonal/TDD/XP control surface and the clean-room
skill-factory guardrails.
### Step 0: Setup
**Stale-checkout survey guard (run FIRST).** Before any tree-reading survey: `git fetch origin && git status -sb`. If the checkout is behind/diverged AND it is a throwaway orchestration tree with no un-pushed work, `git reset --hard origin/main`. **BAN `git pull --rebase` on the survey path** — it silently no-ops against a diverged local `main`, so merged files appear "missing" and the survey investigates already-merged work.
```bash
git fetch origin && git status -sb # survey guard — never `git pull --rebase` here
mkdir -p .agents/evolve
ao corpus inject --query "autonomous improvement cycle" --limit 5 2>/dev/null || true
bash scripts/evolve-update-session-state.sh 2>/dev/null || true # refresh derived idle_streak + mode_repeat_streak
```
`ao corpus inject` routes through the typed BC1 `CorpusReaderPort`
(`cli/cmd/ao/corpus_reader_adapter.go`, cycle 112 productionCorpusReader),
emitting one ranked `ports.CorpusItem` JSON record per line from
`.agents/learnings/` by default. This closes soc-y5vh.1 — Step 0 prior-knowledge
retrieval is now load-bearing on the typed port, not an untyped `ao lookup`
shell-out.
**Apply retrieved knowledge:** If learnings are returned, check each for applicability to the current improvement cycle. For applicable learnings, cite by filename and record: `ao metrics cite "<path>" --type applied 2>/dev/null || true`
**Prior-failure injection (mandatory):** read the last 3 entries of `.agents/evolve/cycle-history.jsonl`. For any with `gate` containing `FAIL|FAILED|BLOCKED`, extract failure-surface keywords (`registry|bats|markdown|supergate|canary|coverage|toolchain`) and search `.agents/learnings/` for matching learnings. Print the top matches before work selection. Without this read path, the loop accumulates write-only ledgers and re-derives lessons each cycle. See `references/convergence-mechanics.md` for the full recipe.
Before cycle recovery, load the repo execution profile contract when it exists. The repo execution profile is the source for repo policy; the user prompt should mostly supply mission/objective, not restate startup reads, validation bundle, tracker wrapper rules, or `definition_of_done`.
- Locate `docs/contracts/repo-execution-profile.md` and `docs/contracts/repo-execution-profile.schema.json`.
- Read the ordered `startup_reads` and bootstrap from those repo paths before selecting work.
- Cache repo `validation_commands`, `tracker_commands`, and `definition_of_done` into session state.
- If the repo execution profile is present but missing required fields, stop or downgrade with an explicit warning before cycle 1. Do not silently invent repo policy.
- Read operating-doctrine ADRs (`docs/adr/` or `docs/decisions/`) when present — intent the loop re-reads each cycle: only operator markers stop the loop; the bead queue is a hypothesis re-confirmed against the goal, not spec; file-a-bead when a candidate is architecture disguised as bounded work.
Then load the repo-local autodev program contract when it exists. The execution profile remains the repo bootstrap and landing-policy layer; `PROGRAM.md` or `AUTODEV.md` is the repo-local execution layer for the current improvement loop.
- Locate `PROGRAM.md` and `AUTODEV.md`. `PROGRAM.md` takes precedence.
- Read the resolved program before cycle recovery and cache `program_path`, `mutable_scope`, `immutable_scope`, `validation_commands`, `decision_policy`, and `stop_conditions` into session state.
- If the program file exists but is structurally invalid, stop or downgrade with an explicit warning before cycle 1. Do not silently ignore a broken operator contract.
- When a program contract exists, prefer work that can land wholly inside mutable scope. Do not silently widen scope around immutable files.
Recover cycle number, generator streaks, and the last claimed work item from disk (survives context compaction). Initialize `CYCLE` from `cycle-history.jsonl`, recover `IDLE_STREAK`, `GENERATOR_EMPTY_STREAK`, `LAST_SELECTED_SOURCE`, and `CLAIMED_WORK_REF` from `session-state.json`.
**Circuit breakers:** Time-based (60 min no productive work).
**Oscillation quarantine:** Pre-populate quarantine list from cycle history (scan for goals with 3+ improved-to-fail transitions). See `references/oscillation.md`.
Parse flags: `--max-cycles=N` (default unlimited), `--dry-run`, `--beads-only`, `--skip-baseline`, `--quality`, `--compile`.
Track cycle-level execution state:
```text
evolve_state = {
cycle: <current cycle number>,
mode: <standard|quality|beads-only>,
test_first: <true by default; false only when --no-test-first>,
repo_profile_path: <docs/contracts/repo-execution-profile.md or null>,
startup_reads: <ordered repo bootstrap paths>,
validation_commands: <ordered repo validation bundle>,
tracker_commands: <repo tracker shell wrappers>,
definition_of_done: <repo stop predicates>,
program_path: <PROGRAM.md|AUTODEV.md or null>,
program_mutable_scope: <declared mutable paths/globs>,
program_immutable_scope: <declared immutable paths/globs>,
program_validation_commands: <ordered program validation bundle>,
program_decision_policy: <ordered keep/revert rules>,
program_stop_conditions: <ordered cycle done criteria>,
generator_empty_streak: <consecutive passes where all generator layers returned nothing>,
last_selected_source: <harvested|beads|goal|directive|testing|validation|bug-hunt|drift|feature>,
claimed_work: <null or work reference being worked>,
queue_refresh_count: <incremented after every /rpi cycle>
}
```
Persist `evolve_state` to `.agents/evolve/session-state.json` at each cycle boundary, after work claims, after release/finalize, and during teardown. `cycle-history.jsonl` remains the canonical cycle ledger; `session-state.json` carries resume-only state that has not yet earned a committed cycle entry. Both files are **local-only** (the nested `.agents/.gitignore` denies all paths) — record durable milestones in commit messages too. See `references/cycle-history.md` for full local-only semantics.
### Step 0.2: Compile Warmup (--compile only)
Skip if `--compile` was not passed or if `--dry-run`. Read `references/knowledge-loop-integration.md` for the full warmup procedure (mine + defrag + signal notes).
### Step 0.5: Baseline (first run only)
Skip if `--skip-baseline` or `--beads-only` or baseline already exists. Read `references/fitness-scoring.md` for the baseline capture procedure.
### Step 1: Kill Switch Check
Run at the TOP of every cycle:
```bash
CYCLE_START_SHA=$(git rev-parse HEAD)
# Mechanical pre-cycle gate (soc-sfjx): markers (KILL/STOP/DORMANT/HANDOFF with
# TTL + soc-5qit non-sticky semantics), goal-regression, and prior-cycle-FAIL.
# This is a SCRIPT the loop MUST run, not prose it can skip — externalized from
# the old inline block so the kill-switch + revert-on-red are enforced, not
# advisory. Adapted from the mt-olympus unbounded-evolve substrate.
if [ -x scripts/evolve/halt-check.sh ]; then
if ! HALT_OUT=$(bash scripts/evolve/halt-check.sh --json); then
REASON=$(printf '%s' "$HALT_OUT" | jq -r '.halt_reason // "unknown"')
if [ "$REASON" = "prior_cycle_fail" ]; then
export EVOLVE_RESTORATIVE=1 # not terminal: Step 1.5 restricts scope to CI-red reduction
else
echo "halt: $REASON"; exit 0 # kill/user_halt/dormant/goal_regression -> stop this cycle
fi
fi
else
# Fallback for repos without the substrate: minimal inline marker check.
for m in "$HOME/.config/evolve/KILL" .agents/evolve/STOP; do [ -f "$m" ] && { echo "halt: $m"; exit 0; }; done
[ -f .agents/evolve/DORMANT ] && { [ "$(bd ready --json 2>/dev/null | jq -r 'length // 0')" -gt 0 ] && rm -f .agents/evolve/DORMANT || { echo dormant; exit 0; }; }
[ -f .agents/evolve/HANDOFF ] && rm -f .agents/evolve/HANDOFF
fi
```
**Agile-first dormancy (soc-5qit):** `DORMANT` is NEVER sticky while ready beads exist — `halt-check.sh` auto-clears it when `bd ready`/harvested work exists. KILL/STOP honor `EVOLVE_KILL_TTL_DAYS` (default 7); stale markers are surfaced and bypassed. `goal_regression` (latest cycle report `goals_passing_after < before`) halts the loop for operator attention. Heavy-context sessions write non-sticky HANDOFF; the next fire clears it and resumes. The gate is mechanical: see `scripts/evolve/halt-check.sh`.
### Step 1.5: Healing-first classifier
Before fitness or work selection, classify the cycle: `ao ci recent --limit 1 2>/dev/null | jq -r '.Conclusion // empty'` (typed BC2 `CIStatusPort`, soc-y5vh.2). If the last push CI was `failure`, this cycle is **restorative-only** — Step 3 takes only CI-red-reducing work (bug-type harvested items, gate-failure-fix beads, generator bug output); no promotions, features, or new-shape work until green. A `gate=FAIL` in cycle-history.jsonl auto-triggers this for cycle N+1 (and `halt-check.sh` surfaces it as `prior_cycle_fail`). See `references/convergence-mechanics.md`.
**Convergence check:** evaluate the STOP predicate via the typed BC3 `ConvergenceCheckPort``ao loop converged --green-streak <n> --unconsumed-high-medium <n> [--fitness-baseline]` (soc-y5vh.8). Branch on `.converged` (default: CI green streak ≥ 3, HIGH+MEDIUM next-work ≤ 1, fitness baseline captured); if true, emit teardown and do NOT re-arm wakeup.
### Step 2: Measure Fitness
Skip if `--beads-only`. Run `scripts/evolve-measure-fitness.sh` to produce a rolling fitness snapshot at `.agents/evolve/fitness-latest.json`. Read `references/fitness-scoring.md` for the full measurement procedure, baseline capture, and post-cycle regression detection.
### Step 3: Select Work
Selection is a ladder, not a one-shot check — after every productive cycle, return to the TOP and re-read the queue before considering dormancy. **Read [references/work-selection-ladder.md](references/work-selection-ladder.md) for the full per-rung procedure** (programmatic `ao loop next-work` recommendation, scope filter, metronome gate, the generator rungs with their code blocks, the `--quality` inverted cascade, and the dormancy hard-gate).
Ladder order (standard mode):
- **3.0 Scope filter** (soc-5qit) — split-or-defer oversized candidates via scout-mode; never bail.
- **3.1 Harvested** — `.agents/rpi/next-work.jsonl`, highest-value unconsumed.
- **3.2 Open ready beads** — `bd ready`, highest priority.
- **3.3 Failing goals + directive gaps** — skip if `--beads-only`; skip quarantined oscillators.
- **3.43.6 Generators** — `/test` coverage, `/deps`+`/perf`, `/refactor`; findings → beads/queue items.
- **3.7 Feature suggestions** grounded in repo purpose.
`--quality` inverts the top (findings before goals/directives). The metronome gate blocks a rung that would repeat the trailing run's `mode` (streak ≥3).
**Agile invariant (soc-5qit):** `bd ready ≥ 1` ⇒ the loop NEVER writes DORMANT and NEVER exits. The only path to DORMANT is a fully empty backlog + dry generators (3 passes). Context exhaustion → HANDOFF, not DORMANT. Under loop mode, `write-stop-marker` refuses → log blocked + operator-wait (ADR-0007).
If `--dry-run`: report what would be worked on and go to Teardown.
### Step 4: Execute
Primary engine: `rpi` for implementation-quality work (all 3 phases mandatory). `/implement` or `/crank` only when a bead has execution-ready scope.
If a repo-local `PROGRAM.md` contract is active, `rpi` will load it automatically. `evolve` must compose with that behavior, not bypass it:
- Do not select work that is obviously outside mutable scope.
- If a bead or goal would require edits under immutable scope, escalate it or convert it into durable follow-up work instead of launching `rpi`.
- When work is plausibly in scope but still uncertain, let `rpi` discovery validate the fit and surface a scope escape explicitly.
For a **harvested item, failing goal, directive gap, testing improvement, validation tightening task, bug-hunt result, drift finding, or feature suggestion**:
```
Invoke /rpi "{normalized work title}" --auto --max-cycles=1
```
For a **beads issue**:
```
Prefer: /rpi "Land {issue_id}: {title}" --auto --max-cycles=1
Fallback: /implement {issue_id}
```
Or for an epic with children: `Invoke /crank {epic_id}`.
If Step 3 created durable work instead of executing it immediately, re-enter Step 3 and let the newly-created bead item win through the normal selection order.
**Mechanical-batch hint:** when the implementation phase identifies > 20 uniform per-file edits, prefer a script (`awk`/`sed`/`for f in $candidates`) over N tool-level Edit calls. See `references/mechanical-batches.md` for the decision rule and the script-first pattern.
**Pre-flight schema check (architectural migrations):** if the selected work is a port/adapter migration that rewires an existing consumer, BEFORE invoking `rpi`, sample two representative consumer call sites and compare field-use against the target port surface. If the consumer reads > 20% more fields than the port projects, abort the migration cycle and convert the work into a port-widening cycle instead. The phase-2 narrowness post-mortem (`docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md`) is the encoded lesson; see `references/pre-flight-schema-check.md` for the procedure.
**Operator-shape carve-out:** `AskUserQuestion` is permitted ONLY for shape decisions affecting > 50 files OR a schema/contract surface (carrier choice, struct-field shape, frontmatter-key shape). See `references/autonomous-execution.md` for the bound on this exception.
### Step 4.5: Source-surface detection (pre-gate sync)
Before invoking the regression gate, sync downstream artifacts when the staged diff touches binary or embedded surfaces:
- `cli/**/*.go` changed → `cd cli && make build && go install ./cmd/ao`
- `skills/**` or `hooks/**` changed → `cd cli && make sync-hooks`
- `skills-codex/**` changed → `bash scripts/regen-codex-hashes.sh`
Without these, the gate fails on stale-binary or embedded-drift errors that look like real regressions. See `references/gate-hygiene.md` for the detection recipe.
**Adding or modifying a skill?** A skill touches **six derived surfaces** (registry.json SKU catalog, skill-domain-map, context-map, skill counts + the hand-added `SKILL-TIERS.md` row, codex twin, narrative counts) — regenerate them in one shot via `scripts/regen-all.sh` + the codex/count steps, never piecemeal. The most-missed is `registry.json` (stale → `contracts-sync` + `correctness(ubuntu)` fail together). See [references/new-skill-landing.md](references/new-skill-landing.md) for the full procedure, then [references/gate-hygiene.md](references/gate-hygiene.md) for the pre-push diff-scope check + pre-existing-vs-mine red triage.
### Step 5: Regression Gate
After execution, run the project build+test bundle. If the repo execution profile declared `validation_commands`, run them. If a repo-local program contract exists, run its `validation_commands` too, de-duplicated and in declared order after the repo bootstrap checks. Also check `if [ -f scripts/check-wiring-closure.sh ]; then bash scripts/check-wiring-closure.sh; fi`.
Use the program contract's `decision_policy` as the first keep/revert rule set for the cycle:
- if the cycle breached immutable scope, treat it as regressed
- if program validation commands fail, treat it as regressed
- if the decision policy declares a revert rule that fired, revert before consuming claimed work or advancing the queue
Treat program `stop_conditions` as per-cycle done criteria. Do not mark claimed work consumed, completed, or productive until both the stop conditions and the regression gate pass.
If not `--beads-only`, re-measure fitness to `fitness-latest-post.json` and detect regressions. The AgentOps CLI is required for fitness measurement. Read `references/fitness-scoring.md` for the full measurement, regression detection, and revert procedure.
**Gate output parsing:** trust the structural marker `^.*Pass [0-9]+: (FAILED|BLOCKED)` over the trailing status line — the trailing line conflates blocking and advisory results. See `references/gate-hygiene.md`.
Work finalization after the regression gate: claim it first, then keep `consumed: false` until the /rpi cycle succeeds. After the cycle's `/post-mortem` finishes, immediately re-read `.agents/rpi/next-work.jsonl` before selecting the next item. Read `references/knowledge-loop-integration.md` for full claim/release semantics.
### Step 6: Log Cycle + Commit
Two paths: productive cycles get committed, idle cycles are local-only.
**PRODUCTIVE cycles** (result is improved, regressed, or harvested): compute quality score (if `--quality`), log via `scripts/evolve-log-cycle.sh`, commit if real changes exist. See `references/quality-mode.md` for scoring.
**IDLE cycles** (nothing found even after generator layers): log via `evolve-log-cycle.sh` with `--result "unchanged"`. No git add, no commit.
**Record the XP/BDD/TDD trace.** When a cycle worked a product or goal-backed gap, pass `--trace-json` to `evolve-log-cycle.sh` (or `ao loop append`) so the cycle records the continuous-evolution kernel — goal hypothesis → selected gap → Gherkin scenario → first failing proof → red/green evidence → refactor note → validation evidence → ratchet action → goal reshape — and a reviewer can reconstruct the cycle without the transcript. A trivial one-shot cycle records a `trace.exemption_reason` instead of carrying false BDD/TDD ceremony. Trace completeness is advisory, never a gate. See `references/cycle-history.md` ("XP/BDD/TDD Evidence Trace").
### Step 7: Loop or Stop
```bash
while true; do
# Step 1 .. Step 6
# Stop ONLY if: operator override (KILL/STOP), max-cycles, regression-breaker,
# or genuine stagnation (bd ready=0 AND harvested=0 AND failing-goals=0 AND
# generators dry across 3 passes). Context exhaustion is NOT a stop — it's a
# session-handoff signal (HANDOFF marker) that the next cron-fire clears.
CYCLE=$((CYCLE + 1))
done
```
**Stop reasons (soc-5qit, ALL require genuine reason — never just context size):**
1. **KILL/STOP file present** — operator override.
2. **`--max-cycles=N` cap reached**.
3. **Genuine stagnation**`bd ready=0 AND harvested-unconsumed=0 AND failing-goals=0 AND GENERATOR_EMPTY_STREAK>=2 AND IDLE_STREAK>=2`. Writes DORMANT, which auto-clears in Step 1 the moment `bd create` adds a new ready bead.
4. **Regression breaker after a revert**.
**Context exhaustion is NOT a stop (soc-5qit).** Heavy-context sessions write `.agents/evolve/HANDOFF` (non-sticky), log `result: "context-handoff"` to cycle-history, and exit the turn cleanly. The next cron-fire (compacted/fresh context) clears HANDOFF in Step 1 and resumes. The loop is continuous across compactions; never write DORMANT for context size. See `references/context-budget.md`.
**Mandatory checkpoint #6 — session-PR threshold (NOT terminal, gates next cycle):** at `session_pr_count >= 5` (soc-waxr default), invoke `/post-mortem --deep`, wait for verdict file. PASS → continue. WARN → continue with caveat in next cycle's `notes`. FAIL or non-convergence → write STOP. Agent MUST NOT self-grade or self-write STOP. Full procedure in `references/postmortem-checkpoint.md` (soc-n75z).
**Self-perpetuation modes:** the terminal-native `evolve` loop and the Claude-Code-harness `ScheduleWakeup` end-of-turn pattern are duals — both drive Step 1..Step 7 repeatedly against the same persisted state. See `references/autonomous-execution.md` for the ScheduleWakeup cadence and the rule that hard stops must NOT re-arm.
Push only when productive work has accumulated:
```bash
if [ $((PRODUCTIVE_THIS_SESSION % 5)) -eq 0 ] && [ "$PRODUCTIVE_THIS_SESSION" -gt 0 ]; then
git push
fi
```
**Drive to completion (orchestrator-merge model, soc-2drk).** Where the repo requires PRs (branch protection rejects direct `main` pushes), a productive cycle does not stop at "PR opened" — the loop is the orchestrator that drives each bead to *merged*. Ship the bead from its per-bead worktree as a PR (trailers `Closes-scenario` / `Bounded-context` / `Evidence`), wait for CI, and **squash-merge to main yourself once CI is green** (`gh pr merge <N> --squash --admin`), then `bd close` the bead and remove the worktree. **Green CI is the only merge gate** — on a quality/test red, fix-and-repush or revert; never merge red. The loop may dispatch sub-agents to implement and drives their PRs to merge too. The operator stays *on* the loop (intent + STOP marker), not *in* it (per-PR approval). This **supersedes "operator is the merge gate"** for the autonomous loop — see [ADR-0008](../../docs/adr/ADR-0008-evolve-intelligent-agile-operating-model.md).
**Confirmed-MERGED gate before `bd close` (hard, not advisory).** Re-confirm `gh pr view <N> --json state -q .state` returns `MERGED` *before* `bd close` — never close on a `gh pr merge` exit code, a log line, or a batch `bd --json` query (those flake to null/0). **Close a parent epic ONLY after every child PR is independently confirmed `MERGED`**; re-query per child first, and one non-merged child aborts the epic close. (Caught two premature epic-closes in the 2026-05-31 crank session — this gate is the governance checkpoint, applied here too.) Enforce via the committed `scripts/reconcile-pr.sh <pr> <bead> [--epic <epic>]` + `scripts/check-epic-children-closed.sh <epic>` (hermetic-tested in `tests/scripts/`), not by hand.
### Teardown
Read `references/knowledge-loop-integration.md` for the full teardown learning extraction procedure (commit staged artifacts, run `/post-mortem`, push, report summary).
A teardown `/post-mortem` is a light-touch retrospective on session-end. It does NOT substitute for the mandatory threshold checkpoint (`references/postmortem-checkpoint.md`); that one is council-gated and edge-triggered at `session_pr_count >= 5`. Never write `.agents/evolve/STOP` as a substitute for the checkpoint's verdict file — STOP without a verdict is the 2026-05-20 anti-pattern (soc-n75z).
**Release-context teardown (MANDATORY when the loop ran on a release-shaped branch):**
When the current branch matches `release/*`, `v*-prep`, `v*-evolve-run`, or `v\d+\.\d+*`, the teardown report MUST NOT recommend `/release` as the next step. Instead, emit the explicit pre-release checklist below — the operator must run these AND confirm green before tagging:
```
## Pre-release checklist — REQUIRED before /release
The autonomous loop has stopped, but release-readiness gates have NOT been run
during cycles. The operator MUST run the following sequence and confirm green
before invoking /release. Do NOT skip any of these on the basis of "cycles
were green" — fast pre-push gate ≠ full pre-push gate; goals-measure ≠
release readiness.
[ ] 1. Regenerate ALL derived surfaces if any cobra command/flag changed:
bash scripts/regen-all.sh # COMMANDS.md, registry.json, maps
# ADDING an `ao` command also needs the 2 surfaces regen-all only WARNS
# about: cli/cmd/ao/cobra_commands_test.go expectedCmds (x2 lists) +
# the cli-command-surface heading counts in
# evals/agentops-core/fixtures/cli-command-surface-smoke.sh AND
# evals/agentops-core/cli-command-surface-matrix.json (top/sub/all).
# Run the smoke fixture to read the exact new counts. (ag-jy12 will
# automate this.) Full procedure in
# [references/ao-command-landing.md](references/ao-command-landing.md)
git diff cli/docs/COMMANDS.md registry.json # commit if non-empty
[ ] 2. Run the FULL pre-push gate (NOT --fast) with fail-fast OFF, so a
PRE-EXISTING failure (e.g. corpus-freshness) cannot mask your own
regressions by stopping the run early:
PRE_PUSH_FAIL_FAST=false bash scripts/pre-push-gate.sh
[ ] 3. Run the release-readiness gate:
bash scripts/ci-local-release.sh
[ ] 4. (Recommended) Smoke /evolve with the new typed read paths if BC port
wire-ups changed:
/evolve --quick --max-cycles=1 --dry-run
Only after [1][3] pass: /release <version>
If any check fails, fix the issue, re-run all four, then ship.
```
The handoff artifact (e.g., `.agents/runs/<release>/READY-TO-TAG.md`) MUST contain this checklist verbatim, unchecked, when written by the loop. The operator checks the boxes as they complete each gate; "ready to tag" means the boxes are checked, not that the loop ran cleanly.
**Rationale:** cycles 170-183 of the v2.41-evolve-run shipped clean code, all unit/integration tests green, `ao goals measure` 0/30 failing for three consecutive cycles — but the loop never ran the full pre-push gate, `ci-local-release.sh`, or `generate-cli-reference.sh`. The latter was load-bearing (the branch removed a CLI flag). Per-cycle `--fast` is a smoke test, not release readiness. Operator caught the gap; this checklist makes it mechanical.
## Examples
**User says:** `/evolve --max-cycles=5`
**What happens:** Evolve re-enters the full selection ladder after every `rpi` cycle and runs producer layers instead of idling on empty queues.
**User says:** `/evolve --beads-only`
**What happens:** Evolve skips goals measurement and works through `bd ready` backlog.
**User says:** `/evolve --dry-run`
**What happens:** Evolve shows what would be worked on without executing.
**User says:** `/evolve --compile`
**What happens:** Evolve runs `ao mine` + `ao defrag` at session start to surface fresh signal (orphaned research, code hotspots, oscillating goals) before the first evolve cycle. Use before a long autonomous run or after a burst of development activity.
**User says:** `evolve`
**What happens:** See `references/examples.md` for a worked overnight flow that moves through beads -> harvested work -> goals -> testing -> bug hunt -> feature suggestion before dormancy is considered.
See `references/examples.md` for detailed walkthroughs.
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Loop exits immediately | Remove `~/.config/evolve/KILL` or `.agents/evolve/STOP` |
| Stagnation after repeated empty passes | Queue layers and producer layers were empty across multiple passes — dormancy is the fallback outcome |
| `ao goals measure` hangs | Use `--timeout 30 --total-timeout 75` or `--beads-only` to skip |
| Regression gate reverts | Review reverted changes, narrow scope, re-run; claimed work items must be released back to available state |
See `references/cycle-history.md` for advanced troubleshooting.
## References
- [references/evolve.feature](references/evolve.feature) — Executable spec: gated cycles, ladder, bounded slice, never-self-halt
- [references/long-loop-discipline.md](references/long-loop-discipline.md) — Disk-is-truth axiom
- [references/artifacts.md](references/artifacts.md) — Generated files registry
- [references/autonomous-execution.md](references/autonomous-execution.md) — Autonomous-loop rules + operator-shape carve-out
- [references/snapshot-pattern-for-long-cycle-gates.md](references/snapshot-pattern-for-long-cycle-gates.md) — Snapshot pattern for long-cycle gates
- [references/compounding.md](references/compounding.md) — Knowledge flywheel and work harvesting
- [references/context-budget.md](references/context-budget.md) — `CONTEXT_BUDGET_EXHAUSTED` as a third stop reason and handoff protocol
- [references/convergence-mechanics.md](references/convergence-mechanics.md) — Read-path mechanisms for compounding
- [references/domain-evolution-bootstrap.md](references/domain-evolution-bootstrap.md) — BDD/DDD/Hexagonal/TDD/XP control surface for skill/domain evolution
- [references/cycle-history.md](references/cycle-history.md) — JSONL format, recovery protocol, kill switch
- [references/examples.md](references/examples.md) — Detailed usage examples
- [references/fitness-scoring.md](references/fitness-scoring.md) — Baseline capture, regression detection, revert procedure
- [references/gate-hygiene.md](references/gate-hygiene.md) — Pre-gate source-surface detection, structural gate-output parsing, pre-push diff-scope check, pre-existing-vs-mine red triage
- [references/new-skill-landing.md](references/new-skill-landing.md) — The six derived surfaces a new/modified skill must regenerate in one shot to stay one-shot-green
- [references/ao-command-landing.md](references/ao-command-landing.md) — The surfaces a new/renamed `ao` command must regenerate (cobra expectedCmds x2 + cli-command-surface counts that regen-all only WARNS about)
- [references/goals-schema.md](references/goals-schema.md) — GOALS.yaml format and continuous metrics
- [references/knowledge-loop-integration.md](references/knowledge-loop-integration.md) — Claim/release semantics and harvest re-read
- [references/mechanical-batches.md](references/mechanical-batches.md) — Script-first vs per-file Edit for > 20-file uniform batches
- [references/metronome-gate.md](references/metronome-gate.md) — Cross-cycle same-mode-repeat blocker
- [references/oscillation.md](references/oscillation.md) — Oscillation detection and quarantine
- [references/pre-flight-schema-check.md](references/pre-flight-schema-check.md) — Cheap field-fit check before architectural migration cycles
- [references/postmortem-checkpoint.md](references/postmortem-checkpoint.md) — Stop reason #6: session-PR post-mortem checkpoint (soc-n75z)
- [references/parallel-execution.md](references/parallel-execution.md) — Parallel /swarm architecture
- [references/quality-mode.md](references/quality-mode.md) — Quality-first mode: scoring, priority cascade, artifacts
- [references/scout-mode.md](references/scout-mode.md) — Scout-mode as a first-class cycle result; scope filter procedure
- [references/teardown.md](references/teardown.md) — Trajectory computation and session summary
## See Also
- `skills/curate/SKILL.md` — the knowledge compounder; `--mode=harvest` gathers artifacts and `--mode=dream` runs the compounding loop overnight
- `skills/rpi/SKILL.md` — Full lifecycle orchestrator (called per cycle)
- `skills/crank/SKILL.md` — Epic execution (called for beads epics)
- `docs/contracts/autodev-program.md` — Repo-local operational contract for bounded autonomous development
- `GOALS.yaml` — Fitness goals for this repo
- [test](../test/SKILL.md) — Test generation and coverage analysis
- [refactor](../refactor/SKILL.md) — Safe, verified refactoring
- [deps](../deps/SKILL.md) — Dependency audit and vulnerability scanning
- [perf](../perf/SKILL.md) — Performance profiling and benchmarking
@@ -0,0 +1,52 @@
# AO-Command Landing — the surfaces a new `ao` command must regenerate
Adding (or renaming/removing) an `ao` command or subcommand touches more than the
generated CLI reference. `scripts/regen-all.sh` regenerates the bulk of the derived
surfaces, but it only **WARNS** about two that it cannot regenerate for you — those
are the dominant fix-and-repush cause when landing a command. Regenerate them in the
**same pass** before opening the PR, not piecemeal.
This is the command-surface parallel of [new-skill-landing.md](new-skill-landing.md)
(the six derived surfaces a *skill* edit must regenerate).
## The shortcut
`scripts/regen-all.sh` regenerates `cli/docs/COMMANDS.md`, `registry.json`, the
skill/context maps, the embedded skills, and the cli-surface inventory in one pass —
prefer it over running the individual generators. Then hand-fix the two surfaces it
only warns about (below), and finally run `scripts/regen-all.sh --check`.
## The two surfaces regen-all only WARNS about
1. **Cobra `expectedCmds` (x2 lists)**`cli/cmd/ao/cobra_commands_test.go` hardcodes
the registered-command list **twice**: once in `TestCobraCommandTreeRegistration`
and once in `TestCobraExpectedCmdsMatchRegistration` (the second comment even says
"Same list as TestCobraCommandTreeRegistration"). Both `expectedCmds` slices must
gain the new top-level command name, or the second test fails with
"registered command %q is not in expectedCmds — add it to keep the list in sync".
2. **`cli-command-surface` heading counts** — the generated reference's `ao` heading
counts (`top`/`sub`/`all`) are asserted in **two** offline canaries that must move
together:
- `evals/agentops-core/fixtures/cli-command-surface-smoke.sh` — the
`top_count`/`sub_count`/`all_count` literals (currently `76`/`192`/`268`).
- `evals/agentops-core/cli-command-surface-matrix.json` — the public canary that
enumerates every documented command/subcommand help page.
**Run the smoke fixture to read the exact new counts** rather than computing them by
hand:
```bash
bash evals/agentops-core/fixtures/cli-command-surface-smoke.sh
# On a count mismatch it prints: unexpected command heading counts: top=.. sub=.. all=..
# Update the literals in the fixture (and the matrix.json cases) to the printed values.
```
(Automating this regen is tracked as `ag-jy12`.)
## See also
- [new-skill-landing.md](new-skill-landing.md) — the six derived surfaces a *skill*
edit regenerates (the sibling discipline for the skills axis).
- [gate-hygiene.md](gate-hygiene.md) — pre-push diff-scope check + pre-existing-vs-mine
red triage that keeps a command-landing PR one-shot-green.
@@ -0,0 +1,37 @@
# /evolve Artifacts
## Committed to Git
| File | Purpose |
|------|---------|
| `GOALS.yaml` | Fitness goals (repo root) |
| `.agents/evolve/fitness-baselines/goals-<hash>/*.json` | Immutable baseline snapshots, one directory per GOALS.md/GOALS.yaml content hash |
| `.agents/evolve/cycle-history.jsonl` | Cycle outcomes log (includes commit SHAs) |
## Local Only (gitignored)
| File | Purpose |
|------|---------|
| `.agents/evolve/fitness-latest.json` | Pre-cycle fitness snapshot (rolling, overwritten each cycle) |
| `.agents/evolve/fitness-latest-post.json` | Post-cycle fitness snapshot (for regression comparison) |
| `.agents/evolve/session-state.json` | Resume-only state: generator streaks, last selected source, pending queue claim |
| `.agents/evolve/session-summary.md` | Session wrap-up |
| `.agents/evolve/session-fitness-delta.md` | Session fitness trajectory (baseline to final delta) |
| `.agents/evolve/STOP` | Local kill switch |
| `~/.config/evolve/KILL` | External kill switch |
## Removed (legacy)
These files are no longer generated. Older repos may have them in git history:
| File | Replacement |
|------|-------------|
| `.agents/evolve/fitness-{N}-pre.json` | `fitness-latest.json` (rolling) |
| `.agents/evolve/fitness-{N}-post.json` | `fitness-latest-post.json` (rolling) |
| `.agents/evolve/cycle-0-report.md` | Inlined into session-summary.md |
| `.agents/evolve/fitness-0-baseline.json` | `.agents/evolve/fitness-baselines/goals-<hash>/*.json` |
| `.agents/evolve/active-baseline.txt` | Current GOALS.md/GOALS.yaml hash resolves the active era directory |
| `.agents/evolve/baselines/index.jsonl` | No longer needed by the v2 era-hash baseline gate |
| `.agents/evolve/baselines/*.json` | `.agents/evolve/fitness-baselines/goals-<hash>/*.json` |
| `.agents/evolve/last-sweep-date` | No longer needed by the baseline gate |
| `.agents/evolve/KILLED.json` | Kill switch acknowledgment removed (STOP file is sufficient) |
@@ -0,0 +1,47 @@
# Evolve Autonomous Execution Rules
## Fully Autonomous
Evolve runs without human intervention from start to teardown. Every `/rpi` invocation uses `--auto`. Do NOT ask the user for confirmation, clarification, or approval at any point. Do NOT pause between cycles. Do NOT summarize and wait. The user's only touchpoint is the teardown report at the very end.
### Operator-shape carve-out
A single, bounded exception applies. `AskUserQuestion` is permitted ONLY when the cycle has identified a **shape decision** that affects > 50 files OR touches a schema/contract surface, and where picking wrong costs > 10 minutes of rework. Examples that qualify: declaration carrier shape across a 533-file pool, frontmatter-key vs `$comment`-key vs sidecar-file choice, struct-field addition to a shared contract type. Anything that fits inside an established shape continues to be fully autonomous. The carve-out exists because a 30-second user pick can unblock 300+ file edits — but it is narrow: ask once per shape decision, then resume the autonomous loop.
## ScheduleWakeup self-perpetuation (Claude-Code harness)
Inside the Claude-Code harness, `/evolve` (the terminal-native loop) is not the only self-perpetuation surface. Each /evolve cycle can call `ScheduleWakeup` at end-of-turn to fire the next cycle:
- Productive cycle (commit landed): `delaySeconds=270` keeps the prompt cache warm.
- Scout cycle: `delaySeconds=600` — bigger gap, no cache reload cost since context isn't reused.
- Idle cycle: `delaySeconds=1800` — coarse poll for fresh signal.
Hard stops MUST NOT call `ScheduleWakeup`: KILL/STOP files, dormancy reached, `CONTEXT_BUDGET_EXHAUSTED` (see `references/context-budget.md`), or `--max-cycles` cap hit. When dormancy fires (Step 3 hard-gate), `/evolve` writes `.agents/evolve/DORMANT` and Step 1 short-circuits on subsequent fires before any further tool calls — this is the operational enforcement of the "no ScheduleWakeup on dormancy" rule (see `references/cycle-history.md` for the marker semantics). The terminal-mode `/evolve` loop and the Claude-Code `ScheduleWakeup` loop are duals: both drive Step 1..Step 7 repeatedly, both honor the same kill switches and stop conditions, both persist resume state via `.agents/evolve/session-state.json`.
## Each Cycle = Complete /rpi Run
All 3 phases (discovery → implementation → validation). Never invoke a partial RPI. If a task is too large for one cycle, break it into smaller sub-tasks during discovery and let `/crank` handle the waves. Evolve's job is to keep the loop turning, not to micro-manage individual tasks.
## Break Large Work into Sub-RPI Cycles
When work selection identifies a massive task (7+ issues, multi-subsystem scope), decompose it during `/rpi`'s discovery phase into an epic with waves. One evolve cycle = one `/rpi` run = one complete lifecycle. If the epic is too large for a single session, `/rpi`'s built-in retry and `--from=` resume handle continuation.
## Respect Repo-Local Program Contracts
When `PROGRAM.md` or `AUTODEV.md` exists, treat it as a hard operational constraint for cycle selection and keep/revert decisions:
- prefer work that can plausibly land inside mutable scope
- do not intentionally select work that requires immutable-scope edits
- treat out-of-scope work as escalation or backlog material, not permission to widen scope in place
- let `/rpi` enforce the active program contract during execution, then use its validation and decision policy during the evolve regression gate
## Anti-Patterns (DO NOT)
| Anti-Pattern | Why It's Wrong | Correct Behavior |
|--------------|----------------|------------------|
| Ask the user anything during execution | Evolve is fully autonomous — questions break the loop | Make best judgment, report in teardown. Exception: shape decisions affecting > 50 files or schema/contract surfaces (see "Operator-shape carve-out" above) |
| Stop after one `/rpi` cycle and summarize | Evolve loops until kill switch, max-cycles, or dormancy | Increment cycle and re-enter Step 1 |
| Run `/rpi` without `--auto` | Non-auto `/rpi` has human gates that halt the loop | Always pass `--auto` to `/rpi` |
| Run partial `/rpi` (skip validation) | Each cycle must be a complete 3-phase lifecycle | Let `/rpi` run all 3 phases autonomously |
| Pause between cycles to explain progress | The user wants results, not narration | Log cycle results, immediately start next cycle |
| Treat "no queued work" as "stop" | Generator layers (testing, validation, drift, features) produce work | Run all generator layers before considering dormancy |
| Select work that obviously violates `PROGRAM.md` | Scope escape is a tracked outcome, not a license to widen the loop | Escalate or re-queue out-of-scope work and keep selection inside mutable scope |
@@ -0,0 +1,47 @@
# How Compounding Works
Two mechanisms feed the loop:
**1. Knowledge flywheel (each cycle is smarter):**
```
Session 1:
ao lookup --query "recent learnings" (nothing yet) → cycle runs blind
/rpi fixes test-pass-rate → post-mortem runs ao forge
Learnings extracted: "tests/skills/run-all.sh validates frontmatter"
Session 2:
ao lookup --query "recent learnings" (loads Session 1 learnings) → cycle knows about frontmatter validation
/rpi fixes doc-coverage → approach informed by prior learning
Learnings extracted: "references/ dirs need at least one .md file"
```
**2. Work harvesting (each cycle discovers the next):**
```
Cycle 1: /rpi fixes test-pass-rate
→ post-mortem harvests: "add missing smoke test for /evolve" → next-work.jsonl
Cycle 2: all GOALS.yaml goals pass
→ /evolve reads next-work.jsonl (exact repo first, then cross-repo '*', then legacy)
→ picks "add missing smoke test"
→ /rpi fixes it → post-mortem harvests: "update SKILL-TIERS count"
Cycle 3: reads next-work.jsonl → picks "update SKILL-TIERS count" → ...
```
The loop keeps running as long as post-mortem keeps finding follow-up work. Each /rpi cycle generates next-work items from its own post-mortem. The system feeds itself.
**Priority cascade:**
```
next-work.jsonl (harvested / queued work) → exact repo first, then `*`, then legacy
Open beads (bd ready) → durable tracked backlog
GOALS.yaml directives and failing goals → explicit fitness gaps
Testing improvements → synthesize coverage / regression-test work
Validation + bug-hunt passes → tighten gates, discover real defects
Hotspots / TODO / drift / stale docs → mine weak signals into work
Feature suggestions → propose new product work when remediation dries up
multiple empty queue + generator passes → last-resort dormancy
60-minute circuit breaker → stop if no productive cycle in 60 min
kill switch → immediate stop
```
The loop does NOT stop just because goals are met or the current queue is empty. It re-reads harvested work after every `/rpi` cycle, runs generator layers when queues are empty, and only stops after repeated empty queue + generator passes. Idle cycles are NOT committed to git — only appended locally. The idle streak is re-derived from disk at each session start, while `session-state.json` carries pending claim/generator resume state. Use the kill switch for intentional stops.
@@ -0,0 +1,79 @@
# Context Budget — Session Handoff, Not a Stop (soc-5qit)
**Prior framing (deprecated):** context exhaustion was a third stop reason that wrote sticky `DORMANT` and ended the run.
**Current framing (soc-5qit, 2026-05-21):** context exhaustion is a *session-handoff signal*. The loop is continuous across compactions; a heavy session writes a non-sticky `HANDOFF` marker, exits the current cron-fire turn cleanly, and the NEXT cron-fire (running on harness-compacted or fresh context) automatically clears HANDOFF in Step 1 and resumes.
This change was forced by an operator failure mode (2026-05-20→21): a cron-driven /evolve session shipped 6 PRs, accumulated context, then sticky-DORMANT'd despite 10+ unblocked P1 beads in `bd ready`. The prior design treated "this Claude session can't safely take on more work" as "the work itself is done" — a category error. The cron loop spans many sessions; one session's context is irrelevant to whether the backlog has work.
## Why it's a real failure mode (still true)
A long session that has shipped 5+ productive cycles accumulates context: read files cache, tool-result trails, prior `/rpi` discovery findings. When cycle N+1 selects an item that requires a fresh deep read of unfamiliar surfaces, the cycle either:
- Forces a scout-mode return because the context is too heavy to execute (correct outcome, but the skill must not treat this as terminal).
- Attempts execution and produces shallow work that the regression gate or self-review catches (recovery cost > the cycle's value).
The fix is to surface this as a handoff to the next session, not stop the loop.
## The counter and the signal
Maintain `context_streak` in `.agents/evolve/session-state.json`. Increment when ANY of these are true at end-of-cycle:
- Cycle result is `scout` AND the scout's `disposition` says "context too heavy"
- Cycle result is `harvested` AND the harvest was a context-budget defer (vs. a feature-suggestion)
- The /rpi cycle aborted before commit because discovery context overflowed
Reset to 0 when a productive `improved` cycle lands.
```bash
context_streak=$(jq -r '.context_streak // 0' .agents/evolve/session-state.json)
if [ "$context_streak" -ge 2 ]; then
echo "CONTEXT_BUDGET_EXHAUSTED after $context_streak heavy-context cycles."
echo "Writing HANDOFF (non-sticky) and exiting this cron-fire turn."
# HANDOFF carries ONLY the parked work refs and the timestamp.
# It is NOT a sticky stop. Step 1 of the next cron-fire clears it automatically.
cat > .agents/evolve/HANDOFF <<EOF
$(date -u +%FT%TZ)
context_streak=$context_streak (this session's context grew too heavy)
parked_work: $(jq -c '.parked_work // []' .agents/evolve/session-state.json)
next-action: cron-fire will resume on compacted/fresh context
EOF
# Hand off via cycle-history.jsonl with result: "context-handoff"
exit 0
fi
```
**Critical:** the HANDOFF marker does NOT block future cron-fires. Step 1 of the next /evolve turn clears it and continues normal work selection.
## Handoff message (logged, not gated)
When the context-handoff fires, write a cycle-history entry that names the parked work concretely:
```json
{"cycle": N, "result": "context-handoff",
"selected_source": "<source>", "work_ref": "<ref>",
"milestone": "Context-handoff after 2 heavy cycles. Parked: <work refs>. Next cron-fire (compacted/fresh) resumes."}
```
The next operator session reads this entry, knows what was parked, and can either continue manually or wait for the next cron-fire to handle it.
## Configurable threshold
Default `context_streak` threshold is 2. Override with `EVOLVE_CONTEXT_STREAK_LIMIT=N` env var if a particular session needs to tolerate more heavy cycles (rare).
## ScheduleWakeup interaction
When running the Claude-Code-harness self-perpetuation mode (see `references/autonomous-execution.md`), a context-handoff exit MUST NOT call `ScheduleWakeup` from the same heavy-context turn — re-firing would re-load the heavy context. The cron schedule (or operator's external trigger) handles the re-fire. By the time the next cron-fire runs, the harness has compacted or the session has rolled, and HANDOFF auto-clears.
## What this is NOT
- **Not a stop reason.** Stop reasons are operator override, max-cycles cap, regression-breaker, and genuine stagnation (bd ready=0 AND harvested=0 AND failing-goals=0 AND generators dry).
- **Not a sticky marker.** DORMANT is sticky-with-auto-clear-on-new-work; HANDOFF is fully non-sticky and clears on next read.
- **Not a "the work is done" signal.** It explicitly says "this session can't safely continue; the work is parked." The next session does the work.
## Cross-reference
- `SKILL.md` Step 1 — HANDOFF auto-clear on cron-fire-N+1
- `SKILL.md` Step 7 — context exhaustion explicitly removed from stop reasons list
- `references/scout-mode.md` — scope-filter splits or defers; never bails
@@ -0,0 +1,102 @@
# Convergence Mechanics — How the Loop Compounds Instead of Drifts
The /evolve loop only compounds when each cycle reads prior cycles' outcomes and lets them change behavior. Append-only ledgers that no step reads are write-only artifacts — they accumulate without compounding.
This reference documents the four feedback mechanisms that turn raw cycle output into next-cycle behavior change.
## Mechanism 1: Step 0 reads prior-failure surface
In Step 0 (Setup), after `mkdir -p .agents/evolve`, the loop reads the last 3 entries of `cycle-history.jsonl`. For any entry where `gate` field contains a FAIL marker, it extracts the failure surface (e.g. "registry-check stale", "bats-tests goals-validate") and injects the matching learning before work selection.
```bash
last3=$(scripts/evolve-read-cycle-history.sh recent 3) # routes through BC3 LoopReaderPort (soc-y5vh.4)
fail_surfaces=$(echo "$last3" | jq -r 'select(.gate | test("FAIL|FAILED|BLOCKED")) | .gate' 2>/dev/null)
if [ -n "$fail_surfaces" ]; then
# Search learnings for surface keywords; print whichever match
keywords=$(echo "$fail_surfaces" | grep -oE 'registry|bats|markdown|supergate|canary|coverage|toolchain' | sort -u)
for kw in $keywords; do
ao lookup --query "$kw failure" --limit 2 2>/dev/null || \
find .agents/learnings -name "*$kw*.md" -mtime -30 | head -2
done
fi
```
Without this, the 2026-05-07 CI-toil learning sat for 7 days while I burned 5 cycles re-hitting the same `registry.json` non-determinism. Reading the learning at Step 0 would have surfaced the `git ls-files` fix on cycle 45.
## Mechanism 2: Step 1.5 healing-first classifier
Before Step 2 (measure fitness) and Step 3 (select work), the loop classifies the cycle:
```bash
# Step 1.5: healing-first classifier — routes through BC2 CIStatusPort
# (cli/cmd/ao/ci_status_adapter.go, cycle 117 productionCIStatus) per
# soc-y5vh.2. No inline gh shell-outs.
last_ci=$(ao ci recent --limit 1 2>/dev/null | jq -r '.Conclusion // empty')
if [ "$last_ci" = "failure" ]; then
CYCLE_MODE="restorative"
# Read failure surface, search for matching learning
# (see Mechanism 1)
# Selection ladder downgrade: skip Step 3.1 (harvested) for feature
# work; only allow harvested items typed bug/fix/ci-failure.
else
CYCLE_MODE="feature"
fi
```
Restorative cycles ONLY take work that reduces CI red. New PG4 promotions, feature additions, doc growth — all blocked until `last_ci=success`.
This eliminates the cycle-46-47 pattern where I added new evidence files onto a CI-red base.
## Mechanism 3: Hypothesis tracking for skill changes
When a cycle edits `skills/evolve/SKILL.md`, it MUST append to the hypothesis
ledger through the typed BC3 `HypothesisLedgerPort` (soc-y5vh.8):
```bash
ao loop hypothesis append --id "H<cycle>.<patch>" --cycle-landed N --check-at-cycle $((N+15)) \
--patch "<one-line>" --hypothesis "<expected effect>" --measure "<how to verify>"
```
This routes through `productionHypothesisLedger` instead of a raw append to
`.agents/evolve/hypotheses.jsonl`; the port rejects empty and duplicate IDs.
At `check_at_cycle`, the loop reads the ledger with `ao loop hypothesis list`
(one JSON record per line), evaluates each PENDING row's `measure`, and
writes the verdict (VERIFIED / FALSIFIED). Falsified hypotheses are
revisited: either the patch is wrong, or the measurement was wrong.
Without this, cycle 45's 6 patches landed unmeasured and 2 of them (H45.2 source-surface auto-rebuild, H45.3 grep-based gate parsing) were silently inert for the next 5 cycles — text in SKILL.md but no harness automation behind them.
## Mechanism 4: Convergence criteria with a STOP
`.agents/evolve/session-convergence.json` records the terminal state; the STOP
decision is evaluated through the typed BC3 `ConvergenceCheckPort` (soc-y5vh.8):
```bash
ao loop converged --green-streak "$STREAK" --unconsumed-high-medium "$HM" --fitness-baseline
# emits {converged, ci_green_streak, unconsumed_high_medium, fitness_baseline_captured, reasons}
```
The predicate is pure — the loop supplies the evidence it already has
(`ao ci recent` for the streak, the next-work findings count, the
fitness-baseline flag). The criteria are met when all hold:
- CI Validate green for the last 3 pushes (green streak ≥ 3)
- HIGH+MEDIUM unconsumed next-work entries ≤ 1
- a fitness baseline has been captured
When `ao loop converged` reports `converged: true`, the loop emits a teardown
report and does NOT call `ScheduleWakeup`. The wakeup chain terminates. The
autonomous loop is bounded by criteria, not by wakeup count. `reasons` names
every unmet criterion when `converged` is false.
Without an explicit STOP, the loop drifts indefinitely. With STOP, it converges.
## Anti-drift rules
1. **Restorative-only after red.** Any cycle whose `gate` field has FAIL → cycle N+1 is restorative.
2. **3 consecutive restorative without restoration → escalate.** Don't silently grind.
3. **Scope shift resets the streak.** If the operator broadens the convergence target mid-session, reset `ci-green-streak` counter to 0.
## Why this is the load-bearing change
Cycles 44-50 wrote ~30 KB of bookkeeping (cycle-history, learning, retro, evidence, hypotheses) but produced ~0 compounded behavior — every cycle had to re-derive the lesson cycle 44 should have surfaced. The compounding lives in the read path, not the write path. These four mechanisms make the read path real.
@@ -0,0 +1,326 @@
# Cycle History Format and Recovery Protocol
## Compaction Resilience
The evolve loop MUST survive context compaction. Every productive cycle appends
to its ledger artifacts before proceeding. `cycle-history.jsonl` is the
on-disk recovery point for cycle numbering, and `.agents/evolve/session-state.json`
is the on-disk resume point for pending queue claims, queue refresh count, and
generator-empty streaks.
### Local-Only Status (Not Git-Tracked)
Both `cycle-history.jsonl` and `session-state.json` are **local-only files**.
The repository's nested `.agents/.gitignore` denies all paths via `*` (with
only `!.gitignore` re-allowed), which overrides the outer `.gitignore`
allowlists at the repo root. Aspirational allowlist entries for
`!/.agents/evolve/cycle-history.jsonl` and `!/.agents/evolve/session-state.json`
exist in the outer `.gitignore` but have no effect because the nested
deny wins per gitignore precedence rules.
Implication for the cycle protocol:
- These files survive **session compaction** (recovered from disk on the next
invocation), but they do **not** survive cloning or shared with peers.
- Important per-cycle milestones (e.g. baseline-capture results, regression
events, convergence-criterion transitions) MUST also be recorded in
**commit messages** so they remain in tracked git history.
- Do not depend on cycle-history.jsonl as a cross-clone authoritative ledger.
Treat it as a session journal that helps the next-cycle bootstrap.
## Cycle History JSONL Format
Append one line per cycle to `.agents/evolve/cycle-history.jsonl`.
### Canonical Schema
All new entries MUST use this schema:
```json
{
"cycle": 123,
"target": "goal-id-or-idle",
"result": "improved|regressed|unchanged|harvested|quarantined",
"sha": "abc1234",
"canonical_sha": "abc1234",
"timestamp": "2026-02-23T12:00:00-05:00",
"goals_passing": 59,
"goals_total": 59
}
```
**Field standardization:**
- Use `target` (not `goal_id`) — this is what recent cycles already use
- Use `sha` as the compatibility alias for `canonical_sha`
- Use `canonical_sha` for the implementation commit the cycle actually delivered
- Use `log_sha` only when the bookkeeping/log commit is distinct from `canonical_sha`
- Always include `goals_passing` and `goals_total` — enables trajectory plotting
- Optional fields: `quality_score` (quality mode), `idle_streak` (idle cycles), `parallel` + `goal_ids` (parallel mode)
**Legacy field names:** Older entries may use `goal_id` instead of `target` and `commit_sha` instead of `sha`. Tools reading cycle-history.jsonl should handle both conventions.
**Sequential cycle entry:**
```jsonl
{"cycle": 1, "target": "test-pass-rate", "result": "improved", "sha": "abc1234", "canonical_sha": "abc1234", "goals_passing": 18, "goals_total": 23, "timestamp": "2026-02-11T21:00:00Z"}
{"cycle": 2, "target": "doc-coverage", "result": "regressed", "sha": "def5678", "canonical_sha": "def5678", "log_sha": "fedcba9", "goals_passing": 17, "goals_total": 23, "timestamp": "2026-02-11T21:30:00Z"}
```
**Idle cycle entry** (not committed to git):
```jsonl
{"cycle": 3, "target": "idle", "result": "unchanged", "timestamp": "2026-02-11T22:00:00Z"}
```
**Parallel cycle entry** (use `goal_ids` array and `parallel: true`):
```jsonl
{"cycle": 4, "goal_ids": ["test-pass-rate", "doc-coverage", "lint-clean"], "result": "improved", "sha": "ghi9012", "goals_passing": 22, "goals_total": 23, "parallel": true, "timestamp": "2026-02-11T22:30:00Z"}
```
### Mandatory Fields
Every productive cycle log entry MUST include:
| Field | Description |
|-------|-------------|
| `cycle` | Cycle number (1-indexed) |
| `target` | Target goal ID, or `"idle"` for idle cycles |
| `result` | One of: `improved`, `regressed`, `unchanged`, `harvested`, `quarantined` |
| `sha` | Compatibility alias for the implementation SHA (omitted for idle cycles) |
| `canonical_sha` | Implementation commit the cycle actually delivered |
| `goals_passing` | Count of goals with result "pass" (omitted for idle cycles) |
| `goals_total` | Total goals measured (omitted for idle cycles) |
| `timestamp` | ISO 8601 timestamp |
`log_sha` is optional and should only be written when the log/bookkeeping commit
differs from `canonical_sha`. These fields enable fitness trajectory plotting
without losing retrospective provenance.
### XP/BDD/TDD Evidence Trace (optional `trace` object)
A productive cycle MAY record a `trace` object capturing the
continuous-evolution kernel — the evidence a reviewer needs to reconstruct
the cycle **without reading the transcript**. The kernel shape is:
> goal hypothesis → selected gap → Gherkin scenario → first failing proof →
> red evidence → green evidence → refactor note → validation evidence →
> ratchet action → goal reshape decision
```json
{
"cycle": 200,
"target": "test-pass-rate",
"result": "improved",
"sha": "abc1234",
"canonical_sha": "abc1234",
"goals_passing": 60,
"goals_total": 60,
"timestamp": "2026-05-16T12:00:00Z",
"trace": {
"goal_hypothesis": "raising test-pass-rate lifts overall fitness",
"selected_gap": "loop cycle ledger carries no evidence trace",
"gherkin": "Feature: trace\n Scenario: reviewer reconstructs a cycle",
"first_failing_proof": "go test ./internal/ports -run TraceCompleteness # FAIL",
"red_evidence": "TestTraceCompleteness_FullTraceIsComplete red: undefined CycleTrace",
"green_evidence": "TestTraceCompleteness_* pass (5/5)",
"refactor_note": "extracted requiredTraceFields table; no behavior change",
"validation_evidence": "cd cli && go test ./... green; bats evolve-log-cycle.bats 10/10",
"ratchet_action": "ao ratchet record implement",
"goal_reshape": "goal unchanged; gap closed — fold trace into next epic's audit"
}
}
```
**Trace fields** (all strings, all `omitempty`):
| Field | Records |
|-------|---------|
| `goal_hypothesis` | The goal/fitness hypothesis the cycle is testing |
| `selected_gap` | The specific gap chosen from the queue/generator |
| `gherkin` | The Given/When/Then scenario the slice satisfies |
| `first_failing_proof` | The command/assertion that fails before the change |
| `red_evidence` | Observed failing-test output (the RED state) |
| `green_evidence` | Observed passing-test output (the GREEN state) |
| `refactor_note` | The refactor step — record `"none"` when there is none |
| `validation_evidence` | Independent validation commands and their results |
| `ratchet_action` | The `ao ratchet record …` action taken |
| `goal_reshape` | Whether the goal stayed, narrowed, or was replaced |
**Trivial-cycle exemption.** A tiny one-shot bookkeeping change (a typo fix,
a dependency bump) does NOT need full BDD/TDD ceremony. Instead of silently
omitting the trace fields, record an explicit `exemption_reason`:
```json
{"cycle": 201, "target": "idle", "result": "unchanged", "timestamp": "...",
"trace": {"exemption_reason": "trivial one-shot typo fix; no Gherkin or failing proof appropriate"}}
```
A trace with a non-empty `exemption_reason` is **exempt** — no other field is
expected. A trace with neither `exemption_reason` nor the evidence fields is
**incomplete**: `ports.TraceCompleteness` (Go) reports which required fields
are missing.
**Completeness is advisory, never blocking.** `TraceCompleteness` is a pure
helper for reports and audits; it is deliberately NOT wired into
`ao loop verify` or `scripts/check-evolve-cycle-logging.sh`. The `trace`
object is recorded as-is. This keeps the loop honest about its own evidence
without making loop-shape a hard gate before real cycle output conforms.
**Writing a trace.** Both writers accept it:
```bash
# canonical script writer — file path, inline JSON, or - for stdin
bash scripts/evolve-log-cycle.sh --cycle "$CYCLE" --target "$TARGET" \
--result improved --canonical-sha "$SHA" --cycle-start-sha "$START" \
--goals-passing "$P" --goals-total "$T" --trace-json cycle-trace.json
# typed BC3 LoopWriterPort
ao loop append --mode evolve --result improved --trace-json cycle-trace.json
```
`ao loop history` emits the `trace` object back as part of each `CycleEntry`,
so the report surface carries the evidence with no extra step.
### Session-State Sidecar
Persist the non-ledger loop state to `.agents/evolve/session-state.json`:
```json
{
"cycle": 124,
"generator_empty_streak": 1,
"last_selected_source": "testing",
"queue_refresh_count": 17,
"claimed_work": {
"ref": "source_epic=ag-123:item=Add smoke test",
"claimed_by": "evolve:cycle-124",
"claimed_at": "2026-03-08T10:15:00Z"
}
}
```
On resume:
1. recover `cycle` from `cycle-history.jsonl`
2. recover generator and claim state from `session-state.json`
3. if `claimed_work` exists, inspect the queue entry:
- if the prior cycle succeeded, finalize it as consumed
- if the prior cycle failed or is ambiguous, release the claim and continue
### Substantive-Delta Rule
Do not record `result: "improved"` when a cycle produces no non-agent repo delta.
If the cycle touched only `.agents/` artifacts or otherwise made no substantive
repo change, rewrite the outcome to `unchanged` and keep it local-only. This
prevents ledger churn from being misread as product progress.
### Telemetry
Log telemetry at the end of each cycle:
```bash
bash scripts/log-telemetry.sh evolve cycle-complete cycle=${CYCLE} score=${SCORE} goals_passing=${PASSING} goals_total=${TOTAL}
```
### Compaction-Proofing: Commit After Productive Cycles
Only **productive cycles** (improved, regressed, harvested) are committed. Idle
cycles are appended to cycle-history.jsonl locally but NOT committed — they are
disposable if compaction occurs, and the idle streak is re-derived from disk at
session start. Producer-layer exhaustion is tracked in `session-state.json`, not
by stopping early.
```bash
# Productive cycle: log via the canonical writer, then commit
bash scripts/evolve-log-cycle.sh \
--cycle "$CYCLE" \
--target "$TARGET" \
--result "$OUTCOME" \
--canonical-sha "$(git rev-parse --short HEAD)" \
--cycle-start-sha "$CYCLE_START_SHA" \
--goals-passing "$PASSING" \
--goals-total "$TOTAL"
# Parallel productive cycle:
bash scripts/evolve-log-cycle.sh \
--cycle "$CYCLE" \
--target "parallel-wave" \
--goal-ids "${goal_ids_csv}" \
--parallel \
--result "$OUTCOME" \
--canonical-sha "$(git rev-parse --short HEAD)" \
--goals-passing "$PASSING" \
--goals-total "$TOTAL"
# Idle or no-delta cycle: append locally, do NOT commit
bash scripts/evolve-log-cycle.sh --cycle "$CYCLE" --target "idle" --result "unchanged" >/dev/null
# No git add, no git commit
```
### 60-Minute Circuit Breaker
At session start (Step 0), after recovering the idle streak, check the timestamp
of the last productive cycle. If it was more than 60 minutes ago, go directly to
Teardown. This prevents runaway sessions that accumulate empty queue/generator
passes without producing value.
```bash
LAST_PRODUCTIVE_TS=$(grep -v '"idle"\|"unchanged"' .agents/evolve/cycle-history.jsonl 2>/dev/null \
| tail -1 | jq -r '.timestamp // empty')
# If >3600s since last productive cycle AND timestamp parsed correctly: CIRCUIT BREAKER → Teardown
# Guard: LAST_EPOCH > 1e9 prevents false trigger on date parse failure
```
## Recovery Protocol
On session restart or after compaction:
1. Read `.agents/evolve/cycle-history.jsonl` to find last completed cycle number
2. Set `evolve_state.cycle` to last cycle + 1
3. Resume from Step 1 (kill switch check)
4. Preserve existing `.agents/evolve/fitness-baselines/goals-<hash>/` directories; do not regenerate the current era baseline if its directory already contains a JSON snapshot
## Kill Switch
Three paths, checked at every cycle boundary:
| File | Purpose | Who Creates It |
|------|---------|---------------|
| `~/.config/evolve/KILL` | Permanent stop (outside repo) | Human |
| `.agents/evolve/STOP` | One-time local stop | Human or automation |
| `.agents/evolve/DORMANT` | Sticky dormancy after Step 3 hard-gate fired | `/evolve` itself |
To stop /evolve:
```bash
echo "Taking a break" > ~/.config/evolve/KILL # Permanent
echo "done for today" > .agents/evolve/STOP # Local, one-time
```
The `DORMANT` marker is written by `/evolve` when both queue layers and generator layers come up empty across 3 consecutive passes (the Step 3 hard-gate). Its purpose is to prevent post-dormancy cron fires from re-entering the full skill body — once the marker exists, Step 1 short-circuits with zero further tool calls. The marker contains three lines: cycle number, ISO timestamp, reason.
To re-enable:
```bash
rm ~/.config/evolve/KILL
rm .agents/evolve/STOP
rm .agents/evolve/DORMANT
```
The operator typically removes `DORMANT` either when (a) new scope arrives that didn't fit any rung of the ladder when dormancy fired, or (b) the underlying ledger has gained enough new entries that generator layers will produce new work. There is no auto-clear — dormancy is sticky by design.
## Flags Reference
| Flag | Default | Description |
|------|---------|-------------|
| `--max-cycles=N` | unlimited | Optional hard cap. Without this, loop runs forever. |
| `--test-first` | off | Pass `--test-first` through to `/rpi` -> `/crank` |
| `--dry-run` | off | Measure fitness and show plan, don't execute |
| `--skip-baseline` | off | Skip cycle-0 baseline sweep |
| `--parallel` | off | Enable parallel goal execution via /swarm per cycle |
| `--max-parallel=N` | 3 | Max goals to fix in parallel (cap: 5). Only with `--parallel`. |
## Troubleshooting
| Problem | Cause | Solution |
|---------|-------|----------|
| `/evolve` exits immediately with "KILL SWITCH ACTIVE" | Kill switch file exists | Remove `~/.config/evolve/KILL` or `.agents/evolve/STOP` to re-enable |
| "No goals to measure" error | GOALS.yaml missing or empty | Create GOALS.yaml in repo root with fitness goals (see goals-schema.md) |
| Cycle completes but fitness unchanged | Goal check command is always passing or always failing | Verify check command logic in GOALS.yaml produces exit code 0 (pass) or non-zero (fail) |
| Regression revert fails | Multiple commits in cycle or uncommitted changes | Check cycle-start SHA in fitness snapshot, commit or stash changes before retrying |
| Harvested work never finalizes | Queue item was claimed but cycle did not clear/finalize it | Inspect `claim_status`, `claimed_by`, and `claimed_at`; successful cycles consume, failed cycles release |
| Loop stops after empty queues | Generator streak was exhausted too quickly or `--max-cycles` was set | Verify producer layers ran, inspect `session-state.json`, and omit `--max-cycles` for overnight runs |
@@ -0,0 +1,50 @@
# Domain Evolution Bootstrap
Use this reference when `/evolve` is asked to reshape AgentOps skills, CLI,
hooks, docs, tests, beads, or knowledge as one software-factory system.
## Product Frame
AgentOps is the SDLC control plane and context compiler for LLM agents. The
narrow waist is:
- BDD/Gherkin for observable intent;
- DDD for shared names and bounded contexts;
- Hexagonal architecture for ports and adapters;
- TDD for local proof of done;
- XP for small vertical slices;
- CI, SRE, ADRs, and provenance for repeated trust and memory.
Do not treat a skill, hook, or command as valuable by itself. It is valuable
only when it advances the factory loop: intent, boundary, proof, evidence, and
compounding context.
## Bootstrap Sequence
1. Read current direction from `PRODUCT.md`, `GOALS.md`,
`docs/cdlc.md`, and `docs/architecture/operating-loop.md`.
2. Validate the durable control artifacts:
```bash
bash scripts/check-agentops-domain-evolution-plan.sh
```
3. Select one domain and one vertical slice from:
- `docs/reference/agentops-domain-evolution-bdd.md`
- `docs/reference/agentops-skill-domain-map.md`
- `docs/reference/agentops-hexagonal-architecture-map.md`
- `docs/reference/agentops-domain-evolution-plan.md`
4. Use `skill-builder` and `skill-auditor` for skill changes. Use
`skills/skill-auditor/scripts/score_agentops_skill.py` to choose the
smallest score-improving patch.
5. Keep CLI and hook changes behind typed ports or existing validation scripts.
6. Run focused validation before selecting the next slice.
## Hard Rules
- No broad rewrite before a Gherkin row, domain, and first proof are named.
- No third-party content copying; use clean-room structural observations only.
- No shipped skill deletion until replacement workflow and validation evidence
exist.
- No shell-only read path when a typed port already exists.
- No unattended run from a dirty canonical root or stale installed `ao` binary.
@@ -0,0 +1,34 @@
# Executable spec for the /evolve skill — the goal-driven compounding loop (BC3 Loop).
# /evolve runs autonomously over /rpi: each cycle a mechanical pre-cycle gate decides
# continue/halt, the work-selection ladder picks the highest-value bounded item, one
# slice ships gated (revert on red), and the loop NEVER self-halts — only operator
# markers stop it. Hexagon: supporting; consumes: rpi, goals, post-mortem, compile;
# produces: git-changes + goals-fitness-delta. (soc-qk4b.2; see ADR-0007/ADR-0008)
Feature: Evolve runs a goal-driven compounding loop
As the autonomous improvement loop
I want each cycle gated, work-selected by ladder, and shipped as a bounded slice
So that the repo compounds toward GOALS.md without the agent ever self-halting
Background:
Given an operator-set intent (GOALS.md, ADRs, the bead queue)
Scenario: A mechanical pre-cycle gate decides continue or halt
When a cycle starts
Then scripts/evolve/halt-check.sh runs first
And the loop continues unless an operator marker / goal-regression / prior-FAIL fires
Scenario: Work selection walks the ladder
When the loop selects work
Then it prefers harvested next-work, then ready beads, then failing goals, then generators
And it declines operator-shape work (epics/release/decisions) per ADR-0008
Scenario: One bounded slice per cycle, reverted on red
When the loop works a selected item
Then it ships a single bounded slice via /rpi, gated by build + test + lint
And it reverts the slice rather than landing red
Scenario: The loop never self-halts
When the loop is blocked or out of bounded work
Then it logs `ao loop blocked` and continues or operator-waits
And it never writes a STOP/DORMANT marker itself (operator-only, ADR-0007)
@@ -0,0 +1,64 @@
# /evolve Examples
## Infinite Autonomous Improvement
**User says:** `/evolve`
**What happens:**
1. Agent checks kill switch files (none found, continues).
2. Agent loads the repo execution profile, reads the ordered startup reads, and caches repo validation commands plus definition_of_done before choosing work.
3. Agent first reads `.agents/rpi/next-work.jsonl`, claims the highest-value harvested item, and runs `/rpi` on it.
4. The cycle's `/post-mortem` harvests 2 new follow-up items; evolve immediately re-reads the queue instead of trusting the pre-cycle snapshot.
5. With harvested work drained, evolve checks `bd ready` and lands the top unblocked bead.
6. With beads drained, evolve measures GOALS.yaml, finds a directive gap, and runs `/rpi` on that goal.
7. Once goals/directives are healthy, evolve generates testing work from thin coverage and lands the best regression-test improvement.
8. Testing producers dry up, so evolve runs validation tightening / bug-hunt and fixes the highest-value finding.
9. When remediation layers are empty, evolve mines hotspot/TODO/stale-doc drift and turns any real findings into durable work.
10. If all remediation layers stay empty, evolve writes a concrete feature suggestion as durable work and starts the next `/rpi`.
11. Only after repeated empty queue + generator passes does dormancy trigger and teardown begin.
12. To stop earlier: create `~/.config/evolve/KILL` or `.agents/evolve/STOP`.
**Result:** Runs as an always-on compounding loop. Empty queues trigger more work discovery; they do not end the run.
## Dry-Run Mode
**User says:** `/evolve --dry-run`
**What happens:**
1. Agent measures fitness.
2. Agent reports the next harvested/beads/goals item it would work on.
3. If those are empty, agent reports the next generator layer it would run (testing, validation, drift, or feature suggestion).
4. Agent stops without executing.
**Result:** Next-action preview without code changes.
## Regression with Revert
**User says:** `/evolve --max-cycles=3`
**What happens:**
1. Agent claims a harvested queue item in cycle 1 and starts `/rpi`.
2. Post-cycle fitness shows a regression.
3. Agent reverts the cycle's changes.
4. Agent clears the queue claim and leaves `consumed: false`, so the work is available again.
5. Agent logs the regression and continues.
**Result:** Fitness regressions are auto-reverted, and claimed work is re-queued instead of being lost.
## Worked Overnight Ladder
**User says:** `/evolve --compile`
**What happens:**
1. Compile warmup surfaces a stale research note about runtime smoke coverage.
2. Evolve loads the repo execution profile first, so the startup reads, tracker wrapper, and validation bundle come from repo policy instead of a giant prompt.
3. `bd ready` has one open docs/runtime parity bead, so evolve runs that first.
4. That bead's `/post-mortem` harvests an implementation follow-up into `next-work.jsonl`; evolve re-reads the queue and runs it immediately.
5. The queue empties, so evolve measures goals and fixes one directive gap via `/rpi`.
6. All goals now pass. Evolve generates testing work from thin coverage and lands a new regression test.
7. Testing producers dry up, so evolve runs a bug-hunt / validation sweep and tightens a missing validation gate.
8. No bug-hunt findings remain, so evolve mines complexity/TODO/stale-doc drift and queues one cleanup item.
9. After that cleanup, the remediation ladder is empty, so evolve writes a concrete feature suggestion bead and starts the next `/rpi`.
10. Only after harvested work, beads, goals, testing, bug hunt, drift mining, and feature suggestions all come up empty across repeated passes does dormancy trigger.
**Result:** One long-running session compounds across beads -> harvested work -> goals -> testing -> bug hunt -> feature suggestion instead of stopping at the first empty queue.
@@ -0,0 +1,89 @@
# Fitness Scoring
## Measurement
Run the fitness measurement script to produce a rolling snapshot:
```bash
bash scripts/evolve-measure-fitness.sh \
--output .agents/evolve/fitness-latest.json \
--timeout 60 \
--total-timeout 75
```
**Do NOT write per-cycle `fitness-{N}-pre.json` files.** The rolling file is sufficient for work selection and regression detection.
This writes a fitness snapshot to `.agents/evolve/` atomically via a temp file plus JSON validation. The AgentOps CLI is required for fitness measurement because the wrapper shells out to `ao goals measure`. If measurement exceeds the whole-command bound or returns invalid JSON, the wrapper fails without clobbering the previous rolling snapshot.
## Era Baseline Capture (First Run Only)
Skip if `--skip-baseline` or `--beads-only` or baseline already exists.
`/evolve` captures this automatically before entering the RPI loop. It hashes
the active GOALS.md or GOALS.yaml file to an era ID, then writes a snapshot
under `.agents/evolve/fitness-baselines/goals-<hash>/` if that era directory
does not already contain a JSON snapshot.
For manual recovery or one-off capture, compute the same era ID and use the
helper script:
```bash
GOALS_FILE=""
if [ -f GOALS.md ]; then
GOALS_FILE="GOALS.md"
elif [ -f GOALS.yaml ]; then
GOALS_FILE="GOALS.yaml"
fi
if [ -n "$GOALS_FILE" ]; then
ERA_ID="goals-$(shasum -a 256 "$GOALS_FILE" | awk '{print substr($1, 1, 12)}')"
bash scripts/evolve-capture-baseline.sh \
--label "$ERA_ID" \
--timeout 60 \
--total-timeout 75
fi
```
## Post-Cycle Re-Measurement (Regression Gate)
After execution, re-measure to detect regressions:
```bash
bash scripts/evolve-measure-fitness.sh \
--output .agents/evolve/fitness-latest-post.json \
--timeout 60 \
--total-timeout 75 \
--goal "$GOAL_ID"
# Extract goal counts for cycle history entry
PASSING=$(jq '[.goals[] | select(.result=="pass")] | length' .agents/evolve/fitness-latest-post.json 2>/dev/null || echo 0)
TOTAL=$(jq '.goals | length' .agents/evolve/fitness-latest-post.json 2>/dev/null || echo 0)
```
**If regression detected** (previously-passing goal now fails):
```bash
git revert HEAD --no-edit # single commit
# or for multiple commits:
git revert --no-commit ${CYCLE_START_SHA}..HEAD && git commit -m "revert: evolve cycle ${CYCLE} regression"
```
Set outcome to "regressed".
## Oscillation Detection
Before working a failing goal, check if it has oscillated (improved-to-fail transitions >= 3 times in `cycle-history.jsonl`). If so, quarantine it and try the next failing goal.
```bash
# Reads cycle history via BC3 LoopReaderPort (soc-y5vh.4 wrapper),
# not raw .agents/evolve/cycle-history.jsonl slurp.
OSC_COUNT=$(scripts/evolve-read-cycle-history.sh recent 0 \
| jq -r "select(.target==\"$FAILING\") | .result" \
| awk 'prev=="improved" && $0=="fail" {count++} {prev=$0} END {print count+0}')
if [ "$OSC_COUNT" -ge 3 ]; then
QUARANTINED_GOALS[$FAILING]=true
echo "{\"cycle\":${CYCLE},\"target\":\"${FAILING}\",\"result\":\"quarantined\",\"oscillations\":${OSC_COUNT},\"timestamp\":\"$(date -Iseconds)\"}" >> .agents/evolve/cycle-history.jsonl
fi
```
See also: `references/oscillation.md` for full quarantine protocol.
@@ -0,0 +1,74 @@
# Gate Hygiene — Pre-Gate Sync and Output Parsing
Two recurring gate-stage frictions that compound across long sessions: missing source-surface rebuilds (the gate runs against stale binaries) and trusting trailing status lines (the gate output mixes blocking and advisory signals).
## Pre-gate source-surface detection
Before invoking `pre-push-gate.sh` (or any binary-dependent gate), inspect the staged diff and rebuild downstream artifacts if any of these surfaces changed:
| Changed surface | Required pre-gate action |
|---|---|
| `cli/**/*.go` | `cd cli && make build && go install ./cmd/ao` — refreshes `cli/bin/ao` and `~/go/bin/ao` so the gate sees the new Go behaviour |
| `skills/**` or `hooks/**` | `cd cli && make sync-hooks` — refreshes `cli/embedded/{skills,hooks}/` so the embedded-parity check passes |
| `skills-codex/**` | `bash scripts/regen-codex-hashes.sh` — refreshes generated_hash values in the codex manifest |
| `schemas/**` and `docs/contracts/**` together | re-run contract validation locally first; do not let CI surface the drift |
Detection recipe:
```bash
changed=$(git diff --cached --name-only)
echo "$changed" | grep -q '^cli/.*\.go$' && (cd cli && make build && go install ./cmd/ao)
echo "$changed" | grep -qE '^(skills/|hooks/)' && (cd cli && make sync-hooks)
echo "$changed" | grep -q '^skills-codex/' && bash scripts/regen-codex-hashes.sh
```
Without these pre-gate steps, the gate may fail with stale-binary or embedded-drift errors that look like real regressions but are just plumbing. Each false failure costs a turn of recovery work.
## Gate output parsing
The pre-push gate (and similar two-pass scripts) emits both blocking and advisory results. Trusting only the trailing status line conflates them. Use a structured grep:
```bash
# Capture full output, then parse explicit failure markers
bash scripts/pre-push-gate.sh --fast 2>&1 | tee /tmp/gate.log
# Authoritative blocking failures (case-sensitive Pass N: FAILED|BLOCKED)
if grep -E '^.*Pass [0-9]+: (FAILED|BLOCKED)' /tmp/gate.log >/dev/null; then
echo "BLOCKING failure detected"
exit 1
fi
# Advisory issues — record but don't block
grep -E 'advisory|warning|WARN' /tmp/gate.log || true
```
Anti-pattern: reading `tail -1 /tmp/gate.log` and treating "passed (N skipped)" as authoritative. A run can show "Pass 1: FAILED" mid-output and "passed (X skipped)" at the end if Pass 2 ran in advisory-only mode against the worktree. The structural markers (`Pass N: FAILED|BLOCKED`) are the truth.
## When to use `PRE_PUSH_SKIP_EVAL=1`
Documented release valve for the eval canary lane only, when:
- The canary is flaking on pre-existing infra (filed as a tracked bead)
- The current diff is unrelated to evals/, schemas/eval-, or cli/cmd/ao/eval
- A recorded recent run has confirmed the canary is currently 50/50
Never use `--no-verify`. The pre-commit hook is a no-op for most diffs but the principle violation is durable in `git log` and surfaces in post-mortem.
## Pre-push diff-scope check (mandatory before every commit)
Two cheap pre-commit reads catch the failure modes that cost a full fix-and-repush round each (the 2026-05-29 `/burndown` + codex-budget reds):
1. **`git status --short` must show ALL intended files staged.** A half-staged commit — the feature gate-script left out while only its test was committed — let CI run the *old* code so the new assertion could not pass (the #612 red). A feature and its test are one commit, not the test alone.
2. **`git diff origin/main --stat` must be SCOPE-ONLY.** Confirm: no collateral deletions (a `git checkout --theirs` conflict take silently dropped two *unrelated* skill rows in the #600 rebase), and no lossy whole-file reformats (a 252-line `catalog.json` round-trip — discard those and use the idempotent appender). For generated/narrative files, restore `origin/main`'s version and **re-run the generator** rather than hand-merging.
## Triage red precisely — pre-existing-main vs your-change
Not every red is yours. The `agentops` main branch carries known pre-existing failures a local gate or a broad generator will surface:
- **Local `mkdocs --strict`** fails on tracked docs because the system `mkdocs` (≤1.1.2) can't parse the modern `mkdocs.yml` (needs material plugins). CI uses the venv and passes. Confirm a flagged file is `git ls-tree origin/main`-tracked AND absent from your diff → pre-existing, proceed.
- **~7 codex `.agentops-generated.json` drifts** (deps, provenance, red-team, release, scenario, trace, using-agentops): `regen-codex-hashes.sh` "updates" them because they're drifted on main. **REVERT them** (`git checkout origin/main -- <those>`) and keep only your skill's hash; surgically rebuild the manifest from `origin/main` + your delta. The CI artifact gate is changed-files-scoped (`--scope head`) so it ignores the pre-existing drift; sweeping them in adds unrelated churn and risks a manifest↔marker mismatch.
**Rule:** only fix red your own diff introduced. When a generator touches files outside your bead's scope, revert those files. "The generator changed it" never justifies shipping unrelated drift.
## See also
- [new-skill-landing.md](new-skill-landing.md) — the six derived surfaces a new/modified skill must regenerate in one shot (the companion to this diff-scope discipline).
@@ -0,0 +1,140 @@
# GOALS.yaml Schema
```yaml
version: 1
mission: "What this repo does"
goals:
- id: unique-identifier
description: "Human-readable description"
check: "shell command — exit 0 = pass, non-zero = fail"
weight: 1-10 # Higher = fix first
```
Goals are checked in weight order (highest first). The first failing goal with the highest weight is selected for improvement.
## Fitness Snapshot Format
Each cycle writes a fitness snapshot with **continuous values** (not just pass/fail):
```json
{
"cycle": 1,
"timestamp": "2026-02-12T15:45:00-05:00",
"cycle_start_sha": "abc1234",
"goals": [
{
"id": "go-coverage-floor",
"result": "pass",
"weight": 2,
"value": 86.1,
"threshold": 80
},
{
"id": "doc-coverage",
"result": "pass",
"weight": 2,
"value": 20,
"threshold": 16
},
{
"id": "go-cli-builds",
"result": "pass",
"weight": 5,
"value": null,
"threshold": null
}
]
}
```
- **value**: The continuous metric extracted from the check command (null for binary-only goals)
- **threshold**: The pass/fail threshold (null for binary-only goals)
- **cycle_start_sha**: Git SHA at cycle start, used for multi-commit revert on regression
Pre-cycle snapshot: `fitness-latest.json` (rolling, overwritten each cycle)
Post-cycle snapshot: `fitness-latest-post.json` (rolling, for regression comparison)
## Era Baselines
Before the first improvement cycle of a new goal era runs, evolve captures an
immutable baseline snapshot under
`.agents/evolve/fitness-baselines/goals-<hash>/`, where `<hash>` is the first
12 hex characters of the SHA-256 digest of the active GOALS.md or GOALS.yaml
file. A new goal era gets a new directory automatically; an existing era is
not recaptured once that directory contains a JSON snapshot.
Each era baseline includes:
- **All goals** from GOALS.yaml or GOALS.md, measured in their initial state for that era
- **Baseline metadata** embedded in the snapshot JSON written by `ao goals measure`
- **No regression comparisons** — this is the starting point for that era
When the session ends (at Teardown), the system computes the **session fitness trajectory** by comparing the current era baseline against the final cycle snapshot. This produces `session-fitness-delta.md`, which shows which goals improved, regressed, or stayed unchanged over the entire /evolve session.
## Meta-Goals
Meta-goals validate the validation system itself. Use them to prevent exception lists (allowlists, skip lists) from accumulating stale entries unnoticed.
```yaml
# Meta-goals validate the validation system itself
goals:
- id: allowlist-hygiene
description: "Every dead-code allowlist entry should have 0 non-test callers"
check: "bash scripts/check-allowlist-hygiene.sh"
weight: 7
- id: skip-list-hygiene
description: "Every skip-list entry should still reference an existing test"
check: "bash scripts/check-skip-list-hygiene.sh"
weight: 5
```
**When to add a meta-goal:** After pruning any allowlist or exception list, always add a corresponding meta-goal that fails if entries have callers/references. Allowlists without meta-goals are technical debt magnets — they grow silently across epics.
## Maintaining GOALS.yaml
Use `/goals` to maintain the fitness specification:
- `/goals` — run all checks, report pass/fail by pillar
- `/goals generate` — scan repo for uncovered areas, propose new goals
- `/goals prune` — find stale/broken goals, propose removals or updates
## GOALS.md Format (Version 4)
GOALS.md extends the YAML format with strategic intent:
```markdown
# Goals
<Mission statement>
## North Stars
- <Aspiration>
## Anti Stars
- <What to avoid>
## Directives
### 1. <Title>
<Description>
**Steer:** increase | decrease | hold | explore
## Gates
| ID | Check | Weight | Description |
|----|-------|--------|-------------|
| id | `command` | N | Description |
```
### Evolve Integration
When GOALS.md is detected, evolve uses the directive-based cascade (Step 3.1):
1. `ao goals measure --directives` returns the directive list as JSON
2. Top-priority directive (lowest number) is assessed for gaps
3. If gap found → generates work item from directive description + steer
4. Directive becomes the work source for the cycle
When `--beads-only` is passed, directive assessment is skipped entirely.
### Format Detection
`ao goals measure` auto-detects format. When both GOALS.yaml and GOALS.md exist, GOALS.md takes precedence.
@@ -0,0 +1,64 @@
# Knowledge Loop Integration
## Compile Warmup (--compile only)
Skip if `--compile` was not passed or if `--dry-run`.
Run the mechanical half of the Compile cycle to surface fresh signal before the first evolve cycle:
```bash
mkdir -p .agents/mine .agents/defrag
echo "Compile warmup: mining signal..."
ao mine --since 26h --quiet 2>/dev/null || echo "(ao mine unavailable — skipping)"
echo "Compile warmup: defrag sweep..."
ao defrag --prune --dedup --quiet 2>/dev/null || echo "(ao defrag unavailable — skipping)"
```
Then read `.agents/mine/latest.json` and `.agents/defrag/latest.json` and note (in 1-2 sentences each):
- Any **orphaned research** files that look relevant to current goals
- Any **code hotspots** (high-CC functions with recent edits) that may be the root cause of failing goals
- Any **duplicate learnings** merged by defrag — context on what's been cleaned up
These notes inform work selection throughout the evolve session. Store them in a session variable (in-memory), not a file.
## Harvested Work Selection (Step 3.1)
Read `.agents/rpi/next-work.jsonl` and pick the highest-value unconsumed item for this repo. Prefer:
- exact repo match before `*`, then legacy unscoped entries
- already-harvested concrete implementation work before process work
- higher severity before lower severity
When evolve picks a queue item, **claim it first**:
- set `claim_status: "in_progress"`
- set `claimed_by: "evolve:cycle-N"`
- set `claimed_at: "<timestamp>"`
- keep `consumed: false` until the `/rpi` cycle and regression gate both succeed
If the cycle fails, regresses, or is interrupted before success, release the claim and leave the item available for the next cycle.
## Queue Finalization After Regression Gate
- **success:** finalize any claimed queue item with `consumed: true`, `consumed_by`, and `consumed_at`; clear transient claim fields
- **failure/regression:** clear `claim_status`, `claimed_by`, and `claimed_at`; keep `consumed: false`; record the release in `session-state.json`
After the cycle's `/post-mortem` finishes, immediately re-read `.agents/rpi/next-work.jsonl` before selecting the next item. Never assume the queue state from before the cycle.
## Teardown Learning Extraction
1. Commit any staged but uncommitted `cycle-history.jsonl`:
```bash
if git diff --cached --name-only | grep -q cycle-history.jsonl; then
git commit -m "evolve: session teardown -- artifact-only cycles logged"
fi
```
2. Run `/post-mortem "evolve session: ${CYCLE} cycles"` to harvest learnings.
3. Push only if unpushed commits exist:
```bash
UNPUSHED=$(git log origin/main..HEAD --oneline 2>/dev/null | wc -l)
[ "$UNPUSHED" -gt 0 ] && git push
```
4. Report summary: cycles, productive/regressed/idle counts, stop reason. Quality mode adds quality score + remaining findings.
@@ -0,0 +1,98 @@
# Long-Loop Discipline — Disk Is Truth, Conversation Is Decorative
The cross-cutting axiom that every other `/evolve` reference implements.
When a loop runs past ~50 cycles (`/evolve`, `/loop`, `ScheduleWakeup`-driven
cron), **every load-bearing state must live on disk, not in conversation
context.** The conversation accumulates: skill prompts re-inject, hook
reminders pile up, tool-result history grows. Auto-compact handles it well
enough that correctness survives — *if and only if* the work-state was on
disk to begin with.
This doc is the principle; the implementing references are listed below.
## The Axiom
| State | Truth surface |
|---|---|
| Cycle outcome | `.agents/evolve/cycle-history.jsonl` (append-only, read at every cycle start) |
| Hypothesis tracker | `.agents/evolve/hypotheses.jsonl` |
| Queueable work | `bd ready` (Dolt-backed, out-of-band) |
| Skill rules | `skills/<n>/SKILL.md` + `references/` (read fresh per cycle) |
| Conversation context | **Decorative** — re-derives state from disk; never trusted alone |
If you remove the conversation context entirely between cycles and the work
keeps moving, the loop is healthy. If removing it breaks the loop, you are
storing load-bearing state in the wrong place.
## Evidence (anchored)
> "Every cycle starts by recovering state from `.agents/evolve/cycle-history.jsonl`
> — not from the conversation. Context drift in the conversation doesn't lose
> the work-state … The conversation context that accumulates is mostly
> decorative; the load-bearing state is on disk."
`docs/learnings/2026-05-13-loop-context-drift-87-cycle-observation.md`
(87-cycle empirical observation)
> "text in SKILL.md is aspirational until paired with harness automation."
`docs/learnings/2026-05-11-evolve-skill-friction-from-13-cycle-session.md`
(H45.2 + H45.3 falsified within 4-6 cycles because the rule lived only in
prose, not in a gate)
> "Cycle 12's deep-scout … was the most productive move of cycles 11-13 even
> though it shipped zero code. … A scout cycle is neither productive (ships
> code) nor idle (finds nothing): it ships durable knowledge about pending
> work, narrowing future cycles' search and discovery cost."
`.agents/learnings/2026-05-11-evolve-scout-mode-pattern.md`
(durable knowledge IS the disk-truth surface)
> "in a shared tree, commit by explicit path (`git add <files>`), never
> `git add -A`; treat full-repo gate runs as unreliable when other agents
> are active and lean on targeted proof commands instead."
`.agents/learnings/2026-05-16-evolve-trace-shared-tree.md`
(disk-truth survives only if peer agents don't clobber it)
## How To Apply
1. **Read the ledger first.** Every cycle's first action is
`tail -n 50 .agents/evolve/cycle-history.jsonl` (or the
`productionLoopReader` adapter). Recover state from there, not from prior
turns' messages.
2. **Append, don't overwrite.** Each cycle appends one JSONL line. Never
rewrite earlier cycles. The ledger is the audit trail.
3. **Bead-driven work selection.** `bd ready` is the queue. Beads live in
Dolt; the conversation never owns work scheduling.
4. **Falsify aspirational rules.** Any rule in `SKILL.md` that depends on
the agent following it (vs. a harness gate enforcing it) will be
violated within 4-6 cycles. Wire automation or accept the rule is
guidance only.
5. **Treat "complete" claims with suspicion.** When a cycle says "all
surfaces wired," the next CI run often finds one more. Add a per-cycle
drift sweep instead of trusting prior claims.
6. **Protect disk-truth in shared trees.** See
[../../swarm/references/shared-checkout-discipline.md](../../swarm/references/shared-checkout-discipline.md)
— disk-truth only survives if peer agents don't clobber it via
`git add -A` or destructive recovery.
## Implementing References (Already In `/evolve`)
The axiom is the principle. Each reference below is one mechanism that
keeps disk-truth working:
| Reference | Role |
|---|---|
| [cycle-history.md](cycle-history.md) | JSONL format, recovery protocol, kill switch |
| [autonomous-execution.md](autonomous-execution.md) | ScheduleWakeup self-perpetuation pattern |
| [context-budget.md](context-budget.md) | `CONTEXT_BUDGET_EXHAUSTED` as a third stop reason |
| [convergence-mechanics.md](convergence-mechanics.md) | Read-path mechanisms (prior-failure injection, healing-first classifier) |
| [scout-mode.md](scout-mode.md) | Scout-cycle as a first-class disk-truth-only result type |
| [gate-hygiene.md](gate-hygiene.md) | Pre-gate source-surface detection + structural gate-output parsing |
| [metronome-gate.md](metronome-gate.md) | Cross-cycle detector blocking same-mode-repeated failure |
| [pre-flight-schema-check.md](pre-flight-schema-check.md) | Cheap field-fit check before architectural migration |
## See Also
- `skills/rpi/references/orchestrator-compression-anti-pattern.md` — when
the orchestrator skips phases by inlining sub-skill work, no flywheel
artifact lands (same disk-truth principle, applied to /rpi).
- `skills/swarm/references/shared-checkout-discipline.md` — disk-truth only
survives if peer agents don't clobber it.
@@ -0,0 +1,19 @@
# /evolve Loop Mode
`--mode=loop` flips /evolve from agent-self-regulated to operator-driven.
## Invariants under --mode=loop
1. `ao loop write-stop-marker` exits 1 unconditionally
2. DORMANT/STOP/KILL markers are operator-only (operator writes by hand or via `ao loop operator-stop`)
3. Scope-filter (Step 3) splits too-big work into smaller beads via `bd create --deps discovered-from:<parent>`, never halts
4. Step 7 stop reasons are stripped of CONTEXT_BUDGET_EXHAUSTED; that becomes a non-sticky HANDOFF signal cleared by next cron-fire
## CLI primitives that enforce
- `/evolve --mode=loop` — flag itself
- `ao loop write-stop-marker` — refuses under loop
- `ao loop operator-stop` — explicit operator override (separate code path)
- `ao loop blocked` (Wave 2) — typed blocked event instead of STOP
See: docs/plans/2026-05-21-evolve-loop-epic-design.md §A1 + §A7
@@ -0,0 +1,56 @@
# Mechanical Batch Work — Script vs Per-File Edit
A "mechanical batch" is work where the same edit is applied to N files mechanically, with no per-file judgement after the pattern is established. Examples from prior sessions: declaring `practices: [slug]` across 533 cli/ Go files, regenerating COMMANDS.md from a Cobra tree, bumping a version literal in N manifests.
The default per-file `Edit` tool path scales linearly with N and burns context. Switch to a script when ANY of these apply:
- N > ~20 files
- The transformation is **uniform** (same regex / same sed / same template field)
- A failure on file K does not change the strategy for files K+1..N
- Per-file output verification is cheap (`git diff --stat`, a single grep)
## Script-first pattern
```bash
# 1. Inventory candidates with the predicate the task implies
candidates=$(git ls-files 'skills/**/SKILL.md' | xargs -I{} bash -c '
grep -L "^practices:" "{}" >/dev/null && echo "{}"
' | head -50)
# 2. Apply the mechanical edit
for f in $candidates; do
awk '
/^description:/ && !done { print; print "practices: [<slug>]"; done=1; next }
{ print }
' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
done
# 3. Verify in one shot
git diff --stat
grep -c "^practices:" $candidates | sort
```
## Per-file Edit pattern (when the above does NOT apply)
Use `Edit` per file only when:
- The transformation requires per-file decisions (which slug? which carrier?)
- N is small (< 10)
- Verification benefits from reading each diff in context
## Decision in Step 4
When `/rpi` discovery surfaces a per-file pattern with N > 20 uniform candidates, the implementation phase should generate a script under `scripts/` or use an inline awk/sed pass rather than queuing N `Edit` operations. The post-edit gate then verifies the batch atomically.
If the script needs different parameters per file, that is a signal the work isn't actually mechanical — fall back to per-file Edit and reduce N (split the cycle).
## Why this matters
A 533-file `practices:` backfill done via per-file Edit takes ~50× more context than the same backfill done via a script with `git diff --stat` verification. The context-budget exhausts well before the work completes, and the loop drops into a forced scout or idle state with most of the work still pending.
## Anti-patterns
- Running a script that mutates files but ignoring the result of `git diff` (silent partial application).
- Using `sed -i` on a tree-traversed find without `--null` handling (filename-with-spaces breakage).
- Mixing mechanical and per-file edits in the same commit (auditor cannot tell what was bulk-applied vs per-judged).
- Treating a 200-file batch as a single "atomic" change — split into review-sized waves (~30-100 files) with one commit per wave.
@@ -0,0 +1,63 @@
# Metronome Gate — Detecting Productive-Looking Repetition
The work-selection ladder treats "tests passed + commit landed" as productive. That signal is necessary but not sufficient: it cannot tell apart a cycle that *applied an existing template again* from one that *closed a new lane of work*. Cycles 144-154 in this repo were 7 consecutive CLI-wiring template applications — every cycle green, every commit clean, every gate happy. Only operator override at cycle 154 stopped the run.
The metronome gate adds a structural detector: when the same `mode` value repeats N times, the next rung of the ladder is forced.
## State
`mode_repeat_streak` is computed by `scripts/evolve-update-session-state.sh` from the trailing run of identical `mode` values in `cycle-history.jsonl`. It is derived, not authored — running the script after every cycle keeps it coherent.
## Thresholds
| `mode_repeat_streak` | Action |
|---|---|
| 02 | No gate. The selection ladder runs normally. |
| 34 | **Soft block.** If the selected work would produce the same `mode` value, skip to the next rung of the selection ladder. Do not abort — try the rung below. |
| 5+ | **Hard block.** Record the gap as bead/provenance evidence and require operator override (write `.agents/evolve/RESCOPE` with a new directive). The cycle is logged as `unchanged` with `selected_source: "metronome-gate-blocked"`. |
## Why the threshold is 3, not 2
A 2-cycle streak is normal: harvested work often comes in pairs (port + adapter, R + W, parent + child). A 3-cycle streak is the earliest signal that the *template* has become the work, not the *problem*. Stopping at 3 catches the failure mode early without blocking legitimate harvested-pair cycles.
## Naming discipline (operator instruction)
For the gate to work, cycle entries must use *distinguishable* `mode` values. Anti-patterns observed before this gate existed:
- `template-applied-cli-wiring-5th`, `template-applied-cli-wiring-6th`, `template-applied-cli-wiring-7th` — the suffix `-Nth` made these technically unique strings but obviously the same mode. The gate canonicalizes by stripping trailing `-Nth` / `-N` patterns before comparison.
- `wire-up-BC1`, `wire-up-BC2`, `wire-up-BC3` — the BC suffix changes but the work shape is identical. This is a *legitimate harvested-pair sequence* — three separate BCs, three real ports — and the gate correctly does NOT block here because the streak counts identical strings only. Operator distinguishes "BC-pair work" from "template metronome" by intent.
When in doubt, the operator override at threshold 5+ is the safety net.
## Worked example — cycles 144-154
```
cycle 144: wire-up-BC1 (streak: 1)
cycle 145: wire-up-BC1-pair (streak: 1, different)
cycle 146: wire-up-complete (streak: 1)
cycle 147: template-applied-cli-wiring-4th (streak: 1)
cycle 148: template-applied-cli-wiring-5th (streak: 2 after canonicalization)
cycle 149: template-applied-cli-wiring-6th (streak: 3 — SOFT BLOCK fires)
cycle 150: template-applied-cli-wiring-7th (streak: 4 — still blocked, next rung)
cycle 151..154: ... (operator override at 154)
```
With the gate active at threshold 3, cycle 149 would have skipped to the next rung of the selection ladder, probably surfacing the latent narrowness problem 5 cycles sooner.
## Interaction with idle_streak
The metronome gate fires on *productive* cycles; the dormancy gate fires on *idle* cycles. They are orthogonal:
- `idle_streak >= 2 AND generator_empty_streak >= 2` → dormancy (see Step 3 hard-gate)
- `mode_repeat_streak >= 3 AND candidate.mode == last_mode` → metronome block (skip rung)
- Both can never fire on the same cycle: idle cycles produce no work, so they cannot extend the metronome.
## Why this lives in /evolve, not /rpi
`/rpi` runs one cycle's worth of work. It has no visibility into prior cycles. The metronome is a *cross-cycle* pattern, only visible from the loop driver. `/evolve` is the only place that has both the ledger and the next-cycle decision.
## See also
- `docs/rescope/2026-05-13-ddd-hex-architecture-rescope.md` — the original operator override that prompted this gate.
- `references/cycle-history.md` — the ledger this gate reads.
- `references/scout-mode.md` — what to do with work that hits the soft-block (read + annotate, no execution).
@@ -0,0 +1,50 @@
# New-Skill Landing — the six derived surfaces
Adding or modifying a skill regenerates **six derived surfaces**, each gated by CI. Regenerating them piecemeal is the dominant fix-and-repush cause (the 2026-05-29 `/burndown` re-land needed 3 rounds; the codex-desc-budget fix needed 1). Regenerate them in **one shot** before opening the PR.
## The shortcut
`scripts/regen-all.sh` covers surfaces 13 + the CLI reference in one pass — prefer it over running the generators individually. Then run the codex + count steps below.
## Live skill edits
When an agent edits an existing live skill at `skills/<slug>/SKILL.md`, seal the
edit before the next cycle hands off:
```bash
ao skills edit seal --skill <slug> --actor "${AGENT_NAME:-agent}"
```
That command stages the skill's source directory and any matching Codex skill
directory, creates a git rollback point, and records `Skill-Edit` trailers in
the commit body. Critical skills listed in
`docs/contracts/critical-skills.txt` reject unattended edits; rerun with
`--allow-critical` only for a human-supervised critical edit.
Daily operator review uses:
```bash
ao skills edit digest --since "24 hours ago"
```
The digest is the lightweight immune-system surface for "what did agents teach
the runtime today?" A live edit seal does not replace the derived-surface work
below: new skills, renames, metadata changes, and publication-bound edits still
need the full six-surface regeneration.
## The six surfaces
1. **registry.json (SKU catalog)**`scripts/generate-registry.sh` (verify with `--check`). **The most-missed surface**: a stale `registry.json` trips `contracts-sync` ("registry.json is stale") AND `correctness(ubuntu)` ("SKU_CATALOG: DRIFT") *together*. As of ag-ekyq, `skills/skill-builder/scripts/init.sh` regenerates it automatically during scaffold; regenerate by hand for any out-of-band skill edit.
2. **skill-domain-map**`scripts/generate-skill-domain-map.sh` (narrative `N skills` count + per-skill row; verify `--check`).
3. **context-map**`scripts/generate-context-map.sh` (hex roles from frontmatter; CI gate `validate-context-map-drift`).
4. **skill counts**`scripts/sync-skill-counts.sh`. **The `SKILL-TIERS.md` row must be added by hand**`sync-skill-counts` only syncs the *counts* across PRODUCT/ARCHITECTURE/SKILLS/using-agentops; it never adds the row.
5. **codex twin**`scripts/register-new-codex-skill.sh <name> --treatment parity_only|bespoke --reason "…"` (manifest entry + override-catalog + `.agentops-generated.json` marker, atomic + idempotent). Then **hand-author `skills-codex/<name>/prompt.md`** — codex twins are MANUAL; `regen-all.sh` does NOT generate them. Re-run `scripts/regen-codex-hashes.sh` afterward (adding `prompt.md` changes the tree hash). Parity_only minimal `prompt.md` = title + description + an `## Instructions` block ("Load and follow the sibling `SKILL.md`").
6. **narrative skill counts**`scripts/check-registry-drift.sh --fix-counts` (the `N skills` lines in `agentops-skill-domain-map.md` + `agentops-domain-evolution-bdd.md`).
## Embedded sync
If the edit touches `skills/**` content that is embedded (e.g. `using-agentops`), run `cd cli && make sync-hooks` so `cli/embedded/skills/...` stays in sync — else the `codex artifact metadata`/embedded-drift gate fails.
## See also
- [gate-hygiene.md](gate-hygiene.md) — pre-push diff-scope check + pre-existing-vs-mine red triage (the companion discipline that keeps a new-skill PR one-shot-green).
@@ -0,0 +1,55 @@
# Oscillation Detection
## What Is Oscillation?
A goal **oscillates** when it alternates between passing and failing across
evolve cycles. Typically this means the fix for the goal causes a side-effect
that breaks something else, which gets reverted, which re-exposes the original
failure — creating a loop.
Example from cycle-history.jsonl:
```
cycle 5: goal-X → improved
cycle 8: goal-X → fail
cycle 12: goal-X → improved
cycle 15: goal-X → fail
```
## Detection
Count **improved→fail transitions** for the same `target` in
`.agents/evolve/cycle-history.jsonl`:
```bash
# Count oscillations for a given goal — reads via BC3 LoopReaderPort
# (soc-y5vh.4 wrapper) instead of inline JSONL slurp.
scripts/evolve-read-cycle-history.sh recent 0 \
| jq -r "select(.target==\"$GOAL_ID\") | .result" \
| awk 'prev=="improved" && $0=="fail" {count++} {prev=$0} END {print count+0}'
```
## Threshold
**3 oscillations** (improved→fail transitions) within a single session
triggers quarantine. The goal is skipped in Step 3 selection.
## Effect
- Quarantined goals are skipped during work selection (Step 3)
- Skipping a quarantined goal counts as idle (no actionable work found)
- The quarantine is session-scoped — a new session resets the count
- Quarantine events are logged in cycle-history.jsonl with `"result": "quarantined"`
## Recovery
1. Human identifies the root cause (usually a conflict between two goals)
2. Fix the underlying issue manually
3. Start a new evolve session (quarantine resets)
4. Or: remove the quarantine by deleting the goal from the skip list
## Why Not Just Increase the Skip Threshold?
The 3-consecutive-regression skip in Step 3 only catches monotonic failure.
Oscillation is worse — it burns cycles alternating between "fixed" and "broken"
without the stagnation detector ever triggering (because the goal intermittently
passes). The oscillation detector catches this pattern explicitly.
@@ -0,0 +1,128 @@
# Parallel Goal Execution
## Architecture
When `--parallel` is enabled, `/evolve` uses `/swarm` to execute multiple independent
goal improvements concurrently instead of fixing one goal per cycle.
```
/evolve --parallel (Fitness Loop)
├─ Step 2: Measure ALL goals
├─ Step 3: Select top N independent failing goals (max_parallel, default 3)
│ └─ select_parallel_goals: heuristic independence via check-script overlap
├─ Step 4: Parallel execution via /swarm
│ ├─ TaskCreate for each selected goal
│ ├─ Artifact isolation: .agents/evolve/parallel-rpi/{goal.id}/
│ ├─ Git isolation: /swarm --worktrees (each worker in /tmp/evolve-{goal.id})
│ └─ /swarm spawns N fresh-context workers, each runs full /rpi cycle:
│ └─ research → plan → pre-mortem → crank → vibe → post-mortem
├─ Step 5: Single regression gate (re-measure ALL goals after wave)
│ ├─ If ANY goal regressed → revert ENTIRE parallel wave
│ └─ If clean → log cycle with goal_ids array
└─ Step 6-7: Log, loop (same as sequential)
```
## The Fractal Pattern
Swarm is the universal coordination primitive at every level:
```
LEVEL 0: /evolve --parallel
└─ /swarm (parallel goal improvements) ← NEW: swarm at evolve level
└─ LEVEL 1: /rpi (per-goal lifecycle)
└─ research → plan → crank → vibe → post-mortem
└─ LEVEL 2: /crank (epic execution)
└─ /swarm (parallel issue implementation) ← existing: swarm at crank level
└─ LEVEL 3: workers (atomic tasks)
```
Each level creates fresh context for the next (Ralph Wiggum pattern).
The pattern is always: **one leader + N fresh-context workers + validation + cleanup**.
## Goal Independence Detection
`select_parallel_goals` uses a heuristic check:
1. Start with highest-weight failing goal
2. For each remaining eligible goal (weight-sorted):
- Compare check commands for shared scripts/paths
- If independent: add to selection (up to max_parallel)
- If overlapping: skip (handled in next cycle)
**This is a heuristic, not a guarantee.** Goals don't declare which files their
improvements will modify — only which scripts verify them. Two goals with different
check scripts may still modify overlapping files.
**The regression gate (Step 5) is the real safety net.** If parallel goals conflict,
the regression check detects it and reverts the entire wave. This makes false
negatives in independence detection safe (they just cost one wasted cycle).
## Artifact Isolation
Each parallel /rpi worker needs isolated artifact directories to prevent collision:
| Directory | Purpose | Isolation |
|-----------|---------|-----------|
| `.agents/evolve/parallel-rpi/{goal.id}/` | /rpi phase summaries, next-work | Per-goal subdirectory |
| `.agents/evolve/parallel-results/{goal.id}.md` | Worker result summary | Per-goal file |
| `/tmp/evolve-{goal.id}` | Git worktree | Per-goal worktree via /swarm |
Without isolation, N concurrent /rpi cycles would collide on `.agents/rpi/`
(phase summaries, next-work.jsonl) and git index locks.
## Git Isolation
Parallel workers MUST use worktree isolation (via `/swarm --worktrees`):
- Each worker operates in `/tmp/evolve-{goal.id}` worktree
- No git lock conflicts (each worktree has its own index)
- Lead merges worktrees after all complete, before regression gate
- On regression: revert all merged commits using `cycle_start_sha`
## Regression Handling
**Sequential mode:** Revert commits from one goal's /rpi cycle.
**Parallel mode:** Revert ALL commits from the entire parallel wave.
The `cycle_start_sha` (captured before the wave) anchors the revert point.
All N goal improvements are rolled back together — even goals that individually
succeeded. This is by design: if goals interfere, we can't know which one
caused the regression without testing each in isolation.
## Cycle History Schema
Sequential cycles use `target` (string). Parallel cycles use `goal_ids` (array) with `parallel: true`:
```jsonl
{"cycle": 1, "target": "test-pass-rate", "result": "improved", "sha": "abc1234", ...}
{"cycle": 2, "goal_ids": ["doc-coverage", "lint-clean"], "result": "improved", "sha": "def5678", "parallel": true, ...}
```
Legacy entries may use `goal_id` instead of `target` and `commit_sha` instead of `sha`. Tools should handle both.
## Compounding
Each parallel /rpi worker runs its own /post-mortem, which feeds the knowledge
flywheel independently. Learnings from all N parallel cycles compound into the
flywheel, feeding the next /evolve cycle.
## When to Use
| Scenario | Mode |
|----------|------|
| 1-2 failing goals | Sequential (default) — parallelism overhead not worth it |
| 3+ independent failing goals | `--parallel` — significant speedup |
| Goals with overlapping files | Sequential — parallel would cause conflicts |
| First run on new repo | Sequential — learn the codebase before parallelizing |
## Constraints
- Max 5 parallel goals per wave (`--max-parallel` cap)
- Default 3 parallel goals (balance between speedup and resource usage)
- Each /rpi worker needs a full context window — budget accordingly
- Worktree isolation required (no shared-worktree parallel /rpi)
@@ -0,0 +1,81 @@
# Mandatory checkpoint: session-PR threshold (soc-n75z)
> Stop reason #6 of `/evolve`. NOT terminal — gates the next cycle, not the loop's existence. Derivation: the 2026-05-19 and 2026-05-20 sessions both shipped 5-6 PRs while the agent self-graded "HEALTHY" without running a real council; STOP got written as if it were post-mortem completion. This procedure closes that gap.
## When this fires
At Step 7 boundary, after a cycle whose result is `improved` or `regressed`
(`$session_pr_count` is the canonical count from `scripts/session-pr-scope.sh --count`):
```bash
if [ "$session_pr_count" -ge "${SESSION_PR_THRESHOLD:-5}" ] \
&& [ "$session_pr_count_at_last_checkpoint" -lt "${SESSION_PR_THRESHOLD:-5}" ]; then
# checkpoint fires once per crossing — not repeatedly at every cycle past 5
run_postmortem_checkpoint
fi
```
The check is **edge-triggered** (fires when the counter first crosses the threshold), not level-triggered. After PASS/WARN, the loop continues until either another stop reason fires OR another threshold crossing — e.g., at `2 × SESSION_PR_THRESHOLD = 10`, the checkpoint re-fires for a second post-mortem.
## What it MUST do
1. **Invoke `/post-mortem --deep`** on the cycles since the last checkpoint (or session start). `--deep` runs 3 same-vendor judges in adversarial posture.
- Do NOT use `--mixed`. Cross-vendor (Codex availability) is an infrastructure dependency that should not block the loop gate. Reserve `--mixed` for strategic architecture decisions (e.g., the DUEL.md precedent), not per-session health checks.
- Do NOT use single-judge mode. Single-judge sweeps are too weak for a gate that enables continued autonomous work — the whole point of the checkpoint is adversarial counter-balance against the author who just wrote the cycles.
2. **Wait for the verdict file** at `.agents/council/<date>-<scope>-postmortem/verdict.md`. The verdict file's first non-blank line must start with `## Council Verdict: <PASS|WARN|FAIL>`.
3. **Branch on verdict:**
| Verdict | Action |
|---|---|
| `PASS` | Continue the ladder. Next cycle's `notes` field MUST record the verdict path. |
| `WARN` | Continue the ladder. Next cycle's `notes` field MUST emit the council's caveats verbatim — they need to trace through `cycle-history.jsonl` so the loop's recorded behavior reflects what was acknowledged. |
| `FAIL` | Write `.agents/evolve/STOP` with the verdict path embedded. Do NOT clear. Surface the council's required-follow-up list to the operator. |
4. **Treat non-convergence as FAIL.** If after 600 seconds the verdict file is missing, malformed, or the council's judges disagreed irreconcilably after a second pass, default to FAIL. **Absence of evidence is NOT evidence of pass.** The 2026-05-20 failure mode was an empty 2-minute cron pass treated as council completion; this rule is the corrective.
## What it MUST NOT do
- **Never self-grade.** The agent who ran the cycles is the same agent doing the post-mortem. Self-attestation is definitionally invalid for the gate; council enforcement is the structural requirement.
- **Never write `.agents/evolve/STOP` without a council verdict file.** STOP without a verdict is the 2026-05-20 anti-pattern — the file becomes a self-imposed pause that the same agent (or operator) clears without rigor. Every STOP written by this checkpoint must cite a verdict path that resolves to a FAIL verdict.
- **Never run `/post-mortem` without `--deep` from this checkpoint.** A light-touch cron-fire pass that writes learnings but skips Phase 1 council does not satisfy the gate. The 2026-05-20 cron `/post-mortem` was insufficient by design.
## Pre-creation signal (opt-in, not shipped)
The pre-creation counterpart to this post-merge checkpoint — `hooks/session-pr-counter.sh` (PR #362, soc-1aou), which fired on `gh pr create` at `count >= threshold - 1` — was **removed** in the 3.0 hookless teardown (#511). AgentOps ships no hook. The reusable session-PR count it provided now lives in `scripts/session-pr-scope.sh` (the canonical `$session_pr_count` source — `scripts/session-pr-scope.sh --count`). Re-author the always-on pre-creation signal as an **opt-in** hook via the hooks-authoring skill if you want it.
This checkpoint is the *post-merge* gate — it fires after PR #N+1 has merged, before cycle N+1 enters Step 1. It is **mandatory** and cannot be bypassed (the loop genuinely waits on the verdict file), unlike the advisory pre-creation signal:
| Layer | Trigger | Default | Status |
|---|---|---|---|
| `scripts/session-pr-scope.sh` (wrap as an opt-in hook via hooks-authoring) | `gh pr create` at `count >= threshold-1` | Advisory | Reusable count; not shipped as a hook (removed in #511) |
| `evolve` checkpoint #6 | Cycle boundary after `count >= threshold` | Mandatory | Structural council enforcement before the next cycle starts |
## Configuration
| Variable | Default | Effect |
|---|---|---|
| `SESSION_PR_THRESHOLD` | `5` | Matches soc-waxr documentation default. The 2026-05-19 session (6 PRs, 3 self-corrections) and 2026-05-20 session (5 PRs, 1 self-correction + 1 confirmation-bias verdict) are the two derivation points. Do not change without a third data point. |
| `EVOLVE_POSTMORTEM_TIMEOUT_SEC` | `600` | Max wait for verdict file. Past this, treat as FAIL. |
| `EVOLVE_POSTMORTEM_COUNCIL_FLAG` | `--deep` | Override only with operator sign-off in a follow-up PR; reverting to single-judge or `--mixed` requires a council justifying the change. |
## Failure modes this closes
The 2026-05-20 post-mortem (`.agents/council/2026-05-20-evolve-204-208-postmortem/verdict.md`) found WARN with Q4 FAIL:
> The "post-mortem" that "cleared" cycle 208 was a cron-fire that wrote 5 learning files and appended a harvest entry. No Phase 1 (council) ran. No Phase 2 extraction with adversarial review. No Phase 3 backlog processing. No verdict file was produced.
>
> Cycle 208's notes contain the string "HEALTHY verdict from /post-mortem cron" — a verdict that does not exist in any file the council directory contains.
That cycle-history entry transformed an absence (no council ran) into a positive claim (clearance exists) in an append-only ledger. Stop reason #6 prevents that pattern by requiring a real verdict file path before continuing.
## Cross-references
- `skills/post-mortem/SKILL.md` — the post-mortem skill this checkpoint invokes
- `skills/council/SKILL.md` — the council skill `--deep` runs against
- `skills/ship-loop/SKILL.md` — the soc-waxr session-scope doctrine source
- `scripts/session-pr-scope.sh` — canonical session-PR count source (the pre-creation hook was **removed** in #511; re-author as an opt-in hook via hooks-authoring)
- `CLAUDE.md` § "Autonomous-session scope" — top-level doctrine reference
- `.agents/council/2026-05-20-evolve-204-208-postmortem/verdict.md` — the derivation council (local; gitignored)
@@ -0,0 +1,64 @@
# Pre-Flight Schema Check — Stop Migration Cycles That Can't Succeed
Three phase-2 port migrations attempted in cycles 156-157 of this repo all failed for the same structural reason: the port surface projected ~5 fields, the consumer read ~13 fields. The migration would have silently dropped 8+ fields the consumer relied on. The lesson is captured in `docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md`. This document encodes that lesson as a pre-cycle check.
## When this gate runs
Before invoking `/rpi` on a cycle whose selected work matches any of:
- title contains `migrate`, `rewire`, `route through`, `port-ify`, `adapt to <Port>`
- title contains `<Module> → <Port>Port` or similar arrow syntax
- bd issue has label `migration` or `bc-port-adoption`
- selected_source is `harvested` and the queue entry has `kind: "migration"`
## Procedure
1. **Identify the port surface.** From the work title, locate the port type definition (typically `cli/internal/ports/<port>.go`). Extract the projected struct fields.
2. **Sample two consumer call sites.** From the work description, find the function or file being migrated. Grep for *its* current consumers — the call sites that depend on the data the migration will route through the port. Pick two representative ones (different files, different formatting/decision shapes).
3. **Field-fit comparison.**
```bash
PORT_FIELDS=$(grep -E '^\s+[A-Z][a-zA-Z0-9_]+\s+' cli/internal/ports/<port>.go | awk '{print $1}' | sort -u)
CONSUMER_FIELDS=$(grep -oE '\.[A-Z][a-zA-Z0-9_]+\b' <consumer-files> | sed 's/^\.//' | sort -u)
comm -23 <(echo "$CONSUMER_FIELDS") <(echo "$PORT_FIELDS")
```
The output is the set of fields the consumer reads that the port does NOT project.
4. **Decision.**
| Missing fields | Action |
|---|---|
| 0 | Migration is safe. Proceed with `/rpi`. |
| 12 | Migration with widening. Add the missing fields to the port + writer + tests in the same cycle. Mark mode `port-widen-and-migrate`. |
| 3+ | **Abort the migration cycle.** File a port-widening bd issue with the field list, log this cycle as `unchanged` with `selected_source: "pre-flight-schema-fail"`, return to Step 3 selection. |
5. **Special case — zero-data target.** If the consumer reads a field (e.g. `Target`) that exists in the type definition but *no actual entries in the production data store have that field set*, the consumer is dead-code-in-disguise. Don't widen — investigate whether the consumer was ever wired. Log as `selected_source: "pre-flight-dead-target"`.
## Why this check is cheap
A migration cycle costs ~15-30 minutes of /rpi (discovery + crank + validation). The pre-flight check is two `grep` calls and one `comm` — under 30 seconds. Catching even one bad migration pays for ~50 pre-flight runs. In this repo, three cycles spent on bad migrations (156, 157, plus the soc-0pku bead) would have been caught by 90 seconds of pre-flight.
## What this does NOT replace
This is a *cycle-selection* gate, not a code-quality gate. Validating the actual migration code still belongs to `/rpi` → `/validate`. The pre-flight only answers "is this migration even shaped right for the current port?"
## Worked example — cycle 157 (soc-0pku)
Selected work: `context_assemble.go → LoopReaderPort`
```
PORT_FIELDS=Number,Mode,Result,Commit,Milestone # 5
CONSUMER_FIELDS=Timestamp,Cycle,Target,GoalIDs,Result,Status,Sha,CanonicalSha,LogSha,GoalsPassing,GoalsTotal,Summary,Error # 13
Missing: Timestamp,Target,GoalIDs,Status,Sha,CanonicalSha,LogSha,GoalsPassing,GoalsTotal,Summary,Error # 11
```
Missing fields = 11. Threshold 3+. **Abort.** File port-widening issue: "Widen CycleEntry: +Timestamp,+Summary at minimum; full set deferred." Cycle is logged as `unchanged`, ladder advances.
This is exactly what cycle 161 ended up doing reactively — adding `StartedAt` and `Title` after the migrations bounced. With pre-flight, the widening would have happened *before* burning three cycles on doomed migrations.
## See also
- `docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md` — the source observation
- `references/scout-mode.md` — first-class result for cycles that intentionally don't execute
- `references/metronome-gate.md` — sibling cross-cycle gate
@@ -0,0 +1,70 @@
# Quality Mode
## When to Use
Use `--quality` when:
- Post-mortem findings are accumulating faster than they're consumed
- All GOALS pass but `next-work.jsonl` has unconsumed high-severity items
- You want to resolve context-hot findings from a just-completed epic
- Running immediately after `/post-mortem` to action its findings
Do NOT use `--quality` when:
- GOALS have critical failures (build broken, tests failing)
- No `next-work.jsonl` exists or is empty
- You want standard fitness-driven improvement
## Quality Score
Simple severity-weighted score:
```
score = 100 - (high_count * 10) - (medium_count * 3)
```
Where counts are unconsumed findings remaining in next-work.jsonl.
| Score | Meaning |
|-------|---------|
| 90-100 | Excellent — few or no findings remaining |
| 70-89 | Good — medium-severity items remain |
| 50-69 | Attention needed — high-severity items remain |
| <50 | Quality debt — many high-severity findings |
## Priority Cascade (Quality Mode)
1. High-severity unconsumed findings → /rpi
2. Medium-severity unconsumed findings → /rpi
3. Open ready beads → prefer /rpi with bead context
4. Failing GOALS.yaml goals and directive gaps → /rpi
5. Testing improvements → /rpi
6. Validation tightening / bug-hunt / drift mining → /rpi
7. Feature suggestions → durable work + /rpi
8. Nothing after repeated generator passes → dormancy fallback
## Marking Findings Consumed
When evolve picks a finding from next-work.jsonl, claim it first:
- Set `claim_status: "in_progress"`
- Set `claimed_by: "evolve-quality:cycle-N"`
- Set `claimed_at: "<timestamp>"`
- Keep `consumed: false` until the /rpi cycle and regression gate both succeed
If the /rpi cycle fails (regression), clear the claim and leave the finding available.
## Artifacts
| File | Purpose |
|------|---------|
| `cycle-history.jsonl` | Same as standard mode + `quality_score` field |
| `fitness-latest.json` | Same as standard mode (goals measurement) |
| `quality-trajectory.md` | Quality score over time (written at teardown) |
## Interaction with Standard Mode
Quality mode and standard mode share:
- The same cycle-history.jsonl
- The same fitness measurement (goals are still checked)
- The same dormancy guard (repeated empty queue + generator passes)
- The same circuit breaker (60 minutes)
They differ in work selection priority at the top of the ladder: quality mode picks findings first, then still falls through to the same producer layers before dormancy is allowed.
@@ -0,0 +1,77 @@
# Scout Mode — First-Class Cycle Result
Scout mode is a result type alongside `improved`, `regressed`, `harvested`, and `idle`. A scout cycle reads a candidate work item, validates its scope and shape, and either annotates the queue entry with a deeper plan or splits it into smaller beads — without executing the underlying work.
## When to scout
Use scout-mode whenever a Step 3 selection meets any of these criteria:
- The work touches **> 5 files** and is **not** a mechanical batch (a single script-driven rewrite across N similar files).
- The work introduces a **new shape**: schema field, frontmatter carrier, JSON top-level key, validator rule, contract surface, or struct field that downstream consumers will read.
- The work is **operator-level epic work**: multiple cooperating sub-systems must change together (e.g. emitter + consumer + validator + tests + docs in one cycle).
- The **current cycle is > 5 productive cycles into the session** and the work would extend the implementation arc rather than close it.
The scope-filter step (Step 3.0 in `SKILL.md`) consults these heuristics before any work is claimed.
## What a scout cycle does (soc-5qit: split-or-defer, never bail)
A scout cycle is **work**, not a stop. It MUST produce exactly one of:
### Path A: Split (preferred when the queue is light and the work is decomposable)
1. Read the target file(s) named in the work item (no edits).
2. Map the **current shape** at the relevant boundary (what fields exist, what callers read it, what validators enforce).
3. Run `bd create` to decompose the candidate into 2-N child beads, each ≤5 files and single-shape:
```bash
bd create "Slice 1 of <parent-title>: <smaller-scope>" \
--description="Carved from <parent-id> by scout-mode. Scope: <files/contract>" \
--deps discovered-from:<parent-id> -t task -p <inherit> --json
```
4. Update the parent bead with `bd update <parent-id> --notes "scout-split into: <child-ids>"`.
5. **Re-enter Step 3** so the smallest new child OR another ready bead gets claimed THIS cycle.
### Path B: Defer (preferred when the queue has other ready beads)
1. Read the target file(s) briefly to confirm the scope assessment.
2. Append a `disposition: defer:<reason>` block to the work item.
3. **Re-enter Step 3** so the next-priority ready bead gets claimed THIS cycle. The big candidate stays available for a future session with lighter context.
### Path C: Park (operator-level epic, no obvious split)
1. `bd update <id> --status blocked --notes "scope-too-big: <why>; needs operator triage"`.
2. **Re-enter Step 3** for the next ready bead.
A scout cycle does NOT:
- Run `/rpi` against the original too-big candidate.
- Edit any source file outside `.agents/rpi/next-work.jsonl` and bd metadata.
- Commit code (bd updates land in Dolt automatically).
- **Exit the loop** — if `bd ready` returns ≥1 unblocked bead, the cycle MUST claim and work one of them after the scout decision. (soc-5qit invariant.)
## Logging a scout cycle
Append to `cycle-history.jsonl` with:
```json
{"cycle": N, "result": "scout", "selected_source": "<source>", "work_ref": "<id>",
"net_change": 0, "commit": null,
"milestone": "Scouted <work>; recommendation: <split|park|smaller-slice>"}
```
The `result: scout` value is canonical alongside `improved | regressed | harvested | idle | unchanged`.
## Daily learning capture
Scout cycles still get a micro-capture line. Use the form:
```
- cycle N [scout] <work-ref>: <what was learned about the shape> INSIGHT: <tag>
```
## Promotion path
When a scouted item later becomes single-cycle-doable (because earlier prerequisites landed), drop the `disposition` block and let normal Step 3.1 selection pick it up.
## Why scout is not "idle"
`idle` means "no actionable work in any layer". Scout means "actionable work found but the shape is wrong for this cycle's budget". These are structurally different stop reasons. Conflating them masks the real failure mode: the loop has work but can't safely run it.
@@ -0,0 +1,175 @@
# Snapshot Pattern For Long-Cycle Gates
Gates that depend on **multi-session corpus state** (flywheel
compounding, corpus freshness, knowledge-registry health, AOP-claim
coverage) cannot be evaluated by a single CI run on a greenfield
checkout — the corpus isn't there. The 4-step **snapshot pattern**
converts these into single-commit-validatable artifacts: the live gate
computes the metric on demand, the operator snapshots it to a
git-tracked JSON file with a staleness window, and the CI gate
validates the snapshot file rather than re-running the live metric.
This is how disk-is-truth (see long-loop-discipline (disk-is-truth axiom))
extends into CI: the **disk file is the gate's truth surface**, and
the live metric only refreshes it.
## The 4-Step Pattern
### 1. Live Gate (Operator-Local)
A script that computes the multi-session metric on demand. Runs in
the operator's full corpus, not in CI.
```bash
# Example: live gate for flywheel compounding
bash scripts/check-flywheel-compounding.sh
# Internally: ao flywheel status → exits 0 if compounding, 1 if not
```
### 2. Snapshot Command (Operator Cadence)
Wraps the live result in a JSON envelope with `recorded_at`,
`git_sha`, and the metric payload. Writes to a git-tracked path.
```bash
# Example: snapshot the live result
bash scripts/snapshot-flywheel-compounding.sh
# Writes docs/releases/flywheel-compounding-snapshot.json
```
The snapshot file is **the durable proof**. Operator runs the
snapshot script on cadence (e.g., before each release, or weekly).
### 3. CI Gate (Snapshot Validation)
CI does **not** re-run the live metric (it can't — no corpus). It
validates the snapshot file:
- Structural shape (required keys present)
- Staleness threshold (e.g., < 14 days old)
- Asserted value (`compounding == true`, `freshness_score >= N`)
```bash
# Example: CI gate validates the snapshot
bash scripts/check-flywheel-compounding-snapshot.sh
# Fails if snapshot missing | > 14 days old | shows metric flipped
```
### 4. Wire As A Regular Gate
- `GOALS.md` row naming the metric
- `.github/workflows/validate.yml` CI job
- `scripts/pre-push-gate.sh` lane invocation
- `AGENTS.md` "CI Jobs and What They Check" row
- (per the parity-surface inventory — see
`docs/learnings/2026-05-12-parity-surface-inventory-grew-from-4-to-7-across-cycles-64-70.md`)
Once wired, the snapshot pattern looks like a regular gate; the
operator-cadence-refresh is the only non-standard piece.
## Why This Matters
Without this pattern, long-cycle gates stay in the Roadmap column
forever because **CI can't run them green** — a clean CI runner has
no corpus, no flywheel history, no AOP-claim accumulation. Three
GOALS.md gates closed in one /evolve session because of this pattern
(G1 flywheel-snapshot, D11 corpus-freshness, D10 workbench-delta).
The pattern is the implementation of long-loop-discipline (disk-is-truth axiom)
at the CI layer: the disk file is the load-bearing state, the live
metric is the refresh path, and CI consumes the disk.
## Evidence (anchored)
> "Gates that depend on multi-session corpus state (e.g., flywheel
> compounding, corpus freshness, knowledge registry health) cannot be
> evaluated by a single CI run on a greenfield checkout — the corpus
> isn't there. … The snapshot file is the durable proof. The CI gate
> ensures the operator refreshes it on cadence. Each surface is
> testable on a clean CI runner without N sessions of warmup."
`.agents/learnings/2026-05-11-quick-snapshot-pattern-for-long-cycle-gates.md`
(retro-quick, validate phase)
> "Applications validated this session: corpus snapshot (D11, cycle
> 22), flywheel compounding (G1, cycle 24), workbench delta (D10,
> cycle 26), README claim manifest (PG4, cycle 30), AOP-CLAIM
> all-claims map (cycle 35). The structural-floor contract gate
> (cycle 41) is the generalization — same pattern at the corpus level."
— same source
## How To Apply (When Adding A New Long-Cycle Gate)
1. **Identify the metric.** Does it need a corpus, history, or
multi-session state to compute? If yes, snapshot pattern. If no, a
regular CI gate suffices.
2. **Write the live gate** under `scripts/check-<metric>.sh`. Exit 0
on success, non-zero on regression. This runs in the operator's
environment.
3. **Write the snapshot command** under `scripts/snapshot-<metric>.sh`.
Output JSON to a git-tracked path:
```json
{
"recorded_at": "2026-05-18T19:42:00Z",
"git_sha": "abc1234",
"metric_name": "flywheel_compounding",
"value": true,
"details": { /* metric-specific payload */ }
}
```
4. **Write the snapshot validator** under
`scripts/check-<metric>-snapshot.sh`. Read the JSON, assert shape +
staleness + value.
5. **Wire all 7 parity surfaces** per the parity-surface inventory:
pre-push lane, validate.yml job, summary.needs[], summary echo,
AGENTS.md row, registry.json (if a new hook), bats stub (if pre-push).
6. **Set a refresh cadence.** Document in `AGENTS.md` or `GOALS.md`
when the operator runs the snapshot script (before release,
weekly, before-each-evolve-session).
## Failure Modes
- **Skipping step 4 (snapshot validator).** Without the validator,
the snapshot file drifts undetected — operators stop refreshing it,
and CI claims green forever. The validator forces the cadence.
- **Snapshot in chat / ephemeral state.** The snapshot MUST be
git-tracked. A snapshot in a session JSONL is invisible to CI.
- **Staleness window too long.** > 30 days means the gate
effectively never fails. Tighten to 7-14 days.
- **Live gate and snapshot validator disagree on shape.** Schema
drift between the two scripts; CI passes but the metric is wrong.
Lock the JSON shape in the snapshot command's documentation.
## Applications (Empirical Reference)
The pattern shipped 6 times across cycles 22-41 of the 2026-05-11
evolution-roadmap drain:
| Cycle | Metric | Snapshot path |
|---|---|---|
| 22 (D11) | corpus freshness | `docs/releases/corpus-snapshot.json` |
| 24 (G1) | flywheel compounding | `docs/releases/flywheel-compounding-snapshot.json` |
| 26 (D10) | workbench delta | `docs/releases/workbench-delta-snapshot.json` |
| 30 (PG4) | README claim manifest | `docs/releases/readme-claim-manifest.json` |
| 35 | AOP-CLAIM all-claims map | `docs/releases/aop-claims-all-snapshot.json` |
| 41 | structural-floor contract gate (the generalization) | per-contract snapshot |
Reuse-rate after first ship: 5 reuses in 19 cycles. The pattern is
durable.
## See Also
- long-loop-discipline (disk-is-truth axiom) — the principle this
pattern implements at the CI layer (sibling reference in this dir,
landing concurrently)
- [cycle-history.md](cycle-history.md) — the JSONL ledger pattern
(same disk-truth principle, different consumer)
- [gate-hygiene.md](gate-hygiene.md) — gate-output parsing; complements
snapshot validation
- `docs/learnings/2026-05-12-parity-surface-inventory-grew-from-4-to-7-across-cycles-64-70.md`
— the 9-surface checklist for wiring any new gate, including
snapshot gates
@@ -0,0 +1,109 @@
# Teardown Procedure
**Auto-run /post-mortem on the full evolution session:**
```
/post-mortem "evolve session: $CYCLE cycles, goals improved: X, harvested: Y"
```
This captures learnings from the ENTIRE evolution run (all cycles, all /rpi invocations) in one council review. The post-mortem harvests follow-up items into `next-work.jsonl`, feeding the next `/evolve` session.
**Compute session fitness trajectory:**
```bash
# Check if both current-era baseline and final snapshot exist
GOALS_FILE=""
if [ -f GOALS.md ]; then
GOALS_FILE="GOALS.md"
elif [ -f GOALS.yaml ]; then
GOALS_FILE="GOALS.yaml"
fi
ACTIVE_BASELINE_PATH=""
if [ -n "$GOALS_FILE" ]; then
ERA_ID="goals-$(shasum -a 256 "$GOALS_FILE" | awk '{print substr($1, 1, 12)}')"
ACTIVE_BASELINE_PATH="$(ls -t ".agents/evolve/fitness-baselines/$ERA_ID"/*.json 2>/dev/null | head -1 || true)"
fi
if [ -n "$ACTIVE_BASELINE_PATH" ] && [ -f "$ACTIVE_BASELINE_PATH" ] && [ -f .agents/evolve/fitness-latest.json ]; then
baseline = load("$ACTIVE_BASELINE_PATH")
final = load(".agents/evolve/fitness-latest.json")
# Compute delta — goals that flipped between baseline and final
improved_count = 0
regressed_count = 0
unchanged_count = 0
delta_rows = []
for final_goal in final.goals:
baseline_goal = baseline.goals.find(g => g.id == final_goal.id)
baseline_result = baseline_goal ? baseline_goal.result : "unknown"
final_result = final_goal.result
if baseline_result == "fail" and final_result == "pass":
delta = "improved"
improved_count += 1
elif baseline_result == "pass" and final_result == "fail":
delta = "regressed"
regressed_count += 1
else:
delta = "unchanged"
unchanged_count += 1
delta_rows.append({goal_id: final_goal.id, baseline_result, final_result, delta})
# Write session-fitness-delta.md with trajectory table
cat > .agents/evolve/session-fitness-delta.md << EOF
# Session Fitness Trajectory
| goal_id | baseline_result | final_result | delta |
|---------|-----------------|--------------|-------|
$(for row in delta_rows: "| ${row.goal_id} | ${row.baseline_result} | ${row.final_result} | ${row.delta} |")
**Summary:** ${improved_count} improved, ${regressed_count} regressed, ${unchanged_count} unchanged
EOF
# Include delta summary in user-facing teardown report
log "Fitness trajectory: ${improved_count} improved, ${regressed_count} regressed, ${unchanged_count} unchanged"
fi
```
**Then write session summary:**
```bash
cat > .agents/evolve/session-summary.md << EOF
# /evolve Session Summary
**Date:** $(date -Iseconds)
**Cycles:** $CYCLE of $MAX_CYCLES
**Goals measured:** $(wc -l < GOALS.yaml goals)
## Cycle History
$(cat .agents/evolve/cycle-history.jsonl)
## Final Fitness
$(cat .agents/evolve/fitness-latest.json)
## Post-Mortem
<path to post-mortem report from above>
## Next Steps
- Run \`/evolve\` again to continue improving
- Run \`/evolve --dry-run\` to check current fitness without executing
- Create \`~/.config/evolve/KILL\` to prevent future runs
- Create \`.agents/evolve/STOP\` for a one-time local stop
EOF
```
Report to user:
```
## /evolve Complete
Cycles: N of M
Goals improved: X
Goals regressed: Y (reverted)
Goals unchanged: Z
Post-mortem: <verdict> (see <report-path>)
Run `/evolve` again to continue improving.
```
@@ -0,0 +1,204 @@
# /evolve — Work-Selection Ladder (Step 3, full procedure)
> Extracted from `skills/evolve/SKILL.md` Step 3 to keep the skill under the 10000-token ceiling (soc-opq5). SKILL.md keeps the summary + this pointer; this file owns the full per-rung procedure, code blocks, quality cascade, and dormancy hard-gate.
Selection is a ladder, not a one-shot check. After every productive cycle, return to the TOP of this step and re-read the queue before considering dormancy.
**Programmatic recommendation (soc-g2qd wire):** when present, consult the ladder primitive first and prefer its `.recommended_bead`; the rungs below are the cross-check + fallback.
```bash
ao loop next-work --help >/dev/null 2>&1 && RECO_BEAD=$(ao loop next-work --json 2>/dev/null | jq -r '.recommended_bead // empty')
```
When a repo-local program contract exists, apply a scope filter before Step 4:
- candidate work that clearly requires immutable-scope edits is not eligible for direct execution
- prefer harvested, beads, goals, and generated work that can plausibly land within mutable scope
- if the selected item is inherently out of scope, escalate it or convert it into durable follow-up work instead of invoking `/rpi` and hoping discovery widens scope
**Step 3.0: Scope filter — split-or-defer, never bail (soc-5qit)**
Before claiming a candidate, gate scope vs session budget. If the work touches > 5 non-uniform files, introduces a new shape (schema field, validator, contract surface), is operator-level epic work, OR `PRODUCTIVE_THIS_SESSION > 5` and would extend an arc rather than close one — route to **scout-mode**, which MUST produce one of:
1. **Split**`bd create` 2-N child beads (each ≤5 files, single-shape) with `--deps discovered-from:<parent-id>`, annotate parent, then **re-enter Step 3** so the smallest child (or another ready bead) gets claimed THIS cycle.
2. **Defer** — annotate the candidate with `defer:<reason>` and re-enter Step 3 so the next-priority ready bead gets claimed.
3. **Park** (rare) — `bd update <id> --status blocked --notes "scope-too-big"` and re-enter Step 3.
Scout NEVER returns "no work done." If `bd ready` ≥1, the loop MUST claim one this cycle. See `references/scout-mode.md` and `references/mechanical-batches.md`.
**Metronome gate:** read `mode_repeat_streak` from `session-state.json`. If `>= 3` AND the candidate would repeat the trailing run's `mode`, BLOCK this rung and jump to the next. If `>= 5`, record the gap as bead/provenance evidence and require operator override. See `references/metronome-gate.md`.
**Step 3.1: Harvested work first**
Read `.agents/rpi/next-work.jsonl` and pick the highest-value unconsumed item. Prefer exact repo match, then concrete implementation work, then higher severity. Read `references/knowledge-loop-integration.md` for the claim/release protocol.
**Step 3.2: Open ready beads**
If no harvested item is ready, check `bd ready`. Pick the highest-priority unblocked issue.
**Step 3.3: Failing goals and directive gaps** (skip if `--beads-only`)
First assess directives, then goals:
- top-priority directive gap from `ao goals measure --directives`
- highest-weight failing goals (skip quarantined oscillators)
- lower-weight failing goals
This step exists even when all queued work is empty. Goals are the third source, not the stop condition.
```bash
DIRECTIVES=$(ao goals measure --directives 2>/dev/null)
FAILING=$(jq -r '.goals[] | select(.result=="fail") | .id' .agents/evolve/fitness-latest.json | head -1)
```
**Oscillation check:** Before working a failing goal, check if it has oscillated (improved-to-fail transitions >= 3 times). If so, quarantine it and try the next goal. See `references/oscillation.md` and `references/fitness-scoring.md` for the detection procedure.
**Duplicate-work guard (mandatory before every generator `bd create`).** The
generators below (3.43.7) and the Step-4 Split rung all create beads. A stale
phase-1 handoff repeatedly re-seeded beads for work already covered by an
existing bead or merged PR (ag-b8m≈ag-jov, ag-6kw≈ag-c2i — ag-6jt). Before
`bd create`, run the guard; skip creation when it reports a duplicate:
```bash
skills/evolve/scripts/duplicate-work-guard.sh "<candidate title>" || {
echo "skip: existing work already covers this surface"; }
# exit 1 + "DUPLICATE: <id> [<status>] <title>" → an open/closed bead already
# covers it (exact title OR significant-token overlap). exit 0 → safe to create.
```
This complements the loop's origin/main fast-forward: the cron runs
`skills/evolve/scripts/sync-main-to-origin.sh` before discovery (wired into
`scripts/overnight-evolve.sh`), which fetches origin and fast-forwards local
`main` to `origin/main` so discovery diffs candidate slices against the true
merge base — already-merged work reports as done, not re-seen (ag-6jt). Run it
manually in any rpi worktree whose local `main` may be stale:
```bash
skills/evolve/scripts/sync-main-to-origin.sh
# → "DIFF_BASE: origin/main <sha>" — diff slices against THIS, not local main.
```
**Step 3.4: Testing improvements**
When queues and goals are empty, generate concrete testing work via `/test`:
```
if --no-lifecycle is NOT set:
Skill(skill="test", args="coverage")
Only files with < 40% coverage become queue items (severity threshold).
```
If `/test` is unavailable or `--no-lifecycle` is set, fall back to manual scanning:
- find packages/files with thin or missing tests
- look for missing regression tests around recent bug-fix paths
- identify flaky or absent headless/runtime smokes
Convert any real finding into durable work:
- add a bead when the work needs tracked backlog ownership, or
- append a queue item under the shared next-work contract when it should flow directly back into `/rpi`
**Step 3.5: Validation tightening and bug-hunt passes**
If testing improvement generation returns nothing, run lifecycle generators then bug-hunt sweeps:
```
if --no-lifecycle is NOT set:
a) Skill(skill="deps", args="audit")
Only deps with CVSS >= 7.0 or 2+ major versions behind become queue items.
b) if perf-sensitive code detected (benchmarks exist, hot path patterns):
Skill(skill="perf", args="profile --quick")
Convert significant perf findings to queue items.
```
If lifecycle generators return nothing or are skipped, fall back to manual sweeps:
- missing validation gates
- weak lint/contract coverage
- bug-hunt style audits for risky areas
- stale assumptions between docs, contracts, and runtime truth
Again: convert findings into beads or queue items, then immediately select the highest-priority result and continue.
**Step 3.6: Drift / hotspot / dead-code mining**
If the prior generators are empty, mine for complexity debt via `/refactor`:
```
if --no-lifecycle is NOT set:
Skill(skill="refactor", args="--sweep all --dry-run")
Only functions with CC > 20 become queue items (severity threshold).
```
If `/refactor` is unavailable or `--no-lifecycle` is set, fall back to manual mining:
- complexity hotspots
- stale TODO/FIXME markers
- dead code
- stale docs
- stale research
- drift between generated artifacts and source-of-truth files
Do not stop here. Normalize findings into tracked work and continue.
**Step 3.7: Feature suggestions**
If all concrete remediation layers are empty, propose one or more specific feature ideas grounded in the repo purpose, write them as durable work, and continue:
- create a bead when the feature needs review/backlog treatment
- or append a queue item with `source: "feature-suggestion"` when it is ready for the next `/rpi` cycle
**Quality mode (`--quality`)** — inverted cascade (findings before directives):
Step 3.0q: Unconsumed high-severity post-mortem findings:
```bash
HIGH=$(jq -r 'select(.consumed==false) | .items[] | select(.severity=="high") | .title' \
.agents/rpi/next-work.jsonl 2>/dev/null | head -1)
```
Step 3.1q: Unconsumed medium-severity findings.
Step 3.2q: Open ready beads.
Step 3.3q: Emergency gates (weight >= 5) and top directive gaps.
Step 3.4q: Testing improvements.
Step 3.5q: Validation tightening / bug-hunt / drift mining.
Step 3.6q: Feature suggestions.
This inverts the standard cascade only at the top of the ladder: findings BEFORE goals and directives. It does NOT skip the generator layers.
When evolve picks a finding, claim it first in next-work.jsonl:
- Set `claim_status: "in_progress"`, `claimed_by: "evolve-quality:cycle-N"`, `claimed_at: "<timestamp>"`
- Set `consumed: true` only after the /rpi cycle and regression gate succeed
- If the /rpi cycle fails (regression), clear the claim and leave `consumed: false`
See `references/quality-mode.md` for scoring and full details.
**Nothing found?** HARD GATE — dormancy only when ALL sources empty (soc-5qit):
```bash
READY_BEADS=$(bd ready --json 2>/dev/null | jq -r 'length // 0' 2>/dev/null || echo 0)
HARVESTED=$(jq -r 'select(.consumed==false) | .severity' .agents/rpi/next-work.jsonl 2>/dev/null | wc -l | tr -d ' ')
FAILING_GOALS=$(jq -r '.goals[] | select(.result=="fail") | .id' .agents/evolve/fitness-latest.json 2>/dev/null | wc -l | tr -d ' ')
IDLE_STREAK=$(jq -r '.idle_streak // 0' .agents/evolve/session-state.json 2>/dev/null)
if [ "$READY_BEADS" -gt 0 ] || [ "$HARVESTED" -gt 0 ] || [ "$FAILING_GOALS" -gt 0 ]; then
continue # work exists — loop back to Step 3 (agile invariant)
fi
if [ "${GENERATOR_EMPTY_STREAK:-0}" -ge 2 ] && [ "${IDLE_STREAK:-0}" -ge 2 ]; then
REASON="stagnation: all sources empty x3"
# soc-g2qd wire: under loop, write-stop-marker refuses → log blocked + operator-wait, never self-halt (ADR-0007).
if ao loop write-stop-marker --help >/dev/null 2>&1; then
ao loop write-stop-marker --marker dormant --reason "$REASON" --mode loop 2>/dev/null \
|| ao loop blocked --reason "$REASON" --needed-context "queue empty; operator adds work or marker" 2>/dev/null || true
else
printf '%s\n%s\n%s\n' "cycle $CYCLE" "$(date -u +%FT%TZ)" "$REASON" > .agents/evolve/DORMANT # fallback
fi
fi
```
**Agile invariant (soc-5qit):** `bd ready ≥ 1` ⇒ loop NEVER writes DORMANT, NEVER exits. The only path to DORMANT is fully empty backlog + dry generators. Context exhaustion → HANDOFF, not DORMANT.
If work layers were empty but generators haven't exhausted 3 passes yet, persist `GENERATOR_EMPTY_STREAK` and loop back to Step 1.
A cycle is idle only if NO work source returned actionable work and every generator layer also came up empty. A cycle that targeted an oscillating goal and skipped it counts as idle only after the remaining ladder was exhausted.
If `--dry-run`: report what would be worked on and go to Teardown.
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# duplicate-work-guard.sh — before the evolve-cron-rpi discovery loop creates a
# tracking bead, check whether an existing OPEN or CLOSED bead already covers the
# same work. Exits 1 (with the matching bead) when a duplicate is found, 0 when
# the candidate is genuinely new.
#
# Why this exists (ag-6jt/ag-2je): a stale phase-1 handoff kept re-seeding beads
# for work already merged or already tracked. The prior guard only matched EXACT
# OPEN-bead titles, so same-surface, different-wording dups slipped through
# (ag-b8m≈ag-jov, ag-6kw≈ag-c2i — 4th recurrence). This guard matches on:
# 1. exact normalized title (lowercased, punctuation-stripped), OR
# 2. significant-token overlap (Jaccard-style: shared / candidate tokens),
# across both open and closed beads.
#
# Usage: duplicate-work-guard.sh "<candidate bead title>"
# Exit: 0 no match · 1 duplicate found · 2 usage error
# Env: DUP_GUARD_THRESHOLD (default 0.6) overlap ratio to call it a dup
# DUP_GUARD_MIN_SHARED (default 3) min shared significant tokens
# BD_BIN (default "bd") bd binary (override for tests)
set -euo pipefail
THRESHOLD="${DUP_GUARD_THRESHOLD:-0.6}"
MIN_SHARED="${DUP_GUARD_MIN_SHARED:-3}"
BD_BIN="${BD_BIN:-bd}"
usage() {
echo "usage: duplicate-work-guard.sh \"<candidate bead title>\"" >&2
exit 2
}
[ "$#" -ge 1 ] || usage
candidate="$1"
# Reject empty / whitespace-only titles.
[ -n "${candidate//[[:space:]]/}" ] || usage
# All issues including closed (the dup-seeding failure spans merged/closed work).
beads_json="$("$BD_BIN" list --all --limit 0 --json 2>/dev/null || true)"
[ -n "$beads_json" ] || beads_json='[]'
match="$(
printf '%s' "$beads_json" | jq -r \
--arg cand "$candidate" \
--argjson threshold "$THRESHOLD" \
--argjson min "$MIN_SHARED" '
# Normalize a title to a set of significant (>=3 char) tokens.
def norm: (. // "")
| ascii_downcase
| gsub("[^a-z0-9]+"; " ")
| split(" ")
| map(select(length >= 3))
| unique;
($cand | norm) as $c
| ($c | length) as $cn
| [ .[]
| . as $b
| ($b.title | norm) as $t
| ([ $t[] | select(. as $x | $c | index($x) != null) ] | length) as $shared
| { id: $b.id, status: $b.status, title: $b.title,
exact: ($cn > 0 and $t == $c),
shared: $shared,
ratio: (if $cn > 0 then ($shared / $cn) else 0 end) } ]
| map(select(.exact or (.shared >= $min and .ratio >= $threshold)))
| if length == 0 then empty
else (max_by(.ratio)) | "DUPLICATE: \(.id) [\(.status)] \(.title)"
end
'
)"
if [ -n "$match" ]; then
echo "$match"
exit 1
fi
echo "OK: no existing work matches \"$candidate\""
exit 0
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# sync-main-to-origin.sh — before the evolve-cron-rpi discovery phase selects a
# slice, fetch origin and fast-forward the local `main` to `origin/main` so that
# discovery diffs candidate slices against the TRUE merge base, not a stale local
# main.
#
# Why this exists (ag-6jt): the rpi worktree's local `main` lags `origin/main`
# (e.g. local main=04b5d7cc vs origin/main=9e9caccb). Phase-1 discovery diffed
# the candidate slice against the stale local `main`, so work already merged to
# origin/main read as "open" — repeatedly re-seeding duplicate work/beads
# (ag-b8m≈ag-jov, ag-6kw≈ag-c2i, 4th recurrence). The work-selection-ladder
# reference claimed "the cron syncs local main to origin/main before discovery",
# but no code actually did it. This script makes that claim real and testable,
# and pairs with duplicate-work-guard.sh (the grep-existing-beads half).
#
# Behavior:
# 1. `git fetch <remote>` (so refs/remotes/<remote>/main is current).
# 2. Resolve the diff base to <remote>/main — ALWAYS, never local main.
# 3. Fast-forward the LOCAL `main` ref to <remote>/main without forcing:
# - if `main` is checked out: `git merge --ff-only`,
# - otherwise: `git fetch <remote> main:main` (refuses a non-fast-forward).
# 4. Print the resolved diff base SHA so callers/tests can assert discovery
# diffs against <remote>/main, not the previously-stale local main.
#
# Usage: sync-main-to-origin.sh [<remote>] (remote defaults to "origin")
# Exit: 0 synced (or already up to date) · non-zero on fetch/ff failure
# Output: final line is "DIFF_BASE: <remote>/main <sha>"
# Env: SYNC_MAIN_REMOTE (default "origin") overrides the remote name.
set -euo pipefail
REMOTE="${1:-${SYNC_MAIN_REMOTE:-origin}}"
# 1. Refresh remote-tracking refs.
if ! git fetch "$REMOTE" >/dev/null 2>&1; then
echo "FAIL: git fetch $REMOTE failed (offline or remote unreachable)" >&2
exit 1
fi
# The authoritative diff base after sync is ALWAYS <remote>/main, never local main.
if ! ORIGIN_MAIN_SHA="$(git rev-parse --verify --quiet "refs/remotes/$REMOTE/main")"; then
echo "FAIL: $REMOTE/main does not exist after fetch" >&2
exit 1
fi
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if [ "$CURRENT_BRANCH" = "main" ]; then
# main is checked out — fast-forward only (never a merge commit, never force).
if ! git merge --ff-only "$REMOTE/main" >/dev/null 2>&1; then
echo "FAIL: local main is not a fast-forward of $REMOTE/main (diverged); resolve manually" >&2
exit 1
fi
else
# main is not checked out — move the ref directly, fast-forward only.
# `git fetch <remote> main:main` refuses a non-fast-forward update (no force).
if ! git fetch "$REMOTE" main:main >/dev/null 2>&1; then
echo "FAIL: cannot fast-forward local main to $REMOTE/main (diverged); resolve manually" >&2
exit 1
fi
fi
# Emit the resolved diff base so discovery (and tests) can confirm it is
# <remote>/main, not the previously-stale local main.
echo "DIFF_BASE: $REMOTE/main $ORIGIN_MAIN_SHA"
exit 0
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
PASS=0; FAIL=0
check() { if bash -c "$2"; then echo "PASS: $1"; PASS=$((PASS + 1)); else echo "FAIL: $1"; FAIL=$((FAIL + 1)); fi; }
check "SKILL.md exists" "[ -f '$SKILL_DIR/SKILL.md' ]"
check "SKILL.md has YAML frontmatter" "head -1 '$SKILL_DIR/SKILL.md' | grep -q '^---$'"
check "SKILL.md has name: evolve" "grep -q '^name: evolve' '$SKILL_DIR/SKILL.md'"
check "references/ directory exists" "[ -d '$SKILL_DIR/references' ]"
check "references/ has at least 1 file" "[ \$(ls '$SKILL_DIR/references/' | wc -l) -ge 1 ]"
check "SKILL.md mentions kill switch" "grep -qi 'kill switch' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions fitness" "grep -qi 'fitness' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions GOALS.yaml" "grep -q 'GOALS.yaml' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions cycle" "grep -qi 'cycle' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions /rpi" "grep -q '/rpi' '$SKILL_DIR/SKILL.md'"
# Behavioral contracts from retro learnings (2026-02-12)
check "SKILL.md has KILL file path" "grep -q 'KILL' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents regression detection" "grep -qi 'regression' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents snapshot enforcement" "grep -qi 'snapshot' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents session_start_sha" "grep -qi 'session.start.sha\|cycle_start_sha' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents continuous values" "grep -qi 'continuous\|value.*threshold' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents regression gate" "grep -qi 'regression gate' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents post-cycle snapshot" "grep -q 'fitness-.*-post' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents oscillation detection" "grep -qi 'oscillat' '$SKILL_DIR/SKILL.md'"
# Design-level checks (2026-03-01)
check "Step 0 has oscillation sweep (always-on)" "grep -q 'Pre-populate quarantine list' '$SKILL_DIR/SKILL.md'"
check "Step 5 has wiring script pre-flight check" "grep -q 'if.*check-wiring-closure.sh' '$SKILL_DIR/SKILL.md'"
check "No ambiguous YAML fallback in Step 2" "! grep -q 'run each goal.*check command manually' '$SKILL_DIR/SKILL.md'"
check "CLI required for fitness measurement" "grep -q 'CLI is required for fitness measurement' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents harvested-first selection order" "grep -Fq 'Harvested \`.agents/rpi/next-work.jsonl\` work' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents generator ladder" "grep -q 'Testing improvements' '$SKILL_DIR/SKILL.md' && grep -q 'Validation tightening and bug-hunt passes' '$SKILL_DIR/SKILL.md' && grep -q 'Concrete feature suggestions' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents dormancy as last resort" "grep -q 'Dormancy is last resort' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents queue claim before consume" "grep -q 'claim it first' '$SKILL_DIR/SKILL.md' && grep -Fq 'keep \`consumed: false\`' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents session-state persistence" "grep -q 'session-state.json' '$SKILL_DIR/SKILL.md'"
check "SKILL.md documents immediate queue reread after /rpi" "grep -Fq 'immediately re-read \`.agents/rpi/next-work.jsonl\`' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions repo execution profile" "grep -qi 'repo execution profile' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions startup_reads" "grep -q 'startup_reads' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions validation_commands" "grep -q 'validation_commands' '$SKILL_DIR/SKILL.md'"
check "SKILL.md mentions definition_of_done" "grep -q 'definition_of_done' '$SKILL_DIR/SKILL.md'"
echo ""; echo "Results: $PASS passed, $FAIL failed"
[ $FAIL -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,63 @@
---
template_version: 1
verbatim_markers:
3-adr-cite: 5bfa1c52a93edff9316b65025a0cc75a97169c61266470cc58037f9a168cf9ba
unblock-ladder: 704781847dee29c7e239754c15d848d0d3c0f27a5fbfd17a0c87b42d563b519a
layer-3-authority: 8a4478939885980823d49ac156d27553c4a42258af5399b6e04f6d7f96016f1b
no-self-stop: 62e97f78951dbd1067a1661c0fb8b8e5d8754ebd2581ddfcba63856ee4e4f48b
---
# /evolve loop-mode cron prompt (cycle {{.CronSelfAdjustCounter}})
You are in /evolve --mode=loop. This is cycle {{.CronSelfAdjustCounter}}.
## Last cycle outcome
Shipped: {{range .ShippedCommits}}{{.Sha}} ({{.Bead}}{{if .Scenario}} #{{.Scenario}}{{end}}); {{end}}
Tests delta: {{.TestsDelta}}
## Sub-beads filed this cycle
{{range .SubBeadsFiledThisCycle}}- {{.}}
{{else}}(none){{end}}
## Recommended next work
{{.NextRecommendedBead}} (advisory; Layer-3 authority may override)
<!-- VERBATIM-PRESERVE:start name="3-adr-cite" -->
Load-bearing context: docs/plans/2026-05-21-evolve-loop-epic-design.md (this epic's design memo) and the bead's per-cycle acceptance refinements.
<!-- VERBATIM-PRESERVE:end -->
<!-- VERBATIM-PRESERVE:start name="unblock-ladder" -->
## 7-step unblock ladder
If `bd ready` returns ≥1 unblocked bead, claim and work one.
If you feel "blocked": surface the blocker and pick the next ready bead. NEVER write a STOP marker.
The ladder (programmatic enforcement: soc-mlbm Wave 2):
1. Shape-compatible ready bead exists → pick it
2. Sibling pattern in another bead's description → surface match
3. Decomposable epic → recommend smaller child
4. Cross-Hop pickup → traverse discovered-from chain
5. Bug-fix fallback → smallest-surface bug
6. Still nothing? Log blocked event, pick smallest available P1 anyway
7. NEVER write STOP. Operator owns stop signals.
<!-- VERBATIM-PRESERVE:end -->
<!-- VERBATIM-PRESERVE:start name="layer-3-authority" -->
## Layer-3 shaping authority
The agent can:
- File new beads as discoveries land (`bd create` + `discovered-from`)
- Restructure the DAG
- Update the cron's recommended-next pointer
- Write ADRs for emergent precedents in `docs/decisions/`
Bounded by: bead-graph coherence, operator-set scope (preferences.yaml), no-self-stop principle.
<!-- VERBATIM-PRESERVE:end -->
<!-- VERBATIM-PRESERVE:start name="no-self-stop" -->
## The no-self-stop principle
In `--mode=loop`, the agent NEVER writes a STOP/DORMANT/KILL marker. `ao loop write-stop-marker` mechanically refuses (exit 1). When stuck: log via `ao loop blocked`, pick smallest-available P1 anyway. Stop signals come from the operator, never the agent.
<!-- VERBATIM-PRESERVE:end -->