Context Management

Long agent runs pile up tool results, model replies and subagent messages. Left alone, that history can overflow the model's context window and cause errors or truncation. B4.run keeps it in check two ways:

  • Tool-output offloading is on by default.
  • Summarization is off until you turn it on.

Tool-output offloadingCopy link to section: Tool-output offloading

When a tool returns a large result, B4.run writes the full output to workspace/tool-outputs/ and puts a short stub in the context. The stub has a few preview lines (you choose how many) and a file path. When the agent needs the full content, it calls readFile with that path, and the content goes into the next model turn.

Offloading turns on as soon as a workspace/ directory exists at the app root. There's nothing else to configure.

What the model seesCopy link to section: What the model sees

The stub looks like this:

text
[Tool output offloaded — 12,345 chars exceeded the 1,500-char limit.
Full output saved to: tool-outputs/abc123.txt
Preview (first 10 lines):
line 1 …
line 2 …
…
Read the full output with the readFile tool at the path above.]

The model can work from the preview or call readFile for the rest. The full content stays on disk, so it's there on later turns without using context tokens.

ConfigurationCopy link to section: Configuration

Here are the toolOutput options with their defaults:

b4.config.ts
export default {
  toolOutput: {
    offloadThresholdChars: 40000, // default
    previewLines: 10,             // default
    maxBytes: 268435456,          // default (256 MB)
    ttlMs: 10800000,              // default (3 h)
    gcThrottleMs: 10000,          // default (10 s)
    noOffloadTools: [],           // merged with built-in exempt set
  },
}
KeyTypeDefaultDescription
offloadThresholdCharsnumber40000Offload results longer than this many characters (serialized).
previewLinesnumber10Lines kept in the stub.
maxBytesnumber268435456Total bytes kept in workspace/tool-outputs/. Past that, the oldest files go first.
ttlMsnumber10800000Delete offloaded files older than this (3 hours).
gcThrottleMsnumber10000Minimum time between cleanup passes (10 seconds).
noOffloadToolsstring[][]More tools to never offload, on top of readFile and listDir.

Offloading needs a filesystem, so the build turns toolOutput off for the hono edge target. A runtime with no filesystem fails each request with B4_E1005 rather than silently ignoring the settings. Summarization needs no filesystem and runs on every target.

ExemptionsCopy link to section: Exemptions

readFile and listDir are never offloaded, so the agent can read an offloaded file back without it getting offloaded again. Use noOffloadTools to exempt more tools, such as one that already returns a short summary.

Garbage collectionCopy link to section: Garbage collection

At most once per gcThrottleMs, B4.run runs a cleanup pass in the background. It deletes files older than ttlMs. If the total is still over maxBytes, it deletes the oldest files until it fits. Agent turns never wait on it.

Conversation summarizationCopy link to section: Conversation summarization

Once a thread's token count passes a threshold, summarization shrinks the older messages. The most recent turns stay word for word. Everything older is folded into a running summary, which goes at the start of the thread on the next model call.

Let's enable it in b4.config.ts:

b4.config.ts
export default {
  summarization: {
    enabled: true,
  },
}

ConfigurationCopy link to section: Configuration

KeyTypeDefaultDescription
enabledbooleanfalseTurns summarization on.
maxTokensnumber12000Summarize older history past this many tokens.
keepRecentTurnsnumber6Recent turns (each starting at a HumanMessage) that are never summarized.
modelstringRoute's modelModel that writes the summary.
tokenCounter(text: string) => number | Promise<number>gpt-tokenizer (o200k_base), loaded on demandYour own token counter.
summarize(args) => Promise<string>One model callYour own summarizer. Receives messages, model, previousSummary, signal, and maxRetries (the route's retry.maxAttempts minus one).

Use tokenCounter to plug in a different tokenizer. summarize replaces the whole summary step, for example to use a cheaper model, compress in a way that suits your domain, or call a different provider.

How they composeCopy link to section: How they compose

Offloading and summarization handle different kinds of growth:

  • Tool-output offloading works on one tool result at a time. It keeps a single large result from filling the context.
  • Summarization works on the whole thread. It shrinks history that built up over many turns.

You can use both at once. Here's a typical setup for a long-running research agent:

b4.config.ts
export default {
  toolOutput: {
    offloadThresholdChars: 1500, // tight threshold for a research agent
    previewLines: 10,
  },
  summarization: {
    enabled: true,
    maxTokens: 12000,
    keepRecentTurns: 6,
  },
}