# Long session context (/docs/coding-agent/long-session)



<Callout type="info">
  **Status: strategy factories shipped; runtime manager roadmap.** Both
  strategy factories — `createLruContextStrategy` and
  `createImportanceContextStrategy` from `@pleach/coding-agent/context` —
  ship today, alongside the locked `ContextStrategy` contract on
  `CodingAgentRuntimeConfig` (`packages/coding-agent/src/types.ts`). What
  is NOT shipped yet is the runtime-side `CodingContextManager` that
  tracks entries and calls `strategy.evict()` after each tool turn —
  that remains conditional on an open scoping decision (whether the
  budget consumer lives in `@pleach/coding-agent` or in
  `@pleach/core/runtime`). Consumers who bring their own context manager
  can inject a shipped strategy today.
</Callout>

Long coding sessions exhaust the model's context window long before
they exhaust their workspace. A 200K-token context window holds \~50
typical source files; a non-trivial coding task touches more than
that. The model needs a policy for *which files to evict* from its
working set when the budget is tight — not at the runtime layer
(which would defeat replay determinism) but at a layer the runtime
delegates to.

`ContextStrategy` is that contract. `CodingContextManager` is the
runtime-side consumer that calls the strategy with the current
state and a budget.

## The contract [#the-contract]

```ts
import type {
  ContextStrategy,
  ContextEntry,
  ContextBudget,
} from "@pleach/coding-agent";

export interface ContextStrategy {
  evict(
    state: readonly ContextEntry[],
    budget: ContextBudget,
  ): readonly string[];
}

export interface ContextEntry {
  readonly id: string;            // stable id, typically `${path}:${revisionHash}`
  readonly path: string;          // file path or other resource identifier
  readonly lastAccessedAt: number; // epoch ms — LRU-load-bearing
  readonly sizeBytes: number;
  readonly accessCount?: number;  // optional frequency — importance scoring; absent → recency×size proxy
}

export interface ContextBudget {
  readonly maxBytes: number;
  readonly maxEntries?: number;   // strategies MAY ignore
}
```

The strategy returns the array of entry ids to evict, in eviction
order. Strategies MUST be pure: same `state` + `budget` returns the
same eviction list. Side-effects belong outside the strategy.

This is the same shape pattern as the `EvictionPolicy` contract in
[`@pleach/core/cache`](/docs/cache) — `(state, budget) → string[]` —
which keeps the strategy substitutable across the runtime and the
cache layers.

## Strategies [#strategies]

Two pure `ContextStrategy` factories ship today from
`@pleach/coding-agent/context`.

### `lru` — recency-only [#lru--recency-only]

```ts
import { createLruContextStrategy } from "@pleach/coding-agent/context";

const strategy = createLruContextStrategy({ maxEntries: 100 });
```

Sorts `state` by `lastAccessedAt` ascending; evicts the oldest entries
until the remaining set's total `sizeBytes` fits under `budget.maxBytes`.

The default strategy. Coding agents that work file-by-file fit this
shape well — a file the agent hasn't touched in N tool calls is
probably not needed for the next N tool calls either.

### `importance` — weighted by access frequency × recency [#importance--weighted-by-access-frequency--recency]

```ts
import { createImportanceContextStrategy } from "@pleach/coding-agent/context";

const strategy = createImportanceContextStrategy({ maxEntries: 100 });
```

Scores each entry as `frequency × recency ÷ size`, where `frequency` is
the optional `ContextEntry.accessCount`, `recency` is `lastAccessedAt`
normalized across the current `state`, and `size` is `sizeBytes`.
Lowest-importance entries evict first. When `accessCount` is absent the
score degrades to a recency × size proxy, so the strategy stays usable
against the un-augmented `ContextEntry` shape.

Pairs with project-shaped workflows where the agent re-visits a few
"hub" files (a router, a config, a schema) repeatedly across the
session. Pure recency would evict them between visits; importance
keeps them warm.

## Wiring (planned) [#wiring-planned]

```ts
import { createCodingAgentRuntime } from "@pleach/coding-agent/runtime";
import { createLruContextStrategy } from "@pleach/coding-agent/context";

const runtime = createCodingAgentRuntime({
  sandboxProvider,
  contextStrategy: createLruContextStrategy({ maxEntries: 100 }),
  // ...rest of config
});
```

Omit `contextStrategy` and the runtime ships `createLruContextStrategy({ maxEntries: 100 })`
by default (the literal "v1.0 default" in the
[`CodingAgentRuntimeConfig` contract](/docs/coding-agent/multi-synthesize)).

## Honest scope-limit [#honest-scope-limit]

What IS shipped today: the `ContextStrategy`, `ContextEntry`, and
`ContextBudget` types AND both strategy factories
(`createLruContextStrategy`, `createImportanceContextStrategy` from
`@pleach/coding-agent/context`) — the contract is locked, the field is
on `CodingAgentRuntimeConfig`, and the factories are pure functions
over the `(state, budget) → string[]` contract.

What is NOT shipped today: the runtime-side `CodingContextManager`
that tracks entries and calls `strategy.evict(state, budget)` after
each tool turn. Until it lands, the factories are injectable into a
context manager you bring yourself; the runtime only smoke-invokes
`evict` once at boot. Consumers can author against the full contract
today and expect it to work without breaking changes when the runtime
ships the manager.

## Why this is conditional [#why-this-is-conditional]

The open question is layer placement. Two candidate homes:

1. **`@pleach/coding-agent/context`** — keeps the strategy with the
   surface that needs it. Adds a `ContextStrategy` field to the
   coding-agent runtime config and nowhere else.
2. **`@pleach/core/runtime` budget primitive** — promotes the
   strategy contract one layer down, so observability / agents that
   are NOT coding-shaped (chat with a large file-attach surface,
   research-agent with a fanout corpus) can share the same eviction
   primitive.

The (2) shape is more general but adds a contract surface to
`@pleach/core` that hosts who don't want budget management still pay
type-check cost for. The (1) shape ships the value where the demand
is.

Resolution is tracked in an open scoping decision.

## What strategies CAN'T do [#what-strategies-cant-do]

The strategy is a pure function of `(state, budget) → string[]`. It
cannot:

* **Re-fetch evicted content.** When the agent asks for a file it
  previously evicted, the runtime re-reads from the sandbox via
  `read_file`. The strategy doesn't cache; the workspace IS the cache.
* **Mutate the entry shape.** The strategy returns ids to evict —
  not modifications to the remaining entries.
* **Block on async work.** Strategies are synchronous. They run on
  the post-tool-turn boundary; making them async would mean blocking
  the next LLM turn behind their resolution.

These constraints are load-bearing for replay determinism — a strategy
that re-fetched on the side would make the recorded event log not
re-derivable from the recorded inputs.

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="Multi-synthesize per turn" href="/docs/coding-agent/multi-synthesize" description="The per-turn synthesize cap that pairs with the context budget." />

  <Card title="Cache" href="/docs/cache" description="The EvictionPolicy contract this strategy shape mirrors." />

  <Card title="File tools" href="/docs/coding-agent/file-tools" description="The tools whose outputs land in the context budget." />

  <Card title="Determinism" href="/docs/determinism" description="Why strategies must be pure." />
</Cards>
