Plane docs
Guides

Context and prompt caching

What you can set, and the one thing that quietly costs money.

The prompt is rebuilt from the log at every step. Two features keep that from getting expensive, and both are mostly automatic.

Compaction

When the transcript approaches the model's context window, Plane compacts it. Two tiers, both durable: tier 1 replaces old tool results with a one-line marker and costs no model call; tier 2 summarises, and only runs if tier 1 was not enough.

compaction: {
  // Every field optional. `{}` is a complete policy.
  triggerAtFraction: 0.8,
  keepRecentMessages: 10,
  contextTokens: 200_000,             // override, if Plane's catalogue does not know your model
  model: "anthropic/claude-haiku-4.6" // a cheaper summariser
}

Both tiers append a compaction event, so a compaction is part of the log rather than a thing that happens inside one step and is undone by the next.

Three details worth knowing:

  • The trigger reads the provider's own token count from the newest model.request.completed, not an estimate, adding an estimate of only what arrived since. The event records which, as measuredBy. Only a session's first step guesses.
  • keepRecentMessages snaps its boundary down to a user message. A cut between a tool call and its result would strand the result on the wrong side of the summary, and a tool message with no call is a 400 on every provider.
  • A cheaper summariser does not inherit the session's reasoning level. Maximum effort on a three-hundred-word summary is the opposite of the saving you asked for.

A summariser that fails writes a session.error and the turn continues on the full transcript. A cost optimisation must not become an outage.

Prompt caching

Every provider caches; making it work is about keeping the prefix byte-stable. Plane emits, in this order and always:

tools → system[0] stable → system[1] volatile → system[2] compaction summary → messages

and places its cache breakpoints across that. Two consequences you can act on.

A session override's appended text goes in system[1], below the breakpoint that covers the tool schemas. So a two-line per-session note does not invalidate ten thousand tokens of tool definitions on every step. That is why overrides.system: "append" exists as its own mode rather than as string concatenation.

Tool order is frozen per session, in the log. The first turn writes a cache.prefix event with a fingerprint and an order, and later turns re-pin to it. A newly appeared tool goes on the end, where it invalidates only itself.

A second cache.prefix event in one session is a cache bust with a name — which is exactly what makes a cost spike diagnosable after the fact.

What you see

model.request.completed.usage carries { uncached, total, cacheRead, cacheWrite }, and total includes the cached components. Both cache counters are optional: absent means the provider reports nothing, zero means it reported a miss. Render the ratio only when they are present rather than showing 0% cached for a host that publishes no figures.

Those numbers also reach usage.* and the control plane's trace tab.

On this page