Skip to content

A session's log outlives its runs in one searchable object; a run seeds from its notepad and the log's tail and recalls the rest; compaction is a pointer, never a loss ​

The ask. Decide: adopt this as the storage and growth half of record 0034's session rule, before the program's PR 3 (the seed rule, unit U34) is built, because that rule has nothing to seed from once a run finishes and this record says what it seeds from instead. Owner: the maintainer, who on 2026-09-14 named the published Astra approach (a rolling log kept whole, a notepad the model keeps across context windows, earlier windows searchable by the model) as "highly relevant and something we should consider in our design". Written for an engineer who knows the run ledger (record 0019), the pi harness (record 0032) and record 0034. Amended the same day, while proposed, on the plan owner's review: the seed's own turns are rows of the run's range, a per-session byte policy, a sessions registry for the sweep, the compaction row built from pi's event, and the detach case traced.

Success criteria: (1) nothing said or observed in a session is ever out of the agent's reach, whatever the window holds, and a gap is named where it is; (2) a follow-up in a thread starts from what the last run knew, not from 50 Slack replies; (3) a compaction changes what the model holds, never what exists; (4) a resume rebuilds the conversation its run had; (5) storage per session is bounded by age and count as run records are, and by a byte policy of its own.

TL;DR ​

Record 0034 seeds a follow-up from the transcript of the thread's last finished run, and that transcript does not exist: the ledger clears it when the run finishes, leaving a record whose tool outputs are cut to 8,000 characters. Its growth answer, "pi compacts", is a summary pi writes past a threshold we never set, and the Astra launch post says a compaction leaves out why a fix failed or how a component behaves. The bet: one session log per thread and agent, append-only with a full-text index, is the store of record; a run is a range of it that begins with its own seed, the seed is the agent's notepad plus the log's newest turns within a budget, and recall reaches everything before, so compaction removes turns from the window and never from the agent's reach. The cost is moving the transcript store from one object per live run to one per session with a byte policy and a registry, two tools, one steer per compaction and one range read per follow-up. Open are the seed budget and recall's quality on code-shaped text, measured on the first ten sessions.

Today at 6d26cf02 ​

The five facts the design turns on; the survey is Appendix A.

You would expectToday
A finished run's transcript is keptOne object per live run holds the raw turns, the seed as rows 0 to n; the ledger clears it best-effort when the run finishes or is abandoned (src/core/runLedgerWorker.ts:266,275), and nothing else sweeps it. What survives is the record: tool output cut to 8,000 characters, each event to 64 KiB, the record to 1.5 MiB with middle events dropped; the policy's maxBytes bounds those records and nothing else.
Compaction is ours to tunepi exposes the knobs and we set none (src/core/harness/pi/process.ts:212); the declared window is 200,000 tokens whatever the model (process.ts:174), so pi summarizes at about 183,600 and keeps the newest 20,000 verbatim, checking after each tool batch, before a prompt and after a run ends. Its compaction_end event carries the summary, the first kept entry and the token counts; the bridge reads the counts into a prose note (src/core/harness/pi/bridge.ts:342) and the mirror writes no compaction row, so a resume rebuilds the raw turns and pi compacts again (record 0032, line 191). How often a run compacts is unmeasured: coding on pi is behind a deployment flag.
A model can look back at what it sawNo tool reads a run's earlier turns: get_run_status returns the final reply alone (src/tools/runs.ts:78-81); the runs store reads events by sequence only; the one full-text index in the state Worker is the memory store's (deploy/cloudflare-memory/worker.ts:259).
Memory is the agent's notesMemory is cross-thread distilled knowledge, org, user, repo and channel scopes, written by a reflection pass the model cannot call, "never a raw transcript" (src/core/memory/types.ts:8-11), injected as at most 8 records and about 800 tokens.
A failed transcript write ends the run; a resume recomposes its promptIt detaches it: a refused or twice-failed step write leaves the run running untracked with one warning, and its finish record goes to the plain store (src/core/runLedger/writeThrough.ts:8-13). A resume keeps the live row's system prompt (run-history item 29).

The shape ​

