AutoGen vs LangGraph: how do they handle context management (message filtering, per-source limits) in long-running workflows?
AI Agent Automation Platforms

AutoGen vs LangGraph: how do they handle context management (message filtering, per-source limits) in long-running workflows?

10 min read

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 (like MessageFilterAgent and PerSourceFilter) 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 PerSourceFilter and TaskResult(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

ConceptDefinitionWhy 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 & routingRules 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.

  1. Event-Driven Messaging (Core):
    autogen-core models interactions as events between agents running in a runtime (SingleThreadedAgentRuntime for local, or distributed runtimes for host+workers+gateways). Messages flow through the runtime—not directly between agents.

  2. GraphFlow Execution vs. Message Flow (AgentChat):
    In AgentChat, GraphFlow builds workflows on top of a DiGraph execution 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.

  3. Message Filtering Layer:
    Message filtering is a separate feature: you attach filters (e.g., via MessageFilterAgent and 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.

  4. 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.

  1. 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.

  2. 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.

  3. 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 DiGraph defines execution order.
  • The MessageFilterAgent defines who sees what.
  • per_source_max_messages is effectively a per-source limit: the reviewer never sees more than the last N messages from writer. 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:
    The DiGraph in GraphFlow only controls who runs next. Without message filtering, every agent may still see the full message history. Always pair GraphFlow with appropriate filters when workflows will loop or fan out.

  • Letting “messages” grow unbounded in LangGraph state:
    Storing every turn in a single messages list 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)) and TypeSubscription allowed us to route tenant-specific events to the right agents without hard-coding IDs.
  • We wrapped analyzers in MessageFilterAgent instances with PerSourceFilter-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.

Next Step

Get Started