session framework · decision artifact · 2026-08-14
🎩🤖 obot-prime is a standing singleton designed to stay up indefinitely, which makes context compaction a certainty, not a risk. Its charter asserts that "durable state lives in the scratchpad and in the delegates' artifacts, never only in prime's context" — an assertion with no mechanism behind it, and tonight produced a live instance of the failure class it invites. This page turns the assertion into a mechanism, under one binding constraint: the <30-second reply is the objective function, not a tiebreaker. It also evaluates @jwildfire's mid-session proposal of a standing Navigator agent head-to-head against the file-based designs, on the same terms. Every recommendation is measured or cited.
“I'm good with your recommendations.”
All six calls adopted. Implemented on 2026-08-15 in the agent-tooling repo: prime now keeps its durable state in a capped, provenance-stamped file and rehydrates from a single read after a cold turn. The Navigator — the standing bot that keeps that state current — is filed as requirement #157.
prime-state.md — five sections (Open / Delegates / Armed / Claims / Settled), ~4 KB hard cap, a provenance stamp on every line ([verified gh 23:41] vs [asserted 👯🤖 news-rc]), lines deleted on resolution. The log stays as-is: it answers "what happened"; the state file answers "what is true right now".prime-rehydrate, mirroring the proven handoff.sh): state file(s) + the two newest day-files' logs + sweep cache in one bounded, one-round-trip read — as built and measured post-midnight 2026-08-15: ≈17.5 KB ≈ 4.4k tokens in 0.04 s. Fired lazily, on the first post-compaction turn that touches state. Trust the compaction summary for flavor, the bundle for facts.scratchpad-log call costs ≈20. ~10× cheaper, and every sibling benefits.## Settled line until applied, then deleted.C1 gates C3 and C5 (both operate on the state file). C2 composes with C1 — the Navigator needs the state file to write into. C4 is independent and pays across every sibling. C6 is the vehicle.
Prime launched tonight (2026-08-14, 22:44) as the standing Q&A concierge: one long-running session reachable from terminal or phone, whose only product is fast answers — <30 seconds, ≤2 sequential round trips, everything else delegated. Working sessions die at their wrapup and are reborn through session-init's hand-off; prime has no bookends and no rebirth. When its context fills, the harness summarizes the conversation and continues — and per Anthropic's documented compaction contract, what survives on purpose is what lives on disk; what was only ever said in conversation survives only if the summary happens to keep it.
That compaction summary is written by the harness, not by prime: prime does not choose what survives it, cannot audit what it dropped, and cannot re-derive what was never written down. The charter's answer — durable state lives outside the context — is correct doctrine with no mechanism: nothing says what gets written, when, in what shape, or how a freshly-compacted prime gets back to useful without blowing its own latency contract.
Mid-drafting, @jwildfire added a design intuition, relayed through prime: "we probably need a separate 'Roadmap bot' running in the background… call it the 'Navigator'… watching the roadmap for current state and keeping everything up to date for you. I might also delegate the audit updates to the navigator. Its job is to make sure that all of the worker agents are following our github standards and to keep track of current state of the work being done (so that you don't have to)." That is a concrete architectural answer to this exact question — externalize state into a peer agent rather than only into files — and it is evaluated head-to-head in C2, not footnoted.
The incident to design against. At 22:50 prime armed an RC-watch monitor and told @jwildfire it "self-corrects". That was false — the monitor was broken on arm (a zsh word-splitting bug; it was watching nothing) and the finding prime credited to it had actually come from a separate one-off verification sweep. The claim was made inline and never written down, so no record on disk existed to contradict it; it took a peer session independently catching the broken monitor (22:53) to surface the error. A claim that exists only in prime's context is unfalsifiable by anything on disk — and after compaction, it can survive as confident summary-fact with its provenance gone.
And it was not alone. Tonight produced three further provenance errors, every one a summary-of-a-summary: a close-out line reported oa#83 "still open" when it had merged hours earlier (stale self-report, caught by the 22:51 verification sweep); prime's own sweep then misread a creation timestamp as the merge time (corrected by a peer at 23:10); and a peer attributed the hub's two unpushed commits to prime when they were the closing lead's (corrected at 23:10). None of these were lies — each was an agent confidently repeating derived state without a provenance stamp that would have made it checkable. That pattern, not any single mistake, is what the mechanism below must fix.
Read against tonight's scratchpad (66 log lines by 23:16), prime's load-bearing context divides cleanly into what is already safe on disk and what is not:
| State prime holds | On disk tonight? | Recoverable in one bounded read? |
|---|---|---|
| Live sibling roster + what each was told (news-rc, hub-close, blockers-tails) | Yes — the 23:08/23:14 spawn lines are detailed | No — scattered across the log; completion status only implicit in later close-out lines |
| The inherited pending queue (2 RCs, 2 decisions, 4 blockers, worktrees, stale prereqs, 2 ideas) | Yes — enumerated once in the 23:10 handoff line | No — per-item status changes as siblings finish; "what is still open" requires replaying every subsequent line |
| Corrections to earlier claims (oa#83 merge time; whose commits afb431d/788430f are; the monitor claim) | Yes — three CORRECTION lines | No — a reader must fold each correction over the earlier claim; a summary may keep either version |
Armed-monitor state (id bm8f7wvah, 15-min cadence, dies-on-retirement) | Yes — 22:50–22:53 lines | No — three lines tell the story only in sequence: armed → found broken → re-armed corrected |
| Which claims were verified vs self-reported (the 22:51 sweep) | Yes — sweep lines say "verified" | Partially — verified facts are marked, but nothing marks the unverified ones |
| The "self-corrects" claim itself, before the peer caught it | No — never written | No — this is the incident |
The honest reading: tonight's narrative capture is good — prime logged heavily and well. What is missing is not more narrative. It is (a) a queryable rollup: nothing answers "what is open right now, who is doing what, what is armed" without replaying the whole log and mentally applying every correction; (b) a write-with-reply rule for claims: the one thing that went wrong tonight went wrong precisely because it was never written; and (c) provenance per claim: nothing distinguishes a fact verified against GitHub at a known time from an assertion inherited through two summaries.
Everything below was measured on this machine tonight (Python time.perf_counter() over full-file reads; time over script runs; wc -c for sizes; tokens estimated at 4 bytes/token):
| Source | Size | ≈ tokens | Disk read |
|---|---|---|---|
Tonight's scratchpad 2026-08-14.md | 17,049 B | ~4,300 | 0.33 ms |
Heaviest recent scratchpad (2026-07-28.md) | 66,939 B | ~16,700 | — |
Sweep cache gh-sweep.json | 7,429 B | ~1,900 | 0.20 ms |
Memory index MEMORY.md | 15,763 B | ~3,900 | 0.20 ms |
| All August scratchpads together | 89,027 B | ~22,000 | 1.77 ms |
handoff.sh full bundle run (the C3 pattern) | multi-source | — | 0.35 s wall |
| Session transcripts (JSONL, 108 files) | 10–16 MB each | millions | delegate-only lane |
An honest inventory — what each mechanism durably captures, what it costs, and whether prime can actually use it inside 30 seconds:
| Mechanism | Durably captures | Write cost | Read-back cost | Usable in <30 s? |
|---|---|---|---|---|
Scratchpad ## Session log (multi-writer, append-only) | Narrative: spawns, milestones, corrections, close-outs | ≈190 gen-tokens/event (heredoc) | ~4.3k tokens tonight; ~16.7k on a heavy night; corrections must be folded by the reader | Yes, but degrading — one read, unbounded growth, state must be inferred |
Sweep cache gh-sweep.json (~2 ms warm) | Cross-repo issue/PR delta since the window start | Free to prime (hub tooling refreshes it) | ~1.9k tokens, 0.2 ms | Yes — the canonical "what changed on GitHub" answer |
Agent memory (MEMORY.md + fact files) | Cross-session durable facts, preferences, workstream state | One Write + index line | Index auto-loaded at session start — and re-injected from disk after every compaction (documented harness behavior), so memory is compaction-proof by construction | Yes — but wrong granularity for tonight's queue; it is for facts that outlive weeks, not hours |
handoff.sh Tier-0 bundle | Wrapup hand-off + diary heads + sweep cache, ===-delimited, ages precomputed | — | 0.35 s wall, one Bash call; zero round trips when pre-injected by /s-init | Yes — the proven pattern, but built for working-session bookends; prime has no bookends, so nothing invokes it for prime today |
| Session transcripts (JSONL) | Everything, verbatim — full provenance | Free (harness) | 10–16 MB per session; grep-able; also queryable via claude -p --resume <id> (a supported lane) | No — delegate-only mining lane, never inline |
obot-session-state → hub site | Outbound status indicator for the roadmap page | One script call | n/a (it publishes, it does not rehydrate) | n/a — not a memory mechanism; listed to be honest that it is not one |
| Harness compaction summary | Whatever the harness chose to keep | Free, automatic | Free — it is the post-compaction context | Yes for flavor, no for state — uncontrolled, unauditable, and the lane by which an unwritten false claim becomes confident summary-fact |
| Diary drafts / wrapup checkpoint files | End-of-session record for @jwildfire | Lead-owned | ~12.8 KB tonight | Not prime's lane (prime has no wrapup until retirement) |
The gap is precise: every durable lane is either narrative (the log), external (GitHub, the sweep), or the wrong timescale (memory). Nothing holds the working set — open items, live delegates, armed monitors, claim status — in a form a compacted prime can swallow in one bounded read. And the one mechanism purpose-built for fast state recovery (handoff.sh) is wired only to the bookends prime doesn't have.
A background research pass swept Anthropic's documentation and engineering posts plus the third-party agent-memory literature. The load-bearing findings, each with its source:
CLAUDE.md and the auto-memory MEMORY.md index are re-injected from disk, and anything said only in conversation survives only if the summary keeps it (context-window docs, memory docs). The working set must live on disk; conversation is not storage./compact <instructions>, a # Compact instructions heading in CLAUDE.md — docs) and is worth using, but Anthropic's own belt-and-suspenders advice is to pair compaction with external memory, never rely on the summary alone (memory-tool docs).Why now: tonight's log is good narrative that cannot answer "what is open right now" without a full replay plus mental correction-folding. Compaction makes that replay a recurring cost, and makes the un-replayed alternative — trusting the summary — exactly the confidently-stale failure mode.
prime-state.md, provenance-stampedrecommended.claude/session-hub/prime-state.md: five bulleted sections — Open (pending items, one line + link each), Delegates (slug · lane · told-to-do · status), Armed (monitor ids, cadence, re-arm notes), Claims (assertions made to @jwildfire), Settled (do-not-relitigate, with provenance). Every line carries a provenance stamp: [verified gh 23:41] / [asserted 👯🤖 news-rc 23:20] / [self, unverified] / [corrected 22:53] — written for the reader who must decide whether to repeat the claim to @jwildfire. Hard cap ~4 KB (~1k tokens); resolved lines are deleted — the log keeps history. One line in MEMORY.md points at the file and the rehydrate procedure, making the mechanism itself compaction-proof (the index is re-injected from disk after every compact). Prime is sole writer of its sections, so section-replacing edits are safe under the multi-writer rules.C1-b — the capped, provenance-stamped Markdown state file.
This is the snapshot/log split — Anthropic's published long-running-agent pattern and Letta's "core memory" — applied at exactly prime's scale: the log remains the append-only record of what happened; the state file is the ~1k-token snapshot of what is currently true, with every line carrying how it is known. The write is free at the latency margin because it rides the reply message, and the Claims section is the direct fix for the "self-corrects" incident: the rule becomes "if it's worth telling @jwildfire, it's worth one stamped state line, written in the same message" — making every claim falsifiable by disk before it can fossilize into a compaction summary.
Unblocks
C3's one-read rehydration, C5's retention rules, and C2's Navigator — which needs a stamped state surface to keep true.
Why now: @jwildfire, mid-session: a background Roadmap bot "watching the roadmap for current state and keeping everything up to date for you", possibly owning audit updates and GitHub-standards enforcement across worker agents, "so that you don't have to". Evaluated here on the same latency-first terms as everything else. The blunt architectural fact first: a peer prime must ask is strictly worse than a file prime reads; a peer that writes the file prime reads is strictly better than prime maintaining everything alone. Which of those the Navigator is decides whether it helps or hurts.
navigator-state.md beside prime's file (two files, each single-writer — no multi-writer hazard) — upgrading stamps to [verified gh HH:MM], flagging drift (stale claims, items that moved, standards violations) as report lines. Prime's rehydration bundle picks it up in the same one read at ~0 marginal cost (0.2 ms/file, measured). Day-one scope: job (a) only — read-only bookkeeping and verification. Job (b), standards enforcement, is observe-and-report: the Navigator files what it sees (state lines, issues); it never corrects another agent's work, because a bot that polices peers can be wrong about them — tonight two careful agents disagreed about whether a merge-lane denial even reproduced — and the recourse for a wrong report is a reply, while the recourse for a wrong correction is an incident. Job (c), audit updates, stays in its existing lane (the local audit framework) until (a) has proven reliable; migrating it is a later, separate call. Ramp: start as a scheduled verification sweep — tonight's 22:51 one-off sweep is the working prototype, and it caught the stale oa#83 claim — and graduate to a standing session only if cadence and scope demand one.[verified] stamps age unpredictably; prime carries bookkeeping for cross-agent state, which grows with fleet size — the exact burden @jwildfire is proposing to lift.C2-b — yes to the Navigator, strictly as a file-writing verifier, scoped to bookkeeping on day one, ramped from a scheduled sweep.
The head-to-head is not Navigator versus state file: a Navigator only works as a writer of the state surface C1 creates, and it answers the one weakness of self-maintained state — that self-reports go stale and nobody re-verifies them (all four of tonight's provenance errors). One boundary is non-negotiable: the Claims section stays prime's own. Only prime knows what it just told @jwildfire; the "self-corrects" claim was invisible to any GitHub watcher by construction, so the write-with-reply rule stands regardless of how capable the Navigator becomes. And enforcement stays observe-and-report until there is a track record — the standard for a bot that judges other agents should be the same one this page applies to prime: every judgment written down, stamped, and checkable.
Unblocks
@jwildfire's "so that you don't have to" — prime stops carrying cross-agent bookkeeping — without ever putting a peer agent on the response path. Approving C2-b also settles where audit updates do not go yet.
Why now: the budget allows one read round trip on a cold turn. The question is what that one read contains, and whether it fires eagerly (every post-compaction turn) or lazily (only when a question touches state).
prime-rehydraterecommendedhandoff.sh: emits prime-state.md (+ navigator-state.md if C2-b lands) + the two newest day-files' ## Session log sections (a today-only read goes amnesiac at midnight — found by prime's first dogfood, fixed in obot.agent#94) + gh-sweep.json, ===-delimited, ages precomputed, byte-bounded to keep the newest lines. One Bash call; as built and measured post-midnight 2026-08-15: ≈17.5 KB ≈ 4.4k tokens in 0.04 s wall (the pre-implementation estimate read ~7k tokens / 0.35 s). Fired lazily: on the first post-compaction turn whose answer touches state scope; pure-lookup questions (conventions, where-does-X-live) skip it. Also wired as /s-prime preprocessing so a relaunch rehydrates with zero round trips — the same Tier-0 trick that took init toward its <10 s bar. Complement, not substitute: a # Compact instructions block (a documented harness feature) steering summaries to keep roster/queue/corrections makes the summary better, but the bundle stays the authority.C3-b — the lazy scripted bundle.
It is the only option that is simultaneously bounded (≤ ~7k tokens, one round trip, fitting the ≤2-round-trip contract with the answering trip to spare), grounded (state comes from disk, not from the summary), and honest about cost (0.35 s wall, measured on the identical handoff.sh pattern; sub-2 ms of that is I/O). The compaction summary is still used — for conversational continuity, which it is good at — just never as the authority on state. The one-line version: trust the summary for flavor, the bundle for facts.
Unblocks
Compaction stops being an event prime fears or even notices much: cold turns cost one bounded read, relaunches cost zero. The charter's longevity clause becomes true by mechanism instead of by assertion.
Why now: the heartbeat heredoc (briefing template) is generated fresh by every agent on every event: ≈190 output tokens ≈ 2–3 s of generation each time, plus permanent context burn, plus 759 characters embedded in every sibling briefing. Prime logs a lot (14 events tonight); under latency pressure the temptation is to skip a write — and a skipped write is exactly how the "self-corrects" class is born.
scratchpad-log scriptrecommendedobot.agent/tools/scratchpad-log. A log event becomes scratchpad-log '👯🤖 slug' 'event text' — ≈20 generated tokens, ~10× cheaper, identical semantics. The briefing template shrinks by the whole code block and gains one line. Needs one allowlist entry in workspace settings — @jwildfire's edit to make, same carve-out lane as tonight's merge-lane fix.C4-b — script-ify, for everyone.
This is the one change in the page that makes writes cheaper rather than adding any — it lowers the marginal cost of the exact behavior (write-with-reply, every claim, every event) that C1 depends on. When logging costs ≈20 tokens inside the same message as the answer, "too busy to log it" stops being a real pressure, which is the honest mitigation for the skipped-write failure mode: make the right thing nearly free instead of exhorting discipline.
Unblocks
Cheaper heartbeats for every sibling and ultracode job, a ~750-character-lighter spawn prompt on every spawn, and the write-with-reply rule at a cost nobody will dodge.
Why now: a memory mechanism that only accretes recreates the problem it solves. The discipline of forgetting is what keeps the state file at ~1k tokens and the cold read bounded forever. And post-compaction, a prime that has lost the feeling of a settled argument will cheerfully reopen it unless the settlement is written where rehydration finds it.
gh-sweep.json and one bounded gh call are the authorities; copied state is born stale and reads as confident. (4) A settled decision is one ## Settled line with provenance ("hub-commit inspection: Jeremy chose inspect-then-push — log 23:14") kept until applied, then deleted. Settled means cite-and-move-on, not re-argue.prime-state-archive.md instead of deleting.C5-b — the four rules, enforced by the cap.
Letting go is what the scaffold's layering already implies: verbatim history in transcripts, narrative in the log, deliverables in artifacts, live truth on GitHub. The state file is the only layer with no archive duty, so it is the only layer allowed to forget — and it must, or the bounded read stops being bounded. The ## Settled rule is the anti-relitigation mechanism: post-compaction prime does not remember the argument, but it rehydrates the verdict with a pointer, which is all it needs to decline to reopen it.
Unblocks
A state file that is still ~1k tokens in December; settled questions that stay settled across any number of compactions.
Why now: the mechanism touches a skill (session-prime), the canonical contract doc (session-framework.md), a template every sibling inherits, the tools directory — and, if C2-b is approved, a new agent role. Scattering it invites drift; the framework doc's own convention is that there is only ever one copy of a contract to keep current.
tools/prime-rehydrate and tools/scratchpad-log, plus the briefing-template edit with heredoc fallback; (iv) /s-prime preprocessing wiring. PR left open for @jwildfire per the approval gates; the settings allowlist line ships in the PR description as his edit. The Navigator lands separately as a hub requirement issue through the normal lifecycle — it is a new agent role with scope questions of its own (the (a)/(b)/(c) boundary above), not a doc edit to smuggle into a tooling PR.C6-b — one PR for the mechanism, one requirement for the Navigator.
The skill owns the contract, the framework doc points at it in three lines, the tools make it cheap, and the template spreads C4's savings to every sibling. The Navigator gets the front door every new capability gets — a requirement with its scope boundary made explicit — rather than arriving as a side effect. Extraction into a standalone skill (C6-c) stays available the day a second standing agent exists.
Unblocks
Approving any of C1–C5 has a place to land; approving all of them lands in one review plus one requirement draft.
prime-state.md was created for prime, and no Navigator was spawned; the mechanism starts, if approved, with prime writing its first state file from tonight's log.jwildfire/obot.roadmap was committed; the hub commits here are the artifact itself plus its index entries, under the standing hub grant.| Claim in this page | How it was established |
|---|---|
| Warm-source reads are sub-millisecond; the full August scratchpad set reads in 1.77 ms | Python time.perf_counter() over full-file read_bytes(), this machine, 2026-08-15 |
handoff.sh full bundle costs 0.35 s wall | time handoff.sh >/dev/null, this machine, 2026-08-15 |
| As-built rehydration cost: ≈17.5 KB ≈ 4.4k tokens in 0.04 s (two-day-file read, byte-bounded) | Acceptance run 2026-08-15 00:08 after the midnight fix (obot.agent#94); supersedes this page's pre-implementation ~7k-token / 0.35 s estimate |
| The heartbeat heredoc is 759 characters ≈ 190 output tokens per event | awk extraction of the code block from templates/sibling-briefing.md piped to wc -c; tokens at 4 bytes/token |
| Scratchpads: 17,049 B tonight; 66,939 B on 2026-07-28; transcripts 10–16 MB × 108 files | wc -c / ls -S over the live directories |
| The "self-corrects" claim was made, was false, and was never logged before its correction | Tonight's scratchpad, lines timestamped 22:50–22:53: the arm line, the peer catch, the re-arm, and prime's own 22:53 CORRECTION line stating "I told Jeremy the monitor 'self-corrects'. False." |
| Three further provenance errors, all summaries-of-summaries (oa#83 "still open"; creation time read as merge time; commit ownership guessed) | Tonight's scratchpad: the 22:51 verification-sweep correction and the two 23:10 CORRECTION lines; corroborated by prime's own account relayed mid-session |
| The pending queue and roster are on disk but not answerable in one bounded read | End-to-end read of 2026-08-14.md (66 log lines at 23:16): queue enumerated once at 23:10, status mutations scattered across subsequent lines, three CORRECTION folds required |
| Compaction survival: system prompt unchanged; CLAUDE.md and the MEMORY.md index re-injected from disk; conversation-only content at the summary's mercy | Anthropic documentation: context-window and memory pages, fetched 2026-08-15 by the research pass |
| The post-idle cache cliff (full-context reprocess after >1 h idle) | Anthropic, Claude Code costs — "your first message after a break longer than the cache lifetime misses the cache and reprocesses your full context" |
obot-session-state is outbound-only | Script read in full: renders and publishes session.json to the orphan session-state branch; contains no read-back lane |
.claude/session-notes/2026-08-14.md — read end to end (the case study).claude/skills/session-prime/SKILL.md (the charter), obot.agent/docs/session-framework.md, templates/sibling-briefing.md, tools/session-init/handoff.sh, scripts/obot-session-state, scripts/obot-prime