---
title: "Budget-triggered session reset: the human-like restart fak does at a token budget"
description: "When a long session crosses its token budget, fak distills the durable facts, drops the ephemera, keeps the recent tail, and continues on a fresh session — the carryover is a pluggable contributor registry, and the auto-continue is one opt-in flag."
date: 2026-06-25
---

# Budget-triggered session reset

*What a human does when a chat gets too long — start over, but keep what matters — made
into a mechanism fak can run at a token budget, transparently, for a Claude Code or Codex
session it proxies.*

## TL;DR

A user sets a token budget (e.g. `fak serve --context-budget-tokens 150000`). When a served
session crosses it, fak does not just stop. With `--reset-on-budget` on, it **distills the
conversation into a compact carryover, re-arms a fresh session, and the client's next request
continues transparently** — no 409, no manual restart. The carryover is the smart part: it
keeps the **durable facts** (preferences, identity, conventions — classified by the SHIPPED
`ctxmmu` durability prior), a **task recap**, a **warm-prefix descriptor**, and the **recent
verbatim tail**, and it drops the ephemera ("it's 3pm" evaporates; "I prefer concise answers"
survives). The carryover is a **pluggable contributor registry**, so new items register
without editing the core.

This was ~90% assembly. fak already had the **detection** (`session.Table` budgets +
`DebitUsage` minting a continuation id on context exhaustion) and the **directive**
(`gateway` emitting a `SessionResetDirective` to a supervisor). What was missing was the
**smarts and the auto-continue**: the directive carried no carryover content, and nothing
re-armed the session. This change fills exactly that gap.

## The seam (what was already there)

| Rung | Where | Status before |
|---|---|---|
| Per-session token budget | `internal/session` `Budget{TurnsLeft, TokensLeft, ContextTokensLeft}` | shipped |
| Budget exhaustion → drain + continuation id | `session/usage.go` `DebitUsage` | shipped |
| Served proxy debits per turn | `gateway/session_admit.go` `debitServedSessionTurn` | shipped |
| Next-turn refusal at the boundary | `gateway` `beginServedSessionTurn` → 409 | shipped |
| Passive reset directive to a supervisor | `gateway` `SessionResetDirective` | shipped (but **empty** — no carryover) |
| **Durability classifier** ("what to keep") | `ctxmmu.classifyDurability` | shipped (`docs/CONTEXT-IS-NOT-MEMORY.md`) |
| **The carryover builder** | — | **this change** |
| **The re-arm verb** | — | **this change** |
| **The auto-continue** | — | **this change** |

## What this change adds

**1. The re-arm verb — `session.Table.Recontinue(parent, child, fresh Budget)`.**
The one write that may follow a *terminal* budget drain. The existing control verbs refuse a
Stopped session by design ("you start a new session, you do not un-stop one"), and `Reset`
*deletes* the record. `Recontinue` instead mints a NEW live session under the continuation
trace, links it back via `ParentTrace` with `Generation` incremented, stamps the closed
reason `BUDGET_RESET`, and leaves the drained parent intact so the exhaustion stays
observable. (`internal/session/table.go`, `decide.go`.)

**2. The carryover builder — `internal/sessionreset` (a pluggable contributor registry).**
`BuildSeed(Input) Seed` folds every registered `Contributor` deterministically over the
drained transcript. Four built-ins register by default:

- `durability_facts` — keeps the `durable` lines, drops `turn`/`session`, by reusing the
  SHIPPED classifier through the new `ctxmmu.ClassifyText(role, content)` (the chat-shaped
  entry to the same `classifyDurability` the admit gate runs — not a fork).
- `task_distill` — a deterministic, extractive "where we are" recap (objective + latest ask).
  A model-call distiller that summarizes the middle is a named follow-on.
- `warm_prefix` — a descriptor for replaying the stable prefix from the vCache prefix-DAG
  (`vcachechain.ReplayCost`), so the fresh window does not re-prefill the system preamble.
  **Honest scope:** vCache is a decision layer (off the live path), so v1 emits the recall
  PLAN, not a live KV splice. Live same-model KV reuse is the named follow-on.
- `verbatim_tail` — the last N turns verbatim, like a human keeping the last thing on screen.

`sessionreset.Register(c)` is the open seam: "other items that come up" register without
editing `BuildSeed`. The package is a tier-2 mechanism — it imports only `ctxmmu`,
`vcachechain`, and stdlib (NOT the wire `agent` type), so the tier-4 gateway converts at the
boundary.

**3. The gateway auto-continue — `Config.ResetOnBudget` (opt-in, fail-open).**
At the served boundary (`gateway/session_admit.go`, both the Anthropic `/v1/messages` and the
OpenAI `/v1/chat/completions` wires), when a request is refused for a budget drain AND the
host wired `ResetOnBudget`, the gateway asks the host for a fresh trace + seed, splices the
seed ahead of the live messages (after any system framing), switches to the fresh trace, and
**proceeds**. With the hook nil — the default — the path is byte-identical to today's 409 +
directive. The gateway stays session-internals-blind: the host (`cmd/fak`) owns
`session.Recontinue` and `sessionreset.BuildSeed`.

