# pi-context-epochs

Model-driven **context epochs** for [Pi](https://github.com/earendil-works/pi): start a fresh working context inside the **same** session, with **no LLM-generated summary**, while the full transcript stays append-only, durable, searchable, and recoverable.

Inspired by OpenAI Codex's context management work ([openai/codex#27488](https://github.com/openai/codex/pull/27488), the `new_context` tool and context-window replacement), implemented as a thin Pi extension using Pi's own durable compaction machinery.

## Motivation

Long agentic sessions exhaust their context. The two options Pi ships are:

- **Compaction** (`/compact`, auto-threshold): an LLM summarizes old messages. That costs a summarization call, is lossy, and replaces exact history with a paraphrase.
- **New session** (`/new`, `ctx.newSession()`): a different session file. Identity, notes, and continuity are lost.

Neither is "retire the working set and continue". The missing primitive is a **durable context boundary**: old messages leave the model's active context, the transcript keeps everything, and the session keeps its identity. Pi upstream does not yet expose `ctx.newContext()` ([proposal, auto-closed as earendil-works/pi#8972](https://github.com/earendil-works/pi/issues/8972)).

This package provides that primitive as an extension by **reusing Pi's own compaction projection**: an epoch boundary is a real `CompactionEntry` whose kept-entry id can never match a real entry, so Pi itself - context reconstruction, token accounting, auto-compaction checks, `/tree`, fork, resume, reload - treats everything before the boundary as retired.

```
Pi session (one JSONL file, append-only tree)
│
├── epoch 0   user/assistant/tool messages
├── epoch 1   user/assistant/tool messages
├── epoch 2   ← active model working context
├── Notes     ← model-curated, session-scoped, branch-aware
└── History   ← full transcript; retired epochs are still searchable
```

## What the model gets

Exactly three tools (plus a `/epoch` command for the human):

| Tool | Purpose |
|------|---------|
| `new_context` | End the current epoch and continue the same session in a fresh working context. No arguments. No summary. |
| `context_notes` | `list` / `read` / `write` / `delete` small, session-scoped notes that survive rollovers. |
| `context_history` | `search` (lexical) and `read` (by stable entry ref) the durable transcript, including retired epochs. |

### `new_context` semantics

Calling `new_context` arms a rollover. When the current agent run settles, the extension:

1. Asks Pi to compact, and (via the `session_before_compact` hook) supplies a **package-owned, deterministic** boundary entry - **no LLM call**:
   - `summary`: a small `<context_epoch>` marker (epoch id, previous epoch id, trigger, current note keys, the verbatim latest user request as a continuation anchor, one re-grounding instruction).
   - `firstKeptEntryId`: a value that can never match a real entry id, so Pi's projection keeps only the marker plus everything after it.
   - `details`: `{ kind: "pi-context-epochs/epoch", version: 1, epochId, previousEpochId, epochIndex, trigger, createdAt }`.
2. Pi appends the entry, rebuilds the agent's active context from its own session projection, and emits `session_compact` (the extension verifies the persisted entry is really package-owned before continuing).
3. A one-line continuation nudge starts the first turn of the new epoch.

Triggers recorded in the boundary: `model` (the tool), `manual` (`/epoch new`), `threshold` (Pi auto-compaction threshold), `overflow` (provider context-overflow recovery).

The rollover never calls a summarizer. The only LLM cost after a rollover is the model's own continuation turn.

### Notes philosophy

Notes are for **non-reconstructable semantic state**: explicit user decisions, rejected approaches and why, non-obvious constraints, unresolved blockers, acceptance criteria, external side effects. Things that are cheap to re-derive (source files, git, tests, package metadata, history) should be re-derived, not copied. Notes are session-scoped: they end with the session; there is no cross-session memory.

Notes persist as Pi custom entries on the session **branch**, so `/tree` and fork replay only the notes that belong to the branch you are on.

### History

`context_history search` does case-insensitive substring matching with a simple score (occurrences + recency + role bias) over the current branch (or the whole file with `scope: "session"`). No embeddings, no vector DB, no background indexer - the session entries are the source of truth. Results carry stable refs (Pi entry ids). `read` returns one entry plus a bounded neighborhood. Reasoning/thinking blocks are never returned; tool results are tightly truncated; total read size is bounded (`historyMaxChars`, default 16k chars).

## What the runtime gets (the model does not see this)

- **Context pressure reminders** - one-shot per epoch per band, computed by the runtime from `ctx.getContextUsage()` against the model's real `contextWindow` (no per-turn counters, no prompt churn):
  - soft at ≤ 25% remaining (configurable): "preserve non-reconstructable state; start a new context when the working set is no longer useful"
  - critical at ≤ 10% remaining: only sent when `autoRollover` is off - otherwise that zone is owned by Pi's threshold/overflow machinery (see below), and a steered critical reminder would land stale in the fresh epoch.
- **Automatic rollover** - when Pi's auto-compaction threshold fires (or a provider overflow needs recovery), the extension converts the compaction into an epoch boundary instead of a summarization call. Loop-safety: after the boundary the active context is tiny, so the threshold check cannot re-fire; tests cover the no-loop property.
- **Verification** - after any rollover, the extension checks that the persisted compaction entry is package-owned. If another extension overwrote the compaction result, it fails closed: no continuation is sent, and the conflict is surfaced.
- **Conflict scan** - at startup the package warns about installed extensions known to also replace compaction results (pi-agenticoding, pi-context, pi-context-prune, billion-context-pi, pi-codex-compaction, pi-codex-compact, pi-session-continuity, pi-continue).

## Why the boundary is "real"

`buildContextEntries()` - Pi's single projection used for prompt building, token estimation, auto-compaction decisions, reload, resume, and tree navigation - keeps, for the latest compaction on the branch: the compaction entry itself, entries from `firstKeptEntryId` (never matches ⇒ none), and everything appended after it. Therefore:

- **Active context**: marker + new-epoch messages only.
- **Token accounting**: estimated from the projected context; the stale-usage check in Pi's `_checkCompaction` prevents re-triggering off pre-boundary usage.
- **Reload / resume**: `SessionManager.open(file)` reproduces the same projection - the boundary is in the file, not in extension memory.
- **`/tree` / fork**: the boundary is an ordinary branch entry; switching branches switches the epoch sequence (and notes) to that branch.
- **Durability**: the raw JSONL keeps every pre-boundary entry, append-only.

## Installation

```bash
pi install /Users/zzzhizhi/Developer/zzzhizhia/pi-context-epochs
pi list          # should show pi-context-epochs
```

Local path installs are referenced in place (no copying), so editing the project directory is the update; restart pi or run `/reload` to pick up changes.

Uninstall:

```bash
pi remove /Users/zzzhizhi/Developer/zzzhizhia/pi-context-epochs
```

## Usage

- Ask the model to "start a new context" / "roll over" - it calls `new_context` (writing essential notes first).
- `/epoch` - status (epoch, usage, pressure, notes, last rollover).
- `/epoch new` - user-triggered rollover (no summary).
- `/epoch notes` / `/epoch history` / `/epoch debug` - notes list, epoch boundaries, diagnostics (raw vs active context sizes, boundary details, conflicts).

Headless/print mode works; continuation waits for the follow-up run to settle there.

## Configuration

`~/.pi/agent/context-epochs.json` (optional; defaults are safe, invalid values warn and fall back):

```json
{
  "enabled": true,
  "softRemainingRatio": 0.25,
  "criticalRemainingRatio": 0.1,
  "autoRollover": true,
  "historySearchLimit": 8,
  "historyReadAround": 2,
  "historyMaxChars": 16000,
  "noteMaxChars": 8000,
  "continuationAnchorMaxChars": 2000
}
```

## Compatibility & limitations

- **Manual `/compact` is untouched** (native summarization is preserved) unless the instructions string is the package's internal marker.
- **Competing compaction rewriters**: last `session_before_compact` handler wins in Pi. The extension warns at startup and fails closed at verification time; it never sends a continuation for a boundary it does not own.
- **Tiny sessions**: a rollover needs content before Pi's cut point (`keepRecentTokens`, default 20k). `new_context` pre-checks this and refuses gracefully on very small contexts instead of creating a meaningless boundary.
- **Not checkpoint/rewind**: epochs replace the whole active working context generation; they do not snapshot or restore granular states, and they do not interfere with git-checkpoint style tools.
- Thinking/reasoning content is never exposed through history reads.

## How this differs from Pi compaction

Pi compaction summarizes (LLM call, paraphrase). This package retires: no summary, no summarizer cost, exact history still retrievable. It rides on Pi's compaction path because that path is the one place where pi rebuilds agent state from the durable session projection - which is exactly what "same session, fresh context" needs.

## Relation to Codex's `experimental_mode` context management

Parity: same-session fresh working context (`new_context`), token-budget-style sparse pressure reminders computed by the runtime, no planner/orchestrator machinery. Differences: Codex's runtime replaces the active history as a first-class session operation and recomputes usage natively; here the boundary is expressed through Pi's compaction projection (identical observable behavior, different mechanism), and Codex's budget reminder cadence/labels differ in detail. This package does not clone Codex prompts or role workflows.

## Development

```bash
npm install
npm run typecheck
npm test          # unit + SDK integration tests (scripted model, no network)
npm run check     # typecheck + tests
npm run measure   # model-facing overhead measurement
```

## License

MIT
