Skip to main content

Context Manager

When to use this

Use a context manager when your agents run long multi-turn conversations and you need to keep the message history within the LLM's context window. The context manager is called on every Agent node invocation that has trim_context=True.

Import path

from agentflow.core.state import BaseContextManager, MessageContextManager

MessageContextManager

The built-in implementation. Keeps the most recent N user messages plus all system messages.

from agentflow.core.state import MessageContextManager

ctx_mgr = MessageContextManager(
max_messages=10,
remove_tool_msgs=False,
)

graph = StateGraph(context_manager=ctx_mgr)

Constructor parameters

ParameterTypeDefaultDescription
max_messagesint10Maximum number of user-role messages to keep. Older messages are dropped.
remove_tool_msgsboolFalseIf True, also remove AI messages containing tool calls and their corresponding tool result messages before counting.

Preservation guarantee

Messages with role == "system" are never trimmed regardless of max_messages. They are prepended to the front of every trimmed window.

Methods

MethodSignatureDescription
trim_context(state: S) → SSynchronous trim. Returns the state with context replaced by the trimmed list, or the original state if no trimming was needed.
atrim_contextasync (state: S) → SAsync version. Internally delegates to trim_context.

BaseContextManager

Abstract base class. Subclass this to implement custom trimming strategies.

from agentflow.core.state import BaseContextManager, AgentState
from typing import TypeVar

S = TypeVar("S", bound=AgentState)

class MyContextManager(BaseContextManager[S]):

def trim_context(self, state: S) -> S:
# return state with state.context modified
...

async def atrim_context(self, state: S) -> S:
return self.trim_context(state)

Abstract methods

MethodSignatureDescription
trim_context(state: S) → SMust implement. Trim state.context and return the updated state.
atrim_contextasync (state: S) → SMust implement. Async version.

Both methods receive the full AgentState (or custom subclass) and must return it after modifying state.context in place or replacing it.


Where it fits in the graph

graph.compile()
└── Agent node (trim_context=True)
└── calls context_manager.atrim_context(state)
before sending messages to the LLM provider

The context manager is called at the start of each Agent invocation, before the LLM API call. The trimmed state is used only for that invocation; the full state is still written to the checkpointer.


Dependency injection

The context manager is registered in the dependency container automatically when you pass it to StateGraph. Node functions can receive it via injection if needed:

from injectq import Inject
from agentflow.core.state import BaseContextManager

async def my_node(
state,
config: dict,
ctx_mgr: BaseContextManager | None = Inject[BaseContextManager],
) -> ...:
if ctx_mgr:
state = await ctx_mgr.atrim_context(state)
return state

Common errors

ErrorCauseFix
Context grows unbounded despite trim_context=Truecontext_manager= not passed to StateGraph(...).Add context_manager=MessageContextManager() to StateGraph.
atrim_context not foundCustom class only impl trim_context, not atrim_context.Both abstract methods must be implemented.
System prompt is being droppedSystem message role is not "system".Ensure role="system" — the check is exact string equality.