**4. The host wiring — `fak serve --reset-on-budget`.**
`cmd/fak`'s `resetServedSessionOnBudget` builds the seed and re-arms the continuation; the
flag gates it (and requires `--context-budget-tokens`). Default OFF: the slice ships dark and
flips on after a live soak, because it rewrites in-flight transcript history.

**5. The cooperative MCP reset — `fak_session_reset`.**
For clients that use fak as an MCP server rather than a full model-wire proxy, the tool
accepts `{trace_id, context_tokens, messages}`. It first debits the reported context tokens,
then refuses unless the session is genuinely budget-drained, and finally reuses the same
`ResetOnBudget` host hook to return the fresh continuation trace and `seed_messages` for a
new model window. MCP still cannot observe provider token usage by itself; the client or
wrapper must report it.

**6. The child-process supervisor — `fak guard --restart-on-budget`.**
For wrapped CLIs that need a real OS-process boundary, guard wires a separate
`gateway.Config.OnBudgetExhausted` callback. The callback fires after a served turn's
provider usage drains the context budget, while the transcript is still available. Guard
then recontinues the session, writes a carryover seed JSON file, advances the gateway's
default trace to the continuation id, stops the child, and relaunches it with
`FAK_RESET_TRACE_ID`, `FAK_SESSION_ID`, and `FAK_RESET_SEED_FILE`. This is deliberately
additive to `--reset-on-budget`: transparent reset retries in place; restart reset gives a
wrapper a fresh process and a seed file to inject into the new model window.

## Honesty ledger

- **Reuses, does not reinvent.** The "what to keep" decision is the shipped `ctxmmu`
  durability prior verbatim, witnessed to agree with the admit path on the bite pairs.
- **Live KV reuse is deferred.** The `warm_prefix` contributor stamps `live_kv_reuse:deferred`
  in its audit Meta. v1 carries the recall plan, not a live KV splice.
- **The task distiller is extractive, not generative.** v1 is deterministic (no model call);
  a model-call middle-summary is a tracked follow-on.
- **Default behavior is unchanged.** With `--reset-on-budget` off (the default), the 409 +
  `SessionResetDirective` path is byte-identical to before.
- **A hard restart is a handoff, not magic rehydration.** `fak guard --restart-on-budget`
  relaunches the child and exposes the carryover seed through `FAK_RESET_SEED_FILE`; a
  generic child that ignores that file starts fresh under the continuation trace but does
  not automatically ingest the seed.

## Status & follow-ons (epic #739)

The epic is **#739**. The vertical slice described above — the `Recontinue` re-arm verb,
the `internal/sessionreset` contributor registry, the `gateway.Config.ResetOnBudget`
boundary, the `fak serve --reset-on-budget` flag, the MCP `fak_session_reset` tool, and the
`fak guard --restart-on-budget` supervisor — is **shipped on `main`** (swept into the shared
trunk on 2026-06-25; ships dark/default-off). The named follow-on rungs are filed as child
issues:

- **#740** — live same-model KV-included reuse (wire `vcachechain` to a live serve splice;
  today `warm_prefix` carries the recall *plan*, stamped `live_kv_reuse:deferred`).
- **#741** — a model-call task distiller (summarize the middle, not just extract the ends).
- **#742** — MCP `fak_session_reset` cooperative reset for clients/wrappers that report
  context usage (**shipped**).
- **#743** — an operator webhook + a pre-threshold (e.g. 80%) warning before exhaustion
  (**shipped**): `session.Table.WatchBudget(warnFraction, obs)` fires a once-per-drain
  `BudgetWarn` when the context budget first crosses the consumed share (default 80%), then a
  `BudgetExhausted` carrying the continuation id at zero. `fak serve --budget-webhook URL
  [--budget-warn-fraction 0.8]` POSTs each event to an operator monitor — fail-open, off when
  the URL is empty. Tests: `internal/session.TestWatchBudgetWarnsOnceThenExhausts`,
  `cmd/fak.TestBudgetWebhookObserverPostsEvent`.
- **#744** — a cross-session / fleet-wide budget pool (today each session's budget is independent).

## See also

- [`CONTEXT-IS-NOT-MEMORY.md`](../CONTEXT-IS-NOT-MEMORY.md) — the durability axis the
  `durability_facts` contributor reuses.
- `SESSION-CONTROL-STATE-AS-FIRST-CLASS-2026-06-24.md` — the drive-state table the budget and
  `Recontinue` verb live on.
- `PORTABLE-SESSION-IMAGE-AND-SNAPSHOT-2026-06-24.md` — the freeze/rehydrate machinery a
  future KV-included reset would compose with.
