# Architecture

pi-context uses Pi's session branch as the durable source of truth. It does not maintain a second transcript or a hidden prompt overlay. Runtime state exists only to schedule work and reserve a reminder until Pi persists it.

## Ownership

| Module | Responsibility | Boundary |
| --- | --- | --- |
| `index.ts` | Thin public entrypoint: register the context runtime and tool adapters, and preserve public history/notes re-exports | Pi extension API |
| `context/runtime.ts` | Compose runtime hooks, toggle/reset commands, boot construction, marker/boot boundaries, and provider projection | Pi extension API plus context and notes modules |
| `history/history.ts` | Project file-order seq addresses and current-branch items into windows | `SessionReader`, read-only entry list and branch |
| `context/context-window.ts` | Own durable window identity, select the active boot, project provider context, and account for active-window usage | `SessionReader` plus Pi context/system projection APIs |
| `notes/address.ts` | Validate virtual note paths and resolve address/glob forms | Note address syntax; no session-branch selection |
| `notes/notes-snapshot.ts` | Acquire the five notes homes once into a closed boot snapshot and isolate filesystem-home failures | Filesystem-backed notes homes; no rendering or UI effects |
| `notes/store.ts` | Read and mutate the filesystem-backed homes; distinguish an absent home from a real read failure | Notes filesystem only; boot acquisition isolates one home at a time |
| `history/history-tools.ts` | Public history schemas and tool results over branch projections | Pi tool API plus read projections |
| `notes/tools.ts` | Filesystem note tool adapters; validate and perform note reads, writes, edits, listings, and searches | Pi tool API plus notes filesystem |
| `context/budget.ts` | Own the default-path per-extension-instance settings cache, read injected policy live, report usable budget, stage guidance/warning drafts, and resolve automatic reset decisions | Pi settings/context hooks plus protocol warning text |
| `context/thresholds.ts` | Read the selected public settings authority and derive reminder/reserve/warning thresholds from Pi's reserve plus the pi-context margins | Pi `SettingsManager`, read-only; no mutable cache or UI effects |
| `settings.ts` | Merge the global and project `pi-context` settings object per key | Parsed Pi settings scopes; no I/O or runtime state |
| `dream/settings.ts` | Validate and resolve the configured dreamer model from the shared settings merge | Pi `SettingsManager`; no runtime context state |
| `context/prompts.ts` | Render the static boot block, note index, and low-budget reminder from explicit data | Snapshot data and protocol text; no filesystem acquisition or UI effects |
| `context/reset-lifecycle.ts` | Own reset requests, turn-end batching, recovery and continuation | Pi lifecycle hooks and injected boundary builder |
| `protocol.ts` | Persisted entry tags, protocol text and defaults | No imports or effects |
| `tool-schema.ts`, `tool-output.ts` | Shared wire-schema primitives and JSON result encoding | No session state |

Dependencies flow from the composition root and tool adapters to projections and protocol constants. The published Pi entry is `dist/extension.js`, built from `src/index.ts`; its public `pi-ai/utils/estimate` dependency is inlined so Pi's root-package aliases cannot misresolve the utility subpath. Host-owned package APIs remain external. Projections cannot send messages, compact, notify, or mutate the session. A runtime framework or generic event bus would add indirection without strengthening these boundaries.

## State and persistence

`reset-lifecycle.ts` is the sole `turn_end` composer: it accepts incoming drafts, drains budget-owned guidance/warning drafts, and appends a reset boundary for a manual command, explicit tool request, or the hard-reserve safety path. Manual commands commit at the next successful turn end after the complete tool batch. Budget close-outs stay armed across note/tool turns and fall back from `agent_before_settle` only after a successful stop and queued work are drained. Repeated requests for the same window deduplicate. See [reset lifecycle](reset-lifecycle.md).

A new reset is a native empty-summary compaction checkpoint configured to retain none, followed by one `pi-context/reset-marker` custom entry with `{ windowId: string }`, a hidden `pi-context/boot` whose `details.windowId` matches, and a hidden continuation. The checkpoint is the canonical-history cut; the marker is the durable logical window identity. Raw session entries remain available to history tools. Legacy marker-only branches keep a narrow projection fallback. `context/context-window.ts` owns marker/checkpoint validation, active-branch scan, root/current IDs, and per-window message lookup; `history/history.ts` consumes those identity primitives while projecting entries. The scan never uses a global entry tail.

The runtime in `context/runtime.ts` performs final context projection by selecting the active boot through `details.windowId` and folding only the dropped system prefix through Pi's `getCurrentSystemMessage`. Later prompt patches and new messages stay in order. A missing boot aborts the hook with a safe head and notice rather than silently sending raw history. Startup/tree handling repairs only a genuinely incomplete marker tail: a missing boot with no later raw message, custom message, compaction, branch summary, or authoritative raw boot. If later work exists, boot creation is refused and `/wipe-memory` is the explicit recovery path; it does not parse or migrate the legacy reset-v2 protocol.

Boot and reminder deduplication inspect the current branch-local window. Reloading JSONL therefore does not duplicate messages, while navigation to a sibling branch cannot inherit another branch's window state. A fork/clone receives a new session ID while copying its selected path, so startup must also verify that a root boot's `details.windowId` matches the new `rootWindowId(sessionId)` before treating it as present.