A session log is one Durable Object per session (a thread and an agent, the pair record 0034 defines; keyed <threadKey>:<agent>): the transcript rows as they are today, in order, for every run of the session; pi's compaction entry as a row where pi compacted; a full-text index over the text of every turn and tool result; and one notepad row, a document of at most 8 KiB the agent writes. A run's range begins with the turns its seed carried that were not yet in the log (channel history for a first run, the channel's messages since the previous run, a parent's text turns for a spawned child), then its request, then its own turns; the log turns its seed read but did not append start at seedFrom. The log is the transcript store because it is the only place both readers find what they need: a run appends under the ledger's write-ahead rule and fence, a resume reads from seedFrom to the end of the range, a follow-up reads the tail, and nothing is cleared at finish. A run seeds from the notepad, then the log's newest turns within a seed budget, then the channel's messages since and the request; recall searches the turns before and reads a named turn whole; the agent keeps the notepad through notes; the notepad rides the system prompt at run start and is steered into the context after every compaction. Compaction stays pi's and stays on; what changes is that it can no longer lose anything. The shape is Astra's context in Codex with one difference: the log here is already the durable store the ledger writes for resumes, so search and notes attach to an object that exists.

One trace: a fix round across a compaction and a bot death ​

Two runs of one coding session on the pi harness, a compaction inside the first, a death inside the second, and the second needing a detail the compaction dropped.

  1. A unit's coding run r1 starts in its thread. The thread has no coding session, so the runner opens the session log; r1's seed, the channel's history plus the contract, becomes rows 0 to 5, its request row 6, and its range starts at 0 with seedFrom 0.
  2. r1 runs the tests at turn 37; the result is 40 KB of vitest output naming one failing case. The mirror appends the assistant turn before the tool runs and the result after; the record keeps the same events cut to 8,000 characters as today.
  3. r1 keeps a helper in one file and writes to its notepad: the decision, the failing case's name, the head the tests were green at. The notes call and its result are turns 61 and 62; the notepad row is replaced whole.
  4. pi compacts after a tool batch. The bridge writes the compacted note, appends pi's compaction entry as a row with its summary and the index of the first turn pi kept, and steers the notepad plus one sentence that recall reaches every turn before the summary; pi reads the steer at its next turn.
  5. r1 pushes h1, submits its description and handoff, and settles. Its range closes at turn 212 on the finished record. Nothing is cleared.
  6. The requester writes one line in the thread. The review requests changes; the runner's findings step dispatches into the thread as agent:coding. The dispatcher reads the session's tail by index, newest first, until the seed budget (60,000 tokens at 4 characters a token) is met at a boundary that begins with a user text turn: turn 148. r3's seedFrom is 148; the requester's line is row 213, the findings row 214, and its range starts at 213.
  7. A finding says the failing case from turn 37 was fixed by loosening an assertion. The model calls recall with the case's name; the search returns turn 37 whole, under the 120,000-character tool result cap; the model reads the original failure and fixes the code instead.
  8. The bot dies mid-push. The next generation reclaims r3 and rebuilds its conversation from rows 148 to 260 under the live row's system prompt; pi still answers in the container and continues on the same session.
  9. r3 confirms h2, updates the notepad, and settles; its range closes at turn 260.
  10. Round two's review approves; the runner merges. Once every run of the session has left retention and none is live, the sweep drops the session log whole.

A second, shorter case, the hole: in another session the state Worker refuses two writes in a row at turn 80 of run r5. r5 detaches as today, keeps running, and its finish record, written to the plain store, carries range: broken with 79 as the last row it wrote. The next run r6 reads that record, appends a marker row naming r5 and turn 79, seeds from the tail as usual, and a recall whose hits straddle the marker says the log has a gap there. The property of both cases: the detail the compaction dropped reached the agent that needed it, the resume rebuilt the conversation its run had, and where the store failed the log says so instead of pretending.

The difficulty map ​

  1. The session log as the one transcript store (most likely to be wrong, most work): the ledger's write-ahead, fence and detach rules on an object successive runs share, a run as a range that begins with its seed, the sweep's registry, the byte policy, storage. Section "The session log".
  2. The seed budget: how much of the tail a run starts from, against a window pi is told is 200,000 tokens whatever the model. Section "The seed".
  3. Recall on code-shaped text: FTS5's default tokenizer against identifiers, paths and error strings. Section "Recall".
  4. The notepad: its size, its injection points, what the prompt tells the agent to keep in it. Section "The notepad".

