# Context and recall

> The window budget, eviction, the stub it leaves, /evict, the recall forms and checkpoints.

Source: https://nodecode.ai/docs/context
Updated: 2026-10-06

## Budget

The budget is the model's context window in tokens. The first source that has a number wins.

| Order | Source |
| --- | --- |
| 1 | `NODECODE_CONTEXT_TOKENS` |
| 2 | `providers.<id>.models[].context_window` in `config.jsonc` |
| 3 | Overrides for the model recorded in the running image |
| 4 | The models.dev cache at `~/.cache/nodecode/models.dev/api.json` |

A model no source knows has an unknown window.

> **Unknown window.** An unknown window is never proactively evicted. Eviction then starts only when the provider rejects a request as too large. Set `context_window` to give the model a budget.

`/context` reports the window and its usage.

## Eviction

| Setting | Value |
| --- | --- |
| Starts at | 0.85 of the budget |
| Evicts down to | 0.50 of the budget |

Order:

1. Finished turns go first, oldest first. Each goes whole.
2. If that is not enough, the running turn sheds its oldest rounds.

The running turn keeps its user message. It keeps its newest round. It is never evicted whole.

Evicted history is never summarised. It stays in the log, word for word.

The eviction boundary only moves forward. An undo does not bring evicted turns back into the window.

## Stub

A stub is a system message that stands where the evicted history was.

Whole turns:

```lisp
[history evicted: seq A–B · N turns · defined: NAMES · watching: VALUES · recall: (recall-log :match "text" :before B+1)]
```

Rounds of the running turn:

```lisp
[turn history evicted: N rounds · seq A–B · defined: NAMES · watching: VALUES · recall: (recall-log :match "text" :after A-1 :before B+1)]
```

A notes block may follow on the next lines:

```lisp
[the evicted cells noted and judged, oldest first; (recall-entry seq) reads one whole
 seq N · NOTE
 seq N · NOTE]
```

| Part | Meaning |
| --- | --- |
| `seq A–B` | The log positions that left the window. `recall-log` and `recall-entry` take the same numbers. |
| `defined:` | Definitions the evicted rounds recorded. Shown only when there are any. |
| `watching:` | The last values of watched cells. Shown only when there are any. |
| Notes block | One line per evicted cell that opened with a `;;` note. |

| Limit | Value |
| --- | --- |
| Names in `defined:` or `watching:` | 24, then `+N more` |
| Note lines | The newest 100, then `+N earlier` |
| Note line length | 320 characters |

Write a `;;` line at the top of a cell. It is what survives eviction.

## /evict

`/evict [percent]` forces an eviction.

| Fact | Value |
| --- | --- |
| Alias | `/compact` |
| `percent` | 1 to 100. `40` and `40%` both work. |
| Default | 50 |
| Drops | The oldest whole turns, up to that share of the retained history. |
| Keeps | The newest turn, always. |

## Recall

The agent reads evicted history back from the log with three forms.

```lisp
(recall-log :match "text" :before 1200 :limit 10)
(recall-entry 1184)
(recall-search "deploy script")
```

### recall-log

Scans one session's log. Returns clipped rows, oldest first.

| Keyword | Default | Does |
| --- | --- | --- |
| `:match` | none | SQL LIKE over the raw event payload. `%` and `_` are wildcards. ASCII case is ignored. |
| `:kinds` | input, assistant answer, tool call, tool result | One event kind or a list of kinds. |
| `:before` | none | Only rows with a `:seq` below this number. |
| `:after` | none | Only rows with a `:seq` above this number. |
| `:limit` | 20 | Maximum rows. |
| `:clip` | 200 | Characters kept per body. |
| `:session-id` | The current session | Another session to read. |

Each row carries `:seq`, `:at` (UTC time) and `:kind`. A tool result also carries `:duration-ms`.

When more rows match than `:limit`, the newest rows are returned. The first element is then `(:matched N :showing M)`. Page older with `:before` set to the oldest `:seq` returned.

The scan includes the sessions this one was forked from. It includes turns hidden by an undo.

### recall-entry

`(recall-entry seq)` returns the one event at `seq` with its whole body. It returns `NIL` when no event holds that number.

### recall-search

`(recall-search query)` searches every session.

| Keyword | Default | Does |
| --- | --- | --- |
| `:kinds` | Same as `recall-log` | Event kinds to search. |
| `:limit` | 60 | Maximum rows. |
| `:window` | 220 | Characters of text around the match. |
| `:lead` | 32 | Characters of that text before the match. |

The query is split on spaces. Every word must appear. Words are literal: `%` and `_` match themselves. An empty query is an error.

Rows come newest first. Each carries `:session-id`, `:title`, `:seq` and a `:text` snippet.

## Checkpoints

Every turn is a checkpoint on the session tree.

| Command | Does |
| --- | --- |
| `/tree` | Opens the session tree. Continue from a turn, or rewind to it. Alias `/checkpoints`. |
| `/undo` | Rewinds the last turn. |
| `/redo` | Returns to the branch the last rewind left. |
| `/fork` | Forks a new session at the current head. |

A rewind adds a head move to the log. Nothing is deleted. Abandoned branches stay reachable.

In the shell, `/undo` also restores files by default. See [Sessions](https://nodecode.ai/docs/sessions).