Manual `/wipe-memory` acknowledges the request immediately and commits at the next successful `turn_end` after the complete tool batch. While the model is running, it uses the current turn without sending a new model message; when idle, it sends a hidden checkpoint prompt to start one turn. That prompt and the budget warning share the same text but have distinct persisted types, so an aborted manual request cannot suppress a later budget warning. The budget warning remains a multi-turn close-out with an explicit `wipe_memory` commit or a successful normal-stop fallback. Abort/error never counts as successful close-out. The checkpoint, marker, boot, and continuation are emitted as ordered session-boundary drafts. Pi's retain-none compaction entry persists the canonical cut; the marker/boot preserve pi-context's logical identity and protocol.

Boot is a fixed snapshot for its window, stored as an extension custom message and converted by Pi to a user-role model message. History internally distinguishes native system summaries and extension-authored custom messages; its public `context` event groups both without implying provider instruction priority. A system-role projection is technically possible through `context_with_system`, but would change the authority of the mixed protocol/MAP content and provider-specific prompt/cache behavior; it is not a prerequisite for persistence. Boot injection is silent, including startup, reload, and boot repair. Only an actual context reset produces `pi-context: memory cleared · <windowId>` through `ctx.ui.notify`, after the verified checkpoint, marker, boot, and continuation are present in the session. Committed resets are checked at the next turn start or final settlement. Uncommitted reset drafts never announce success. Boot and continuation messages retain `display: false`.

History reads reconstruct the selected session branch on demand without a cache. Stable seq values are allocated from the session's append-order entry list, while visibility remains current-branch-only, so branch navigation cannot expose history from a sibling. The public stream combines each tool call with its result at the call seq; a prior result seq aliases that event for reads, and unpaired results retain their own seq. Summaries and injected messages project as `context`. List and search page chronologically with seq anchors.

The boot notes index in `pi/notes/snapshot.ts` is a closed snapshot: the current session, project, human, agent, and model homes are each loaded at most once while constructing a boot. `context/prompts.ts` then renders that explicit snapshot without reading the filesystem or consulting the clock. MAP bodies and pocket metadata are derived from the same snapshot, so a boot cannot mix two filesystem reads. Note storage and home traversal use `node:fs/promises`; same-file operations queue by absolute physical filename across store instances in this process, without promising symlink/case-alias or cross-process locking. Known persisted metadata is camelCase. Old snake_case keys are unrecognized extras, not interpreted or migrated; startup does not migrate notes. A missing home (`ENOENT`) is normal. A real read failure omits only that home's index, preserves healthy homes, adds a model-facing `notes_list` recovery notice, and notifies the human once for that window. Boot/reset construction captures session, agent, and model identity before awaiting the snapshot, then checks lifecycle generation, active window, enabled state, and abort status before sending or returning artifacts; stale completions cannot commit into a switched or shut-down session. Note reads never mutate files or create fallback state.

Notes remain in their filesystem-backed homes, unchanged by session branch navigation.

The `/wipe-memory` command only waits for idle when the session is busy with non-agent work; during model output it registers the next turn-end request immediately. While enabled, `/compact` is cancelled with an actionable `/wipe-memory` notice. Disabling pi-context stops new automatic resets, but an existing native checkpoint/marker still excludes earlier history and native compaction is still cancelled on that marked branch; a fresh root may use native Pi semantics. Threshold and warning accounting use active-window provider usage rather than pre-reset global usage. Budget policy and staged prompts are instance-owned, so concurrent sessions cannot share reserve/enablement or uncommitted drafts; default file-backed policy is cached per instance, while injected policy is resolved live on each decision and model/session transitions reset the diagnostic lifecycle.

## Evidence and limits

The retained integration tests use real SessionManager and SettingsManager instances. They check reset-window history retention, seq-addressed and anchor-paged history, bounded history/notes reads with resumable character windows, notes-home isolation, and settings precedence. The suite is a representative regression set; the standalone coherence, pagination property, history, and threshold suites were removed during test reduction.

Scripted SDK tests execute the real Pi agent loop with no model/network request. They inspect provider contexts and durable entries for idle and streaming manual requests, budget close-out, explicit and normal-stop commits, abort/error cleanup, same-window command deduplication, queued steering/follow-up delivery, retain-none checkpoints across two resets and disk reopen, bounded overflow recovery, and settings authority. Smaller dream tests retain lock exclusion/ownership, jailed writes, CLI audit/report failure propagation, and read-only doctor behavior. Removing duplicate scenarios and edge-case matrices reduces coverage; the remaining tests do not establish every malformed-input case, branch interleaving, external-provider behavior, or filesystem failure mode.

Mixed tool batches finish before a direct tool or manual reset boundary. Budget close-outs let queued steering/follow-up work run once in the old window before fallback; turn-end resets let Pi schedule queued work across the new boundary. Scripted loop tests check that these messages are neither dropped nor replayed. Runtime overflow recovery is bounded to one reset/retry per failure chain, while ordinary retryable provider errors remain Pi-owned. When either the source or destination branch has a reset marker, `/tree` navigation still succeeds but its generated summary is replaced by an empty summary plus a notice; raw history and branch selection remain available. If neither branch has a marker, Pi's native tree summary is retained.
