persist · diff
git:20260628.9edbf07 to git:20260629.5fd5953
1 added, 0 removed. Audit A to A.
---
name: persist
+ primitive: P12
category: orchestration
description: "bstack P12 — Persistent Loop Discipline. Cross-context restart loop where state lives in the filesystem (PROMPT.md + git tree + state.jsonl), not in the conversation. Each iteration spawns a fresh agent context. Solves the 'context rot' failure mode where long-horizon agentic work (>1h, METR's 80%-reliability ceiling) degrades silently past ~100K tokens. Validation backpressure comes from compilers/tests/linters, not model self-grading. Use persist when: (1) starting work that may span hours and exceed the model's reliability horizon, (2) detecting context drift mid-session (token usage past 100K, repeated failed iterations on the same fix), (3) coordinating long-horizon work that needs to survive crashes / context exhaustion, (4) running parallel work streams (one persist loop per worktree, composed with bstack P5). Triggers on 'persist', 'long-horizon loop', 'context restart', 'fresh-context iteration', 'P12', 'Ralph loop', 'filesystem-state loop'."
---
# persist — bstack P12 Persistent Loop Discipline
**Cross-context restart loop. State in filesystem, not conversation.**
The defining moves:
1. The agent writes a goal + state snapshot to `PROMPT.md`
2. `persist iterate PROMPT.md` spawns a fresh agent context per iteration
3. State persists in the filesystem (`PROMPT.md` + git tree + `state.jsonl`)
4. Validation backpressure: compilers/tests/linters, **not** model self-grading
5. Loop exits when success_condition fires OR budget exhausted OR user interrupts
## Why this exists
METR's [Time Horizon 1.1](https://metr.org/blog/2026-1-29-time-horizon-1-1/) puts the **80%-reliability deployable horizon** at ~1 hour on Opus 4.6. Above that, model coherence degrades silently — context rot past ~100K tokens (the *Dumb Zone*). In-context loops (ReAct/TAO) fail because they share the rotting context window. **Persist solves this by restarting the context every iteration** while keeping state in the filesystem.
## When to invoke
The reflexive trigger rule (full text in workspace AGENTS.md §P12):
1. **Before starting any work that may exceed ~1h of unsupervised agent time** — write `PROMPT.md`, decide budget, pick success condition, call `persist iterate`.
2. **When token usage in the current session crosses ~100K** — restart instead of continuing in the rotted context.
3. **When the same fix has been attempted ≥3 times without convergence** — stop the in-context loop; write the diff history to `PROMPT.md` and start fresh.
4. **When orchestrating long-horizon work** — default to persist with periodic checkpoints; compose with P5 worktrees for parallel persist loops.
## CLI
```bash
persist iterate PROMPT.md \
--max-iterations 50 \
--max-wall-clock 14400 \
--success-condition "grep:DONE:STATUS" \
--agent-cmd "claude -p '{}'"
persist status # show open loops
persist status --json # machine-readable
persist abandon <loop-id> # terminal: mark ABANDONED, free slot
persist doctor # health-check (state dir, git available)
persist conformance # run test battery
```
The `{}` token in `--agent-cmd` is replaced with the prompt file's contents. Default agent is `claude -p '{}'`. Codex: `--agent-cmd "codex {}"`. Gemini CLI: `--agent-cmd "gemini -p '{}'"`.
## Success conditions
Three forms:
- **`exit-code-0`** — last agent invocation returned 0
- **`file-exists:PATH`** — agent writes a sentinel file when done
- **`grep:PATTERN:FILE`** — agent writes a status line that matches PATTERN
The agent is responsible for **updating PROMPT.md or writing the sentinel file** at the end of each iteration. The script doesn't try to interpret agent output — that's the *backpressure must come from external signals* invariant.
## State machine
```
SPAWNED ──→ ITERATING ──→ ITERATING ──→ ... ──→ SUCCESS (terminal)
│ ╲
↓ ↘ BUDGET_EXHAUSTED (terminal)
PAUSED ──→ ITERATING
│
↓
ABANDONED (terminal)
```
State events append to `~/.config/broomva/persist/state.jsonl` (JSONL append-only with `flock`).
## Composition with bstack
| primitive | composes via |
|---|---|
| **P5** Parallel Agents | run N persist loops, one per `git worktree` |
| **P7** CI Watcher | each iteration's pushed PR uses `p9 watch` for productive-wait |
| **P10** Worktree Hygiene | clean tree before iteration; janitor after each merge |
| **P11** Empirical Feedback | per-iteration validation; persist's success_condition is multi-modal evidence |
| **P6** Bookkeeping | persist loops produce graph-relevant material → `bookkeeping replay` between loops |
## Invariants
- **State lives in the filesystem.** Each iteration starts from PROMPT.md content, not conversation history.
- **Validation backpressure is external.** Don't ask the agent "are you done?" — check exit codes, file presence, or status pattern.
- **Budget bounds must be honored.** Default 50 iterations / 4h wall-clock. The 4h default matches METR's 80%-horizon ceiling.
- **State.jsonl is append-only.** Loop terminations are terminal — no resurrection. To restart, spawn a new loop with a new ID.
- **Each iteration is a fresh process.** `persist` calls the agent CLI in a subprocess; agent context never persists between iterations except via filesystem state.
## See also
- bstack workspace AGENTS.md §P12 — the binding reflexive trigger rule
- [bstack/references/primitives.md](https://github.com/broomva/bstack/blob/main/references/primitives.md) — full primitive contract
## Background
Pattern popularized by Geoffrey Huntley as ["everything is a ralph loop"](https://ghuntley.com/loop/) (Jan 2026). Anthropic shipped a [`ralph-wiggum` plugin](https://github.com/anthropics/claude-code/blob/main/plugins/ralph-wiggum/README.md); OpenAI shipped [`/goal` in Codex CLI 0.128.0](https://simonwillison.net/2026/Apr/30/codex-goals/). bstack's P12 is the same mechanism with non-anthropomorphized naming and explicit composition with the rest of the bstack contract.