Agent Context & Message Compilers
Introduction
Every agent maintains a rich context that accumulates messages, metadata, system prompts, and response format preferences throughout its lifetime. Before each LLM call, a message compiler decides exactly which messages from this context should be sent to the model. This separation of storage from presentation is a deliberate architectural choice. TheAgentContext acts as the single source of truth for all conversation data, while the compiler acts as a lens — selecting, filtering, and arranging messages for each individual inference call. You can swap compilers without touching the underlying data, and you can modify the data without worrying about how it will be presented.
Key Insight: Think of AgentContext as a database and the compiler as a query. The database stores everything; the query decides what the model actually sees.
AgentContext
AgentContext is the immutable container at the heart of agent state. It is declared as final readonly, ensuring that every modification produces a new instance rather than mutating existing data. This immutability guarantee makes agent state safe to pass through hook pipelines and across execution boundaries without risk of unintended side effects.
The context holds four distinct concerns:
| Concern | Type | Description |
|---|---|---|
| MessageStore | MessageStore | A sectioned store of all conversation messages, organized by named sections (e.g., messages, buffer, summary) |
| Metadata | Metadata | Arbitrary key-value data carried across the execution — session IDs, user preferences, feature flags, or any application-specific state |
| System Prompt | string | The system-level instruction sent to the model that defines its behavior and persona |
| ResponseFormat | ResponseFormat | Optional structured output format constraints (JSON schema, etc.) that guide the model’s response structure |
AgentState rather than constructing AgentContext directly:
Constructing AgentContext Directly
While most use cases are handled throughAgentState, you can construct an AgentContext directly when you need fine-grained control. The constructor accepts flexible types for convenience:
Mutating Context
SinceAgentContext is immutable, all “mutations” return a new instance. The with() method provides a convenient way to change multiple properties at once, while dedicated methods handle specific updates:
Context Sections
TheMessageStore inside AgentContext is divided into named sections defined by the ContextSections class. Each section holds a distinct category of messages, allowing the system to organize conversation data by purpose:
| Section | Constant | Purpose |
|---|---|---|
messages | ContextSections::DEFAULT | Primary conversation history — user messages, assistant responses, and tool results |
buffer | ContextSections::BUFFER | Temporary working messages such as intermediate reasoning steps or ephemeral context |
summary | ContextSections::SUMMARY | Condensed summaries of older conversation history, typically produced by summarization capabilities |
Extensibility: While the framework defines three built-in sections, the MessageStore supports arbitrary section names. You can create custom sections for domain-specific needs, though you will need a custom compiler to include them in inference.
Message Compilers
Before each model call, the driver asks aCanCompileMessages implementation to select and arrange the messages the model should receive. The compiler reads from AgentState and returns a flat Messages collection:
Built-in Compilers
The framework ships with three compilers, each suited to different scenarios. Understanding when to use each one is key to building agents that manage context effectively.ConversationWithCurrentToolTrace (Default)
The default compiler provides intelligent trace filtering for multi-step agent executions. It includes all non-trace conversation messages plus only the trace messages from the current execution. This prevents the model from seeing internal tool-calling traces from previous executions, keeping the context clean and focused:is_trace— a boolean indicating whether the message is an internal trace (e.g., tool call/response pairs within a sub-execution) or a conversation message visible to the userexecution_id— a UUID identifying which execution produced the message
execution_id matches the current execution. When there is no active execution (between executions), all traces are excluded:
AllSections
The simplest compiler — it sends every message from every section, with no filtering whatsoever. This is useful for debugging, testing, or when you want the model to see the complete, unedited history:
Warning: In production agents with long-running conversations, AllSections can quickly exceed the model’s context window. Consider using it primarily for development and debugging.
SelectedSections
Sends messages from specific sections in a defined order. This compiler is essential when you have a summarization strategy and want to send the summary followed by only recent messages, or when you want to exclude certain sections entirely:SelectedSections compiler can send the summary section followed by only recent conversation messages, keeping the context compact.
Installing a Custom Compiler
Via AgentBuilder (Recommended)
TheUseContextCompiler capability provides a clean, declarative way to replace the default compiler during agent construction:
Via Driver (Manual)
When working directly with the loop and driver, pass the compiler at construction time. Any driver implementing theCanAcceptMessageCompiler interface supports this:
CanAcceptMessageCompiler interface requires two methods:
Writing a Custom Compiler
Implement theCanCompileMessages interface to build your own message selection strategy. The compile method receives the full AgentState, giving you access to the message store, metadata, execution state, and all other agent data:
Decorating the Default Compiler
Often you want to enhance the default compiler rather than replace it entirely. TheUseContextCompilerDecorator capability wraps the existing compiler, letting you post-process its output. The decorator receives whatever compiler is currently configured and returns a new one that wraps it:
Example: Token-Limited Compiler
A common pattern is to limit the messages sent to the model based on an estimated token budget. This decorator wraps any inner compiler and keeps only the most recent messages that fit within the budget, working backward from the newest message:
Note: The token estimate here uses a simple strlen / 4 heuristic. For production use, consider integrating a proper tokenizer for your target model.
Example: Injecting Retrieved Documents
Another common pattern is injecting ephemeral context (such as RAG-retrieved documents) into the message stream without permanently storing them:Serialization
AgentContext supports full serialization through toArray() and fromArray(), making it straightforward to persist and restore agent context across requests, sessions, or process boundaries:
Common Use Cases
Compilers are the right tool when you need to:- Trim older messages to stay within the model’s context window while preserving recent conversation flow
- Inject ephemeral context (e.g., retrieved documents, real-time data) without permanently storing them in the message history
- Exclude internal traces from multi-agent orchestration so child agent tool-calling details do not leak into the parent’s view
- Prioritize sections by sending summaries before raw history, giving the model a structured overview
- Redact sensitive content before it reaches the model, such as stripping PII or credentials from tool outputs
- Implement sliding windows that keep only the most recent N messages or N tokens of conversation
- Support hybrid strategies by combining summarized older history with full recent messages for optimal context utilization