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, asmeasuredBy. Only a session's first step guesses. keepRecentMessagessnaps 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 → messagesand 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.