The session log ​

The constraint: the ledger writes a run's turns to one object per live run, the seed as its first rows, refuses a part the row cannot hold rather than truncating (src/core/runLedger/transcript.ts:50-53), writes each step before its tools run so a resume can settle the calls in flight, fences every write by the generation that holds the lease, detaches a run whose write is refused or fails twice rather than ending it, and clears the object at finish because a resume was its only reader. Record 0034 gave it a second reader, the next run in the thread, and the object is gone by then.

The store moves to one object per session because both readers want one place: a follow-up wants the thread's turns in order, recall wants one index, and the notepad wants a home. The rows are the transcript rows as they are today (turn index, part, JSON; attachments over 1,000,000 base64 characters stored once and referenced), with an integer id and a text column beside them for the index, appended by whichever run is live in the session under the same fence, and under the same INSERT OR REPLACE on (idx, part) so a new generation overwrites a zombie's late row as today; a replaced row's old text is read and deleted from the index before the new row and its text are written, since an external-content FTS5 table is told about deletions and never notices a replaced row on its own. Thread admission gives the log one writer at a time (thread_key UNIQUE on the live rows), and a coordinator's dispatch onto a live thread is refused as today.

A run's row and its finished record carry session: { key, seedFrom, request, range }. The range begins with the seed turns the run appended, the ones not already in the log: a first run's channel history, the channel's messages since the previous run (record 0034's rule, kept), a spawned child's parent turns; then the request row, named by request; then the run's own turns. seedFrom is the index of the first log turn the seed read without appending, the start of the tail. A resume rebuilds the rows from seedFrom to the end of the range under the live row's system prompt, as today; that is the conversation the model started from plus its own turns, with nothing outside the log. A compaction row inside that span is the summary pi wrote of everything before it plus the index of the first turn pi kept, so a session file rebuilt from the span gives pi the window it had: the summary and the kept turns after it. Finish writes the record and closes the range; it clears nothing.

A detached run keeps running as today, and because the write that failed is the detach, range: broken and the last index written land on its finished record, which goes to the plain store; the live row is unchanged. The next run in the session reads the previous record, appends a marker row naming the detached run and the last index it wrote, and seeds from the tail as usual; recall names the gap when a search straddles the marker. A Worker outage costs a session one hole, named, and never its continuity or its honesty.

The full-text index is an external-content FTS5 table over (id, text), so the rows are stored once; tool results index as text, so a test name in a failure output is findable. Bytes: the run-history policy gains sessionLog.maxBytes (default 200 MiB; decided, revisable), enforced by the object on write: past it, the oldest tool-result parts are replaced first, oldest first, by a marker naming what was dropped and that the record keeps its first 8,000 characters; user and assistant text is never dropped. Retention: RunHistoryDO keeps a sessions registry (key, thread, agent, last finish, bytes) upserted at a run's claim and finish, because the sweep runs there over finished runs and cannot enumerate another namespace; inside its transaction the sweep marks the sessions whose every run it dropped, and after the transaction it drops each marked object whose thread has no live row, owner row cleared first so a late write is refused. The dispatch path pays one range read per follow-up, bounded by the seed budget at about 240 KB, and the store pays the rows it writes today plus the index.

Invariants: (1) every turn a model in a session saw or wrote is a row in the session's log, or a marker row names the gap; the record is derived from the rows, never the other way; (2) a run's range is contiguous, begins with its seed's appended turns, names its request, and is closed at finish; two runs of one session never overlap; (3) a resume rebuilds the rows from its seedFrom to the end of its range and nothing else; (4) a write from a generation that does not hold the run's lease is refused, as today.

