Conversation Managers¶
A conversation manager decides how a conversation's message history is kept
inside the model's context window as a session grows. symfonic-core ships three
named, swappable strategies you pass straight to SymfonicAgent:
from symfonic.agent import SymfonicAgent
from symfonic.agent.conversation import SlidingWindowConversationManager
agent = SymfonicAgent(
model_provider=provider,
conversation_manager=SlidingWindowConversationManager(window_size=20),
)
They are thin, legible wrappers over machinery the framework already has — the
summarizing compaction node and the pure message-count window. Passing a
strategy resolves into an AgentConfig override and takes precedence over
any compaction / max_conversation_messages set on the config. There is no new
engine behavior; you're choosing a policy over the existing one.
The three strategies¶
| Strategy | What it does | Cost | Use when |
|---|---|---|---|
SummarizingConversationManager |
Summarizes overflow into a running summary (the framework default) | LLM call on overflow | Older turns still carry information the model needs |
SlidingWindowConversationManager |
Keeps the most recent window_size messages; drops older ones |
free (no LLM) | Only recent context matters |
NullConversationManager |
No management; history grows unbounded | free | Short sessions, or you manage history yourself |
SummarizingConversationManager¶
The framework's default behavior, named and parameterized. When estimated
tokens cross soft_threshold_tokens (or the hard context_window_tokens
budget), the oldest messages beyond keep_recent_messages are summarized via
summary_model and removed from the live history.
from symfonic.agent.conversation import SummarizingConversationManager
SummarizingConversationManager(
context_window_tokens=50_000, # None -> falls back to the model's window
keep_recent_messages=10, # most-recent messages kept verbatim
soft_threshold_tokens=4_000, # summarize early, before the hard budget
summary_model="claude-haiku-4-5",
)
SlidingWindowConversationManager¶
A pure message-count window — no LLM summary. Keeps the most recent
window_size messages; older turns are dropped. The summarizer is disabled so
the count-drop is the only limiter.
from symfonic.agent.conversation import SlidingWindowConversationManager
SlidingWindowConversationManager(window_size=40) # default 40
Cheapest option — no summarization cost — but dropped turns are gone from the prompt. Reach for it when a task only needs the last handful of exchanges.
NullConversationManager¶
No management at all: both the summarizer and the count-drop are disabled and the history grows unbounded (up to the model's own limit). For short sessions, or when the caller trims history externally.
Relationship to HMS memory¶
Conversation window ≠ memory
These strategies govern the LangGraph conversation window only — the raw turns in the current prompt. They are orthogonal to the 5-layer HMS memory: hydration, consolidation, and procedural learning run independently. A sliding window drops raw turns from the prompt; it does not erase what HMS has already persisted. Dropped context can still be recalled through memory retrieval on a later turn.
This is why symfonic's conversation management is stronger than a bare sliding-window buffer: even the cheapest strategy sits on top of a memory system that can bring relevant older context back when it matters.
Choosing a strategy¶
- Default (do nothing) — you get summarizing behavior; good general choice.
- Latency/cost-sensitive, recent-only tasks —
SlidingWindowConversationManager. - Long analytical sessions where history matters —
SummarizingConversationManagerwith a largerkeep_recent_messages/context_window_tokens. - Short or externally-managed sessions —
NullConversationManager.
Writing your own¶
ConversationManager is a small base class — subclass it and implement apply,
returning an AgentConfig with compaction and/or max_conversation_messages
set to realize your policy:
from dataclasses import dataclass, replace
from symfonic.agent.conversation import ConversationManager
from symfonic.core.config import AgentConfig, CompactionConfig
@dataclass(frozen=True)
class TightBudget(ConversationManager):
def apply(self, agent_config: AgentConfig) -> AgentConfig:
return replace(
agent_config,
compaction=CompactionConfig(context_window_tokens=8_000, keep_recent_messages=4),
)
See also¶
- Memory (5-Pentad) — what persists independently of the window
- Cache Tier Tradeoffs — how window size interacts with prompt caching
- The Agent