Context strategies
A long agent run fills its context window with material it will never read again: a 40,000-character command output, twelve tool schemas it never called, thirty turns of a conversation whose conclusion is one sentence. A context strategy decides what the runtime does about that — what gets moved out of the prompt and left retrievable, and what gets withheld until asked for. There are three values:static, hybrid and dynamic. hybrid is the
default, and each one is a superset of the last.
The problem
Context windows are large, but they are not the binding constraint people expect. Three separate pressures push against them, and they want different answers. The first is a single enormous tool result. Onefind over a big tree can spend
a third of a window on output the agent needed two lines of. Truncating loses
the two lines as often as not.
The second is accumulated history. Compaction — summarizing old turns to reclaim
room — is the standard answer, and it is lossy by construction. Once a turn has
been summarized, a question that depends on its detail has nowhere to go.
The third is tool schemas. An agent with eight MCP servers attached carries
every tool definition from all eight on every request, whether or not this task
touches any of them, and schemas are not small.
Solving all three unconditionally is the wrong call, because each solution costs
something. Offloading adds a round trip and a read-back tool the model has to
understand. Lazy tool disclosure adds an activation turn before the first real
tool call, and a model that handles it badly stalls. So the platform makes it a
setting rather than a default behaviour.
How AgentArea approaches it
What each strategy turns on
Note what is not in the table: compaction itself. Compaction runs under all
three strategies. What
static gives up is the preservation of what compaction
discards, not the discarding.
Resolution
The strategy is resolved once, early in the run, before tool discovery — it has to be, because it decides which discovery activity runs. Resolution order is the agent’s override, then the model spec’sdefault_context_strategy, then
"hybrid". The resolved value is stored on workflow state and carried across
continue-as-new so a long run does not change strategy mid-flight.
An unrecognized value does not raise. It resolves to hybrid. See Limits — this
matters more than it sounds.
Output offloading
Underhybrid and dynamic, a tool result longer than 8,000 characters (about
2,000 tokens) is written to object storage under tasks/{workspace_id}/{task_id}
and replaced in the conversation by a summary.
The summary is not a truncation. It carries a header naming the stored id with
the exact character and line counts, then the first 500 characters, then — if
the content is longer than head plus tail — the last 200 characters, and it ends
by telling the model how to get the rest:
grep parameter is the point. The common case is not “read all 41,000
characters back” but “find the three lines that matter”, and offloading turns an
unaffordable read into an affordable search. A read-back is itself capped at
16,000 characters.
Offloading is best-effort in both directions. If the store fails inside the
workflow, the full content stays in the conversation — the agent is never
blocked by a storage outage. The same offload applied inside an activity, where
the payload would otherwise become Temporal history, degrades differently: it
truncates at 8,000 characters and says so explicitly in the text.
History preservation
Underhybrid and dynamic, the messages about to be compacted are written to
object storage as a numbered chunk first, and a counter increments. Compaction
then proceeds as usual.
This makes recall_history meaningfully better. When the model calls it with a
grep or tool_name filter and at least one chunk has been stored, the
workflow searches the preserved chunks in object storage and returns up to 20
matches. Only if that search finds nothing or fails does it fall back to
querying the database event log.
Under static there are no chunks, so recall_history has only the event-log
fallback.
Preservation is also best-effort — a failed store logs a warning and compaction
continues, because refusing to compact would leave the run wedged against its
context limit.
When compaction happens
Usage is tracked against an effective limit of the model’s context window less a 15% reserve for output. A warning fires at 60% of that limit; compaction triggers at 75%. Compaction cannot cut anywhere.find_compaction_boundary searches from the
largest possible split downwards for one that keeps the last 4 messages, does
not orphan a tool result whose originating tool call would be removed, and does
not drop activated skill content. If no such boundary
exists it returns 0 and compaction is skipped with a warning.
Lazy tool disclosure
Onlydynamic changes what tools the model is shown. Instead of discovering and
injecting every tool definition, the workflow discovers tool providers — MCP
servers, code toolsets, agents, built-ins — and injects a catalog block:
activate_tool_source tool is added whose
source_name enum contains exactly the not-yet-activated sources, so the model
cannot request one that does not exist. Activated sources are recorded on
workflow state and replayed across continue-as-new, so a long run does not have
to re-activate.
A separate, finer mechanism exists for OpenAPI connections and is not governed
by the context strategy at all: a tool config with load_mode: "searchable"
keeps its operation schemas out of the prompt behind a load_tools meta-tool
that reveals them by exact name. That is per-tool configuration, not a
strategy-wide switch.
Why not always use dynamic
Dynamic looks strictly better — it is the superset — and it is not the default, for two reasons. It costs a turn. Underdynamic the model cannot call a tool until it has first
called activate_tool_source, which means at minimum one extra LLM round trip
before any work begins, and more when the model activates sources one at a time.
For a task that was always going to use the one MCP server the agent has, that
is pure overhead.
And it depends on a model behaviour that is not uniform. Choosing a source from
a catalog, activating it, and then calling a tool from it is a three-step plan
held across turns. Weaker models activate the wrong source, forget to activate
at all and hallucinate a tool call, or activate everything immediately — which
reproduces static while having paid for the extra turns. The eager path has no
such failure mode: the tool is either in the list or it is not.
hybrid is the default because its two features have no behavioural cost. The
model is not asked to do anything differently; large outputs arrive summarized
with a documented way to read more. The one thing it does ask is that
the model understands read_tool_output, and that is a normal tool call rather
than a multi-turn plan.
The cost of making this a setting at all is that the same agent behaves
differently on two models, and a bug that only appears under dynamic will not
reproduce on a model whose spec leaves the default unset.
Limits
- The per-agent override is not wired. Resolution reads
agent_config["context_strategy"]first, but the agent config the activity builds has no such field —AgentConfigResultcarries onlydefault_context_strategy. There is nocontext_strategycolumn on the agent and no API field for it. The agent-level override is always absent, so the model spec is the only thing that selects a strategy. - There is no auto-inference from the model name. The model-spec DTO
comments
default_context_strategyas “Auto-inferred from model_name if None”, and no inference exists. A null value resolves tohybrid. - An invalid value fails silently to
hybrid. Resolution catches theValueErrorand returnshybridwith no log line, and the enum values are lowercase — so"STATIC"or"Dynamic"does not select what it looks like it selects, it selectshybrid. - Offloading is measured in characters, not tokens. The 8,000-character threshold is documented as roughly 2,000 tokens, which holds for English prose and not for dense JSON, minified code, or non-Latin scripts.
- The preview may not contain the useful part. 500 characters of head and
200 of tail is a small window on a 40,000-character output. The model has to
decide from that whether a
read_tool_outputcall is warranted. - Activating an unknown tool source is silent.
ToolCatalog.activatereturns an empty list for a name it does not know rather than an error, deliberately, on the assumption the model hallucinated it. - Offload and preservation failures are invisible to the agent. Both log a
warning and continue. Under
hybrid, a storage outage means history is compacted without being preserved, andrecall_historysilently has less to find. staticstill compacts. Choosingstaticdoes not preserve the full conversation; it only means nothing is written to object storage before compaction discards it.
Related
- Skills — activated skill content is exempt from compaction, which interacts directly with the boundary search.
- What is an agent — where model selection lives, and therefore where the strategy is chosen today.
- Durable execution — continue-as-new, which the resolved strategy and activated sources are carried across.
- Artifacts — the other thing a task writes to object storage, on a different path and with different durability rules.