Failure modes: a turn over the 1.5 MB part budget is refused as today; with tool results capped at 120,000 characters and attachments externalized, no turn reaches it. A session object lost: the next run seeds from the channel and its record says seed: channel with the reason. A follow-up that lands while the previous run is being reclaimed: admission sees the live row and folds the message into that run's inbox, as today, so the range never overlaps. Storage: a coding session with heavy tool output is tens of megabytes over its life against the byte policy and Cloudflare's 10 GB per SQLite-backed object; the distribution cannot be measured today because the objects are cleared at finish, so the record's event bytes are the floor and the first ten sessions' registry bytes are the measurement.

The alternative it beat: keep one object per run and stop clearing it. Under record 0034 alone each run's object would hold every earlier turn, so a follow-up's seed would be one read; the seed budget this record adds stops that re-copying, and then a follow-up is a stitch across N objects, recall is N queries merged, the notepad has no home, and the bytes multiply by the number of runs until then.

The seed ​

The constraint: a session's log outgrows any window, and a seed that hands the whole log to pi forces a compaction on the first turn, the cost record 0034 names as more input tokens per turn. pi is told a 200,000-token window whatever the model runs, and the harness sets none of pi's compaction knobs today.

The seed is bounded because the window is: the notepad in the system prompt, then the log's newest turns whose text fits the seed budget, 60,000 tokens estimated at 4 characters a token as the memory block estimates, read from the object by index range newest first, never the whole log into the Worker, cut at a boundary that begins with a user text turn so pi's session file opens as the provider requires and no tool call is parted from its result, then the channel's messages since the previous run and the request, which the run appends as the first rows of its range. Decided, revisable: the number is a third of pi's declared window, leaving the rest for the run's own work. seedFrom on the row names the turn the tail began at, so recall and a person both know where the window started. A seed whose newest boundary alone exceeds the budget takes the notepad, the messages since and the request, and says so in a note.

Failure mode: a session whose recent turns are all large tool outputs seeds with few turns of conversation; the notepad carries the state across, which is why the prompt tells the agent to keep it current before long tool runs.

Recall ​

The constraint: FTS5's default tokenizer splits on punctuation, so residentFleet.ts indexes as two tokens and an error string as its words; a model searching for a symbol finds it, and one searching for an exact path finds its pieces.

recall { query, limit? } searches the caller's own session log and returns up to limit (default 5) matching turns as { turn, role, snippet } in relevance order, the order the memory store's search uses, each with its turn index so the model can read around it; recall { turn } returns one turn whole, capped by the runner's 120,000-character tool result cap with the usual truncation marker. Both read the session the run belongs to and nothing else; the session is the requester's thread, so the runs:read predicate that gates get_run_status gates these, and a denied read is an empty result. Both tools join every toolset that runs a loop on a pi preset, so a read-identity child has them too: the notepad is its own session's state, not a repository write. The tools are relayed like every bot-side tool under pi. A query with no hit says so and suggests the notepad; a first run is told the log begins with it; a search that straddles a gap marker says so.

The alternative it beat: an embedding index. It finds paraphrases FTS misses, costs a model call per turn indexed and a second store, and the model searching its own log knows the words it used; if recall's hit rate says otherwise (below), the index changes and the tool's shape does not.

The notepad ​

The constraint: the summary a compaction leaves is pi's and drops what pi judged unimportant beyond the newest 20,000 tokens; the agent knows better what it will need, and a follow-up run needs that knowledge before it has read anything.

