Context assembly¶
Code: ContextAssemblerPlugin.assemble_messages · kernel dispatcher
mas.runtime.boundary.context.assemble · Schema: agent.schema.yaml
(spec.assembler, spec.context_manager) · Bindings: plugin-bindings.md
For request composition, pinning, resolved model limits, context snapshots, cache metrics, and cost accounting, see the full context-management reference.
Each LLM call builds messages[] in a fixed order (spec.assembler, default
assembler):
- System band (injected context, memory seeds,
collect_contextparts) - Committed history — after
ContextManagerContract.manage_history - Current user turn (
last_user_text) - In-turn working memory (assistant/tool rows from the dispatch loop)
context_manager plugins slice committed history by user turn. After
working memory is appended, assembly runs one pairing pass (one result per
tool call). See Tool-call pairing.
Compaction is not a mutation of in-turn working memory. manage_history
bounds the LLM view; after each turn commit the same cap is written back to
the stored log. The summarising plugin caches the last summary
(hysteresis) so the summarizer LLM is not called on every in-turn step.
assembler (required default)¶
Builds messages[]. Omit the field, write assembler: assembler, or write
the object — same behaviour.
# default (all three equivalent)
# (omit)
# assembler: assembler
spec:
assembler:
type: assembler # alias: context-assembler → mas.ctx.assembler
params:
emit_segments: true # default
always_reassemble: false # default
# token_budget: omit = no part-eviction budget
There is one shipped assembler (ContextAssemblerPlugin). History policy is
not assembler params; it is spec.context_manager.
context_manager plugins¶
Each manager is a plugin with strategy code (not an empty shell). The
summarising manager additionally composes a summarizer sub-plugin
(registry type summarizer: llm | drop).
System context parts are rebuilt for each LLM request. Skills and other
ContextParts marked pinned stay outside conversation summarization; tool
schemas are sent separately and also count toward the request budget. See the
Context Management reference for the
complete ordering and pinning rules.
Instance is cached per (ctx, manifest) so hysteresis can reuse a summary.
summarising (package default)¶
Omit context_manager, write context_manager: summarising, or:
spec:
context_manager:
type: summarising
params:
keep_turns: 10
hysteresis_ratio: 0.1
summarizer: llm # agent's model; drop = discard older turns
working_memory_messages: 20
trimmer:
reserve_tokens: 2000
safety_margin_ratio: 0.1
The plugin keeps the last keep_turns user turns verbatim, then asks the
summarizer to compress older turns. summarizer: llm uses this agent's
model; without a live engine it behaves like drop. Hysteresis (0.2) is the
cache: a new summary is not computed until the managed payload exceeds
budget × 1.2.
sliding-window¶
spec:
context_manager: sliding-window # ≡ {type: sliding-window, params: {keep_turns: 10}}
Explicit:
spec:
context_manager:
type: sliding-window # alias: sliding_window
params:
keep_turns: 10 # aliases: window_size, max_turns
Older turns are dropped (no summarizer).
Summarizer sub-plugins¶
Not a top-level spec key. Bound only on type: summarising:
| Plugin | Omit / shorthand | Equivalent | Behaviour |
|---|---|---|---|
llm (default) |
summarizer: llm |
{type: llm} |
Summarizer slot else agent's live engine; degrades to drop without one |
drop |
summarizer: drop |
{type: drop} |
Discard older turns |
Optional cheaper summary model (params.model is a spec.models[].id or LiteLLM string):
params:
summarizer:
type: llm
params:
model: gpt-4o-mini
The context manager owns recency (keep_turns) and the hysteresis cache.
The summarizer only returns summary text (or nothing, which means drop).
The live engine is bound when the context manager is instantiated from
ctx.engine, not stored in the spec.
stack (alias full-history)¶
spec:
context_manager: stack
spec:
context_manager:
type: stack # or full-history
params:
max_messages: 200 # omit = no recency cap; pairing still repaired
Assembly token trim (model context window)¶
The automatic cap is derived from the effective model after infra mappings.
It counts fixed system/context, current input, working memory, and tool schemas,
reserves completion tokens, then leaves a safety margin. Override with
spec.models[].context_window, infra generation.context_window, or
spec.context_manager.params.trimmer:
spec:
models:
- model: gpt-4o
max_tokens: 2000 # completion reserve
# context_window omitted: auto-resolve from the effective model
context_manager:
type: summarising # default; sliding-window | stack also valid
params:
keep_turns: 10 # recent user turns never summarized (override per agent)
hysteresis_ratio: 0.1 # summary reuse stays within the hard safety budget
working_memory_messages: 20
trimmer:
reserve_tokens: 2000 # defaults to the effective completion budget
safety_margin_ratio: 0.1
| Field | Meaning |
|---|---|
trimmer.max_tokens |
Optional explicit input ceiling. Omit to resolve the effective model window. |
trimmer.token_budget |
Alias for max_tokens |
trimmer.reserve_tokens |
Completion reserve. Defaults to the effective output budget. |
trimmer.safety_margin_ratio |
Fraction held back for estimate error and provider overhead (default 0.1). |
keep_turns |
Last N committed user turns kept verbatim (default 10). Older turns are summarized (or dropped). |
hysteresis_ratio |
Reuse the summary until near the safety-bounded hard budget (default 0.1). |
In-turn working memory is passed as pin_tail: oldest tool-call groups in
history are dropped first; WM can be trimmed by group only after history is
exhausted. That is what keeps the live tool round intact for the provider.
The trim implementation lives in mas-library-standard
(trim_messages_to_budget). Pairing repair, history slicing, working-memory
caps, and compile defaults live under lib/context/. The assembler plugin
uses those helpers; mas-ctl compile uses the same defaults. The kernel does
not. The kernel raises ProviderPayloadError if pairing is still broken after
the plugin returns (survives python -O). Custom spec.assembler plugins must
produce a paired payload; they can call sanitize_provider_messages.
After each turn commit the kernel asks the assembler plugin to apply the same
recency policy to committed_messages and conversation chunks. Folded prefix
data is dropped, so snapshots stay bounded (keep_turns / max_turns /
max_messages). Hysteresis still avoids re-summarizing every in-turn LLM call.
A context_manager value that is neither a plugin name nor a {type, ref,
params} object is a hard error at mas-ctl compile/validate time. At
assemble time the kernel is lenient instead: a malformed value that reached
the runtime anyway (bypassing mas-ctl) is treated as omission and the
default context manager is used, so one bad field cannot fail every turn.
See plugin-bindings.md.
Working memory count cap¶
working_memory_messages (default 20) limits how many WM rows are considered
for assembly (tool groups are atomic). This is independent of trimmer.
Tool-call pairing¶
Canonical rules for plugin authors: ContextManagerContract (two layers: protocol vs tokens; ask-then-results).
The HTTP API is stateless. If the model asked for tools A and B, the next
request we build must contain that assistant message and then those two
results. Assembly runs one pairing pass after manage_history and working
memory (one result per tool call). The kernel then raises
ProviderPayloadError if the payload is still unpaired. The server checks
ids and counts, not the text inside a tool result.
Related¶
- Compiled agent defaults —
assembler+context_manager+summarizer, fully expanded - plugin-bindings.md — string shorthand vs
{type, params}vs list slots - summarization.md — thresholds, which model, MCE judge, logs
- agent.md —
working_memory(live round + persistent buffer; compaction is sugar) - ContextManagerContract
- working-memory-compaction.md
- plugins-reference.md