
AutoGen vs LangGraph: how do they handle context management (message filtering, per-source limits) in long-running workflows?
Most teams evaluating agent frameworks focus on prompts and models, then get surprised when long-running workflows fall over from context bloat and noisy message histories. The real constraint is how your runtime manages messages: what gets routed where, how much history each agent sees, and how you prevent one noisy source from drowning everything else. This is the layer where AutoGen and LangGraph really diverge.
Quick Answer: AutoGen treats context management as a first-class runtime concern, with message filtering and per-source controls wired directly into its event-driven stack and GraphFlow workflows. LangGraph lets you shape context via “state” (including per-node slices of
messages), but you usually have to hand-roll filtering logic in your reducers and node functions. For long-running, multi-agent workflows, AutoGen gives you explicit, reusable controls (likeMessageFilterAgentandPerSourceFilter) while LangGraph gives you a flexible—though more DIY—state model.
Why This Matters
In long-running workflows, context is your primary bottleneck. If every agent sees the full conversation history, you:
- Blow through token limits
- Increase hallucinations from irrelevant context
- Lose determinism, because small ordering changes cause different histories
Framework-level context controls determine whether your system degrades gracefully after 100+ turns or becomes un-deployable. AutoGen’s context story is “runtime-enforced filtering and per-source shaping,” while LangGraph’s is “application-controlled state shaping.”
Key Benefits:
- Predictable context growth: AutoGen’s message filtering and topic routing help you cap history per agent and per source, keeping token usage under control as flows loop or fan out.
- Cleaner mental model per node/agent: Both frameworks let each component see only what’s relevant, but AutoGen does this via message filters, while LangGraph does it via state slices and reducers.
- Safer long-running workflows: With explicit controls like
PerSourceFilterandTaskResult(stop_reason=...)in AutoGen (and LangGraph’s checkpointing/state), you can design workflows that can run for a long time without silently accumulating junk context.
Core Concepts & Key Points
| Concept | Definition | Why it's important |
|---|---|---|
| Message filtering (AutoGen) | Runtime feature that controls which messages each agent receives based on rules, source, and history constraints. | Lets you “Reduce hallucinations,” “Control memory load,” and “Focus agents only on relevant information” in GraphFlow and AgentChat teams. |
| State shaping (LangGraph) | Pattern of using a shared or partitioned state object (often with a messages list) plus reducers to control what each node sees. | Gives you flexible, code-level control over context, but shifts responsibility from the runtime to your application logic. |
| Per-source limits & routing | Rules that cap or filter messages by who sent them (source) and what “channel” or topic they belong to. | Prevents a single agent or tool from flooding the context and enables multi-tenant isolation and topic-based workflows. |
How It Works (Step-by-Step)
At a high level, both frameworks aim to solve “who sees what, when,” but they do it differently.
AutoGen: Runtime-Enforced Context Control
AutoGen is layered—Studio → AgentChat → Core → Extensions—so message and context control live at the runtime level (Core) and are surfaced through AgentChat teams.
-
Event-Driven Messaging (Core):
autogen-coremodels interactions as events between agents running in a runtime (SingleThreadedAgentRuntimefor local, or distributed runtimes for host+workers+gateways). Messages flow through the runtime—not directly between agents. -
GraphFlow Execution vs. Message Flow (AgentChat):
In AgentChat,GraphFlowbuilds workflows on top of aDiGraphexecution graph. The docs are explicit: the execution graph controls which agents run in what order, but does not control what messages they see. By default, all messages go to all agents in the graph. -
Message Filtering Layer:
Message filtering is a separate feature: you attach filters (e.g., viaMessageFilterAgentand per-source filters) to limit which messages each downstream agent receives. This is where you implement source-level caps, topic-level inclusion, or role-specific views. -
Type- and Source-Based Routing (Core):
At the Core layer, topics and subscriptions (TypeSubscription, topic =(Topic Type, Topic Source)) let you subscribe agents by class/type instead of hard-coded IDs. That’s where per-source controls naturally plug in: you can filter by topic source and subscribe only the agents (or tenants) that should see those messages.
LangGraph: State-Centric Context Control
LangGraph is built around a graph where each node is a function and the graph passes a mutable “state” object between nodes.
-
State as Context:
You typically define a state like:from typing import TypedDict, List from langgraph.graph import StateGraph class WorkflowState(TypedDict): messages: List[dict] # or LangChain messages # other fields...Nodes read from and update
state["messages"], so context is whatever you choose to keep there. -
Reducers and Partial State:
You can attach reducers to merge messages, truncate history, or keep per-node slices. LangGraph gives you hooks to say “this node only cares about the last N messages” by implementing that logic yourself. -
Per-Node Views:
You can model per-node context by shaping state paths (e.g.,state["writer_messages"],state["reviewer_messages"]) and mapping them to different nodes. But the framework doesn’t impose a filtering abstraction—you implement the slice or filter in your node functions or reducers.
In short: AutoGen bakes context control into the runtime and team constructs; LangGraph gives you a flexible state model and expects you to code the filtering yourself.
Concrete AutoGen Example: Message Filtering in a GraphFlow Workflow
Below is a minimal, end-to-end example of context management in AutoGen using GraphFlow, with a writer and reviewer plus message filtering so the reviewer only sees the writer’s last few drafts and a summary agent sees only selected messages.
Installation
pip install -U "autogen-agentchat" "autogen-core" "autogen-ext[openai]"
Python 3.10 or later is required. Set OPENAI_API_KEY or configure your model client explicitly.
Define Agents and GraphFlow with Filtering
import os
from autogen_core import SingleThreadedAgentRuntime
from autogen_ext.openai import OpenAIChatCompletionClient
from autogen_agentchat import AssistantAgent, GraphFlow, MessageFilterAgent
from autogen_agentchat.teams.graph import DiGraph # execution graph
# Configure an OpenAI model client (e.g., gpt-4o)
model_client = OpenAIChatCompletionClient(
model="gpt-4o",
api_key=os.environ["OPENAI_API_KEY"],
)
# Base writer agent
writer = AssistantAgent(
name="writer",
model_client=model_client,
system_message="Draft concise technical paragraphs.",
)
# Base reviewer agent
reviewer = AssistantAgent(
name="reviewer",
model_client=model_client,
system_message="Review the draft for clarity and technical accuracy.",
)
# Summary agent that should NOT see everything
summarizer = AssistantAgent(
name="summarizer",
model_client=model_client,
system_message="Summarize only the final reviewed content.",
)
# Wrap reviewer in a MessageFilterAgent to control its context
filtered_reviewer = MessageFilterAgent(
name="filtered_reviewer",
model_client=model_client,
# This agent proxies to `reviewer` but filters messages before they reach it
downstream_agent=reviewer,
# Example: limit messages per source & overall
per_source_max_messages={"writer": 3}, # only last 3 writer messages
global_max_messages=10, # cap total history
)
# Similarly wrap summarizer with strict filtering
filtered_summarizer = MessageFilterAgent(
name="filtered_summarizer",
model_client=model_client,
downstream_agent=summarizer,
# Only see messages from reviewer, last 2
per_source_max_messages={"filtered_reviewer": 2},
global_max_messages=5,
)
# Build the execution graph:
# writer -> filtered_reviewer -> filtered_summarizer
graph = DiGraph()
graph.add_node("writer", agent=writer)
graph.add_node("filtered_reviewer", agent=filtered_reviewer)
graph.add_node("filtered_summarizer", agent=filtered_summarizer)
graph.add_edge("writer", "filtered_reviewer")
graph.add_edge("filtered_reviewer", "filtered_summarizer")
team = GraphFlow(graph=graph)
# Run the flow
async def main():
runtime = SingleThreadedAgentRuntime()
async with runtime:
result = await team.run(
task="Explain how AutoGen message filtering helps control context in long-running workflows.",
runtime=runtime,
)
# TaskResult includes messages and stop_reason
print("Stop reason:", result.stop_reason)
print("Messages:")
for m in result.messages:
print(m.source, ":", getattr(m, "content", ""))
Note:
- The
DiGraphdefines execution order. - The
MessageFilterAgentdefines who sees what. per_source_max_messagesis effectively a per-source limit: the reviewer never sees more than the last N messages fromwriter. The summarizer only sees what the reviewer produced, not the raw drafts.
Because filtering lives in a distinct abstraction, you can add or tighten filters without changing workflow structure.
Concrete LangGraph-Style Context Control (Conceptual)
LangGraph doesn’t ship a “MessageFilterAgent” equivalent; it focuses on state graphs. A conceptual analogue to the flow above would look like:
from typing import TypedDict, List
from langgraph.graph import StateGraph, END
class WorkflowState(TypedDict):
writer_messages: List[str]
reviewer_messages: List[str]
summary: str
def writer_node(state: WorkflowState) -> WorkflowState:
# generate a draft using LLM of your choice
draft = call_llm("Draft about AutoGen message filtering...")
state["writer_messages"].append(draft)
return state
def reviewer_node(state: WorkflowState) -> WorkflowState:
# reviewer only sees the last 3 drafts
drafts = state["writer_messages"][-3:]
review_input = "\n\n".join(drafts)
review = call_llm(f"Review these drafts:\n{review_input}")
state["reviewer_messages"].append(review)
return state
def summarizer_node(state: WorkflowState) -> WorkflowState:
# summarizer only sees last 2 reviews
reviews = state["reviewer_messages"][-2:]
summary_input = "\n\n".join(reviews)
state["summary"] = call_llm(f"Summarize:\n{summary_input}")
return state
graph = StateGraph(WorkflowState)
graph.add_node("writer", writer_node)
graph.add_node("reviewer", reviewer_node)
graph.add_node("summarizer", summarizer_node)
graph.set_entry_point("writer")
graph.add_edge("writer", "reviewer")
graph.add_edge("reviewer", "summarizer")
graph.add_edge("summarizer", END)
workflow = graph.compile()
Here:
- Execution graph and context shaping are both coded into the node functions and how they slice
state. - You decide per-node history limits (
[-3:],[-2:]), akin to per-source limits in AutoGen, but there’s no dedicated filtering primitive.
This is powerful but also brittle: if you later add a new node, you must ensure its view of state doesn’t accidentally explode context.
Common Mistakes to Avoid
-
Treating execution order as context control in AutoGen:
TheDiGraphin GraphFlow only controls who runs next. Without message filtering, every agent may still see the full message history. Always pairGraphFlowwith appropriate filters when workflows will loop or fan out. -
Letting “messages” grow unbounded in LangGraph state:
Storing every turn in a singlemessageslist without reducers is a recipe for token overflows. Explicitly design per-node views, truncation, or summarization logic.
Real-World Example
In my org, we have multi-tenant, long-running analysis workflows that:
- Ingest logs and documents over hours
- Loop between triage agents, specialized analyzers, and a compliance reviewer
- Need strict isolation: one tenant’s logs should never influence another’s context
With AutoGen:
- Core’s topic-based routing (
Topic = (Topic Type, Topic Source)) andTypeSubscriptionallowed us to route tenant-specific events to the right agents without hard-coding IDs. - We wrapped analyzers in
MessageFilterAgentinstances withPerSourceFilter-style rules to keep only recent, relevant messages per source (e.g., only last N alerts from a given collector). - GraphFlow gave us a clear, debuggable execution graph, while message filtering and topics controlled which messages each node saw.
Attempting the same in a state-centric system like LangGraph meant encoding all of that logic into our state schema and node functions—workable, but much harder to audit and evolve across teams.
Pro Tip: If you expect your workflow to run for more than a dozen turns or to handle multiple tenants, model context policies first (what each agent may see, per source, and per topic), then pick the framework whose primitives map most directly to those policies. In AutoGen, that usually means starting with GraphFlow + MessageFilterAgent and moving to topic-based routing in Core as you scale.
Summary
AutoGen and LangGraph both support long-running workflows, but they differ sharply in how they handle context:
- AutoGen’s event-driven Core and AgentChat teams (especially GraphFlow) treat context as a runtime concern. Message filtering, per-source limits, and topic/subscription routing are explicit primitives designed to “Reduce hallucinations,” “Control memory load,” and “Focus agents only on relevant information.”
- LangGraph gives you a flexible state graph where you define what each node sees by shaping and reducing state. This is powerful but application-driven; you must implement your own message slicing, per-source caps, and summarization.
If your pain points are around message routing, lifecycle, and context control in long-running multi-agent flows, AutoGen’s runtime-level constructs (e.g., MessageFilterAgent, TypeSubscription, TaskResult(stop_reason=...)) give you more operational leverage with less bespoke code than a purely state-centric approach.