One document per session, at most 8 KiB, written whole through notes { text }, so the call and its result are turns in the log and the notepad's history is the log's. It is read at three points: rendered into the system prompt at every run start under a heading naming it the agent's own notes for this thread; steered into the live context after every compaction_end unless pi has settled, at pi's next turn boundary, with the sentence that recall reaches every earlier turn (pi compacts after a tool batch before its next response, so the steer lands one response late and the summary plus the newest 20,000 tokens cover that response; a compaction at the end of a run needs no steer, since the next run's prompt carries the notepad); returned by notes {} on demand. A resumed run keeps its row's system prompt, so a notepad written after the run started reaches a resumed pi by the steer alone. Every pi preset's prompt says what belongs in it, decisions and their reasons, names of things found (failing tests, heads, files), what is not yet proven, and that the notepad is the one thing sure to survive a compaction and reach the next run. The record keeps the final text as a notes event so the run page shows it. Decided, revisable: 8 KiB, about 2,000 tokens, is a page of notes and about one thirtieth of the seed budget.

Failure mode: an agent that never writes notes gets today's behavior plus recall; one that writes too much hits the cap and the tool answers with the size and a request to trim.

Why not X ​

  • Why not rely on pi's compaction summary, since record 0032 makes the record a superset of the model's context? A superset the model cannot read is a superset for people; the only fix for a summary that dropped what a later step needs is a way back to the originals from inside the run.
  • Why not tune pi's compaction instead? Raising keepRecentTokens or the reserve moves the threshold and disabling compaction meets the window; either delays the loss or trades it for the trap 0034 tied the seed rule to the pi harness to avoid. The knobs stay available for the budget; they do not make anything reachable.
  • Why not persist pi's own session file and index that? It lives in the container, is deleted at detach, is one process's tree in pi's format, and the native presets never have one; the mirror already writes its content to the ledger in the runner's vocabulary. The one thing worth taking from it, the compaction entry, this record takes as a row.
  • Why not make memory the notepad? Memory is distilled knowledge across threads, written by a reflection pass after the reply, injected as 8 records within 800 tokens, off by default, and by its own type "never a raw transcript". A notepad is one agent's working state in one thread, written by the agent mid-run.

Boundaries ​

Not decided here: which runs seed from a session (record 0034: pi presets, same agent, same thread; unchanged), while three of 0034's sentences are amended by this record and say so in place: its session definition (the log, not the last run's transcript), its growth paragraph, and its first success criterion (the notepad is the one summary a run starts from, written by the agent it belongs to); the unit page's search for people, which reads one session's index at a time and follows the check-in the maintainer asked for before dashboard work; whether the native loop ever reads a session (it does not; it retires). Compatibility: a run on a deployment without the session object seeds from the channel and says so; RunTranscriptDO stays bound, new runs never write to it, and a deleted_classes migration removes it and its objects once no live row lacks session and seven days have passed, the path the friction store already took. Migration: nothing stored changes meaning; rows without session are runs of the old shape and resume from their own object until then.

What would change our mind ​

  • If fewer than one recall in ten finds what the agent looked for (judged from the next turn using or discarding the result), the tokenizer changes first (a trigram tokenizer for identifiers), and an embedding index after. Evidence: the recall tool events of the first ten sessions.
  • If the first ten sessions' runs spend more than half their turns before the first tool call reading the seed, the budget halves. Evidence: model.turn input tokens on the first step against later steps.
  • If agents leave the notepad empty in more than half of sessions that compacted, the prompt's instruction is wrong or the tool is, and the harness writes a first draft of the notepad from the compaction summary itself. Evidence: notes events against compacted notes.
  • If fewer than one coding run in twenty compacts once coding runs on pi by default, the steer and the compaction row stay and the seed budget is what matters. Evidence: compacted notes over the first hundred pi coding runs.
  • If the byte policy drops tool results in more than one session in ten inside a week, the default doubles and the record changes. Evidence: the marker rows the policy writes.

Reversal: the session object reverts to per-run objects by one pull request while runs in flight finish on the object they started in; the tools and the steer are one revert each.

Rollout ​

Two pull requests under unit U34, in order, then one under U35: (1) the session log as the transcript store: the object with its external-content index and byte policy, the sessions registry and the sweep's live check with the drop after the transaction, the row's and record's session, the write and resume paths over it with the seed's turns as the range's first rows, the compaction row from pi's event, the end of the clear at finish and abandon, the class binding and migration in the state Worker's template, with the ledger's own tests over the fence, the detach and the range, receipted live by a resume from the session log and the clear gone; (2) the seed budget in the dispatcher, the recall and notes tools relayed to pi in every looping toolset, the notepad in the system prompt and the compaction steer, the prompts' notepad instruction, the notes event, and the spec rows in run-history, thread-admission, harness-pi and the session spec; (3) search on the unit page, per session, after the check-in. Record 0034's PR 3 becomes (2) here, on top of (1); (2) starts once (1)'s live receipt is in. RunTranscriptDO's deletion is a follow-up gated as Boundaries says.

