Skip to content

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.

from symfonic.agent.conversation import NullConversationManager

NullConversationManager()

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 tasksSlidingWindowConversationManager.
  • Long analytical sessions where history mattersSummarizingConversationManager with a larger keep_recent_messages / context_window_tokens.
  • Short or externally-managed sessionsNullConversationManager.

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