- Documentation
- Explanation
- Context and compaction
Explanation
On this page
Context and compaction¶
A model reads a bounded window of tokens, and a working chat outgrows it. Nawa's answer is to measure the window exactly and to maintain the conversation inside it openly, rather than to let a provider cut it silently. This page explains the accounting, the maintenance, and the rules that came from watching them fail.
Tokens, never characters¶
Model context budgets are in tokens; document extraction controls explicitly labelled in characters are separate. Before each request the engine materialises the request the provider will receive — messages, tools, native reasoning state, images — and counts it: with the model's own tokenizer where one is bundled, with a provider's same-origin counting endpoint where it declares one, and with a conservative local estimate otherwise. An estimate is marked as one (the gauge shows ~), reserves headroom for what it cannot measure, and cannot certify that a request fits; a null or negative count is not zero.
The window itself is measured, not set: nobody can change it. The capability probe at setup time reads what the provider declares (its own model record, the model list, the size a local server loaded, or the limit a strict server states when probed), and Settings shows that window beside the model with its source. A server that reports nothing is assumed to have 256,000 tokens until it refuses a longer request and states its limit; that limit becomes the model's window on that server, and the refused request is compacted and sent again. A provider's report of how many prompt tokens it actually processed corrects the record in both directions — a silent truncation clamps the window down, a successfully processed larger prompt raises a false clamp back up.
One maintainer, two thresholds¶
One function, fitRequestContext, owns context maintenance for the lead, the coder and every other agent. It compares the measured request with that model's budget and, when the request crosses the trigger, rebuilds a smaller candidate and accepts it only after counting it again in full. In Settings → LLM, Main and Coder each show Compact at (%) beside their model. Each role remembers its own percentage for each server/model pair, even when both use the same connection; blank uses the default displayed in the field, initially 80%. Existing customized defaults are preserved. There is no separate Advanced compaction control. A delegated agent uses the percentage for its configured model profile. Main and Coder maintain separate histories, so one can compact without the other. A running turn retains its settings snapshot. A known output requirement can lower the effective trigger to leave enough response space.
The destination (normally 50%, lowered to stay at least five percentage points below a lower trigger) is where a rebuilt conversation lands. It is not a second trigger, so a valid cached checkpoint above it is not rebuilt prematurely.
Compaction summarises the older part of the conversation into a checkpoint in batches sized by tokens against the summariser's own budget, keeps the retained recent rounds verbatim, keeps every unresolved observation — a rejected finish, a failed test — verbatim wherever it sits, and keeps the original objective before the current work so nothing reads as a restart. Deduplication uses exact text; case-folding and whitespace normalisation merged distinct facts once, and the checkpoint schema was bumped to invalidate the results. An accepted reduction becomes the next active history, not a temporary view.
The coder's checkpoint may use up to half of the rebuilt request, so the current text of the files it was working on comes back beside its facts, newest first; the most recent rounds fill the rest. With a checkpoint the size of a summary, those files were the first thing dropped, and the coder read them all again.
Each compaction is an event in the transcript — one start, one end, with the counts before and after — and the composer's gauge shows the same effective threshold the fitter used.
Provider state and displayed answers¶
The provider's response and the answer shown in the chat are related but distinct. Citation formatting and application previews can change the displayed text. Nawa stores the accepted answer's identity separately from the provider's original reasoning and signed blocks, so those presentation changes do not discard continuation state. Explicit completion calls retain their matching result before replay. An edited answer, changed source list, different account or incompatible model connection cannot acquire that saved state.
Recent coder rounds retained through a checkpoint keep their original provider state too. The request budget includes that state and any completion results; it is not counted as empty merely because it is private and absent from the visible transcript. Opening narration and user corrections remain separate messages in their original order.
Evidence stays whole¶
A tool result too large for the request is not trimmed. It is stored complete in the workspace's evidence with its path, hash, byte size, source and completeness, and returned as a first page the model can read on; the coder's checkpoints refer to the same evidence rather than carrying it. The older steps a compaction takes out of a request are kept the same way: in the conversation database, or, when the database cannot hold them, as a complete file in the project's evidence folder, and the request names where to read them. Token pressure is never a reason to delete a fact.
The same aggregate budget applies when a specialist inherits the caller's operation receipts. If the list is too large, the complete list is archived and the handoff carries a bounded view and its reader. A failed or inaccessible archive never authorizes silently dropping the receipts.
Memory is not compaction¶
Durable memory — what Nawa should know about you across chats — is a separate store on the Memory tab: the model saves a note during the turn, quoting your own words, and you can undo it under the answer or edit and delete it later. Compaction never writes there, and a compacted checkpoint is never read as memory.
Why¶
The rules follow incidents recorded in the notes: a trace that looped for thirty-one minutes because an endpoint truncated the middle of a prompt without saying so; two coder compactions seventy-seven seconds apart because a checkpoint was allowed to grow to the trigger; a gauge that changed without a compaction because different measurements disagreed. Each fix moved a decision to the one owner that could make it with measured numbers.
Evidence: docs/compaction-unification-2026-09-14.md, docs/context-accounting-2026-09-14.md, docs/coder-checkpoint-headroom-2026-09-14.md, docs/context-growth-2026-09-14.md.