Open questions ​

QuestionOwnerResolves itBefore
The seed budget's numbermaintainerthe first ten sessions' first-step input tokensrevisable after PR (2)
Whether tool results index whole or only their first N KBmaintainerrecall hit rate and index size on the first ten sessionsPR (1)
The byte policy's defaultmaintainerthe first ten sessions' registry bytesrevisable after PR (1)

Validation criteria ​

CriterionProof
A run's turns append to its session's log before its tools run, under the run's generation fence and the (idx, part) upsert with the old text deleted from the index first; a write from another generation is refused; the log is not cleared at finish or abandon; the row and the record carry session with the seed's appended turns first and the request named; a detached run's record reads broken with the last index written; the compaction row carries pi's summary and the first kept index[gap] PR (1): deploy/cloudflare-memory/sessionLog.test.ts, src/core/runLedger/writeThrough.test.ts, src/core/harness/pi/mirror.test.ts
A resume rebuilds the rows from seedFrom to the end of the range, compaction rows rendered as pi's entry, under the live row's system prompt, and settles in-flight calls; a row without session resumes from its own object[gap] PR (1): src/core/runLedger/resume.test.ts, src/core/harness/pi/harness.test.ts
The sessions registry is upserted at claim and finish; the sweep marks inside its transaction and drops after it, never a session whose thread has a live row, owner row first[gap] PR (1): the state Worker's retention tests
The byte policy replaces the oldest tool-result parts first with a marker and never user or assistant text; the default and bounds validate with the policy[gap] PR (1): deploy/cloudflare-memory/sessionLog.test.ts, src/core/runRecord.test.ts
The seed is the notepad, then the newest turns within the budget read by index range and cut before a user text turn, then the messages since and the request as the range's first rows; a newest boundary over the budget yields notepad, messages since and request with a note; a broken predecessor yields a marker row and the tail[unit] src/core/dispatch/seed.test.ts::sessionSeed — the tail, the lines since, the request::*, ::sessionSeedFor — the seed read from the ledger, with the notepad::*, src/core/runLedger/writeThrough.test.ts::the session log — a run is a range of it::a seed that reuses the log's tail appends only what is new…
recall searches the caller's session alone in relevance order with turn indices, returns a named turn whole under the tool result cap; a denied read is empty; a first run is told the log begins with it; a straddled gap is named[unit] src/tools/session.test.ts::recall — the caller's own session log::*, deploy/cloudflare-memory/sessionLog.test.ts::session log object — the search recall reads and the notepad notes writes::*
notes replaces the notepad up to 8 KiB and refuses over it naming the size; the notepad rides the system prompt at run start and is steered after every compaction_end unless pi settled; the final text is a notes event on the record[unit] src/tools/session.test.ts::notes — the agent's notepad for this thread::*, src/core/dispatch/provision.test.ts::composePrompt — the system prompt for the first turn::a follow-up seeded from its session carries its notes and the compaction's summary…, src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::after every compaction pi is steered with the notepad…
Live: a resume of a pi coding run reads its range from the session log and the transcript object is not cleared at finish[agent] PR (1)'s receipt, human-gated
Live: a coding session on pi that compacts recalls a tool output from before the compaction, and the next run in the thread starts from the notepad and the log's tail[agent] PR (2)'s receipt, human-gated

Sources ​

  • The Astra context approach as quoted from its launch post (a rolling log across context windows, notes the model keeps, earlier windows searchable), shared by the maintainer on 2026-09-14; Cognition's "Don't build multi-agents" on context compression being hard to get right; pi 0.85.1's docs/settings.md, docs/rpc.md (the compaction_end payload: summary, firstKeptEntryId, tokensBefore, estimatedTokensAfter) and docs/compaction.md (the threshold checked after each tool batch, before a prompt and after a run ends).
  • Records 0019, 0032, 0034; the specs run-history, harness-pi, memory, run-loop, thread-admission.

Appendix A: the survey at 6d26cf02 ​

FactWhere
The transcript is cleared best-effort after the finish or the abandon commits; item 33; nothing else sweeps the objectsrc/core/runLedgerWorker.ts:258-277; docs/reference/specs/run-history.md item 33; deploy/cloudflare-memory/worker.ts (the alarm sweeps run_events, never RUN_TRANSCRIPTS)
One transcript object per live run, keyed by run id; rows (idx, part, json) under INSERT OR REPLACE; an owner row; read whole in order; no thread key; the seed stored as rows 0 to ndeploy/cloudflare-memory/worker.ts:2372-2456; src/core/runLedger/writeThrough.ts:457-458
Transcript fences: 2 MiB request, 1,500,000 bytes a part, 1,000,000 base64 characters before an attachment is referenced; a part over budget is refused, never truncatedsrc/core/runLedger/types.ts:20-25; src/core/runLedger/transcript.ts:32-53
A refused or twice-failed write detaches the run, which goes on untracked; its finish record goes to the plain storesrc/core/runLedger/writeThrough.ts:8-13,424-450,502-508
A resume rebuilds messages from the transcript alone and keeps the live row's system promptsrc/core/runLedger/resume.ts:95-111; src/core/dispatch/provision.ts (resume.row.system); docs/reference/specs/run-history.md item 29
Record caps: 1.5 MiB a record, 64 KiB an event, middle events dropped; tool output 8,000 and commands 4,000 characters; maxBytes sums runs.bytes alonesrc/core/runRecord.ts:392-412,624-700; src/core/runEvents.ts:602,608; deploy/cloudflare-memory/worker.ts:1657-1665
The sweep walks finished runs rows only inside transactionSync; live_runs carries thread_key, the agent inside meta_jsondeploy/cloudflare-memory/worker.ts:1143-1156,1621-1675,1828-1850
The compacted note, prose counts; a failed compaction is a harness_error; the mirror writes no compaction rowsrc/core/harness/pi/bridge.ts:342; docs/decisions/0032-pi-is-the-harness-the-native-loop-retires.md:191
pi's declared window 200,000; the settings file writes no compaction knob; the harness sends set_auto_retry, get_state, prompt, steer; the transport closes at settledsrc/core/harness/pi/process.ts:174,212; src/core/harness/pi/harness.ts:349,368,410
pi's compaction knobs, threshold rule and event payloadpi 0.85.1 docs/settings.md, docs/compaction.md:37, docs/rpc.md:1079-1090 (not vendored; read from the pinned package)
pi's extension surfaces the repo records: context modifies messages, before_agent_start carries the system prompt; our extension registers tools and the tool_call vetodocs/decisions/0032-pi-is-the-harness-the-native-loop-retires.md:199,207; src/core/harness/pi/extensionSource.ts
get_run_status returns the final reply alone; list_runs never a messagesrc/tools/runs.ts:78-81,428-494
The runs store's only event read is by sequence; FTS5 exists in the memory store alone, a copying table kept in step by explicit writes, results in bm25 orderdeploy/cloudflare-memory/worker.ts:1088-1119,1875,259,264-290
Memory scopes, reflection, budgets, "never a raw transcript"src/core/memory/scope.ts:26-58; src/core/memory/reflection.ts:40-58; src/core/memory/scorer.ts:25-26,71-73; src/core/memory/types.ts:8-11
One live run per thread (thread_key UNIQUE); a coordinator's dispatch onto a live thread refused; a follow-up folds into the live run's inboxdeploy/cloudflare-memory/worker.ts:1146; docs/reference/specs/thread-admission.md items 1, 5 and 8
Thread history is the last 50 replies; context events keep 20 turns within 256 KBsrc/channels/slack.ts:692; src/core/dispatch/messages.ts:16-17
The tool result cap, applied on the pi relay toosrc/providers/types.ts:55; docs/reference/specs/run-loop.md item 13
The friction store's deletion as the precedent for removing a classdeploy/cloudflare-memory/wrangler.template.jsonc:67
Record 0034's session definition, growth paragraph and first success criterion, amended in place by this recorddocs/decisions/0034-one-agent-per-unit-a-run-continues-a-transcript.md:12,32,90
Cloudflare's documented limits per SQLite-backed Durable Object: 10 GB storage, 2 MB a rowCloudflare Durable Objects limits page, read 2026-09-14