Describe the bug
MessageFilterAgent._apply_filter (python/packages/autogen-agentchat/src/autogen_agentchat/agents/_message_filter_agent.py, lines 154-167) hands the wrapped agent messages ordered by the position of each source in MessageFilterConfig.per_source, not by each message's actual chronological position:
def _apply_filter(self, messages: Sequence[BaseChatMessage]) -> Sequence[BaseChatMessage]:
result: List[BaseChatMessage] = []
for source_filter in self._filter.per_source:
msgs = [m for m in messages if m.source == source_filter.source]
if source_filter.position == "first" and source_filter.count:
msgs = msgs[: source_filter.count]
elif source_filter.position == "last" and source_filter.count:
msgs = msgs[-source_filter.count :]
result.extend(msgs)
return result
result is built by iterating per_source and appending each source's matches in the order the filters happen to be listed, so the emitted sequence's order reflects filter-config order, not message timeline order.
This directly breaks the class's own documented example. The docstring describes a looping graph A → B → A → B → C where B is configured with per_source=[user-first(1), A-last(1), B-last(N)] specifically "to see the user message, last message from A, and its own prior responses (for reflection)". If A has spoken twice by the time B runs again (once before B's own prior turn, once after), the real chronological order is user → A(older) → B(own) → A(newer), but _apply_filter emits [user, A(newer), B(own)], placing A's newer message before B's own reply, even though B's own reply chronologically happened first. The wrapped LLM agent receives a scrambled timeline with no error or warning.
To Reproduce
import asyncio
from typing import Sequence
from autogen_agentchat.agents import BaseChatAgent
from autogen_agentchat.agents._message_filter_agent import (
MessageFilterAgent, MessageFilterConfig, PerSourceFilter,
)
from autogen_agentchat.base import Response
from autogen_agentchat.messages import BaseChatMessage, TextMessage
from autogen_core import CancellationToken
class RecordingAgent(BaseChatAgent):
def __init__(self, name: str):
super().__init__(name=name, description="records message order")
self.received_order: list[str] = []
@property
def produced_message_types(self):
return (TextMessage,)
async def on_messages(self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken) -> Response:
self.received_order = [f"{m.source}:{m.content}" for m in messages]
return Response(chat_message=TextMessage(content="ack", source=self.name))
async def on_reset(self, cancellation_token: CancellationToken) -> None:
pass
async def main():
# Chronological transcript matching the class's own documented A->B->A->B->C example
transcript = [
TextMessage(content="please solve X", source="user"), # t0
TextMessage(content="A's first attempt", source="A"), # t1
TextMessage(content="B's first review", source="B"), # t2
TextMessage(content="A's second attempt", source="A"), # t3, most recent A
]
inner = RecordingAgent("B_inner")
filtered_agent = MessageFilterAgent(
name="B",
wrapped_agent=inner,
filter=MessageFilterConfig(per_source=[
PerSourceFilter(source="user", position="first", count=1),
PerSourceFilter(source="A", position="last", count=1),
PerSourceFilter(source="B", position="last", count=10),
]),
)
await filtered_agent.on_messages(transcript, CancellationToken())
print("received order:", inner.received_order)
asyncio.run(main())
Actual output:
received order: ["user:please solve X", "A:A's second attempt", "B:B's first review"]
Expected (chronologically ordered):
["user:please solve X", "B:B's first review", "A:A's second attempt"]
B's own prior response ("B's first review", which happened at t2) is placed after A's most recent message ("A's second attempt", from t3), even though it happened earlier. This inverts the actual conversation timeline as seen by the wrapped agent.
Expected Behavior
_apply_filter's output should preserve the original chronological order of messages, regardless of the order sources are listed in per_source.
Which package was this bug in?
Python AgentChat
Model used
N/A — reproducible with pure message-list logic, no LLM call needed.
Additional context
There's no dedicated test file for MessageFilterAgent, which likely explains why this went unnoticed; ordering isn't asserted anywhere.
Suggested fix direction: collect matches with their original indices, then sort the final result by original index before returning (or do a single pass over messages in original order, keeping each message only if it falls inside its source's requested first-N/last-N window), so the emitted subsequence always preserves the original chronological order.
Describe the bug
MessageFilterAgent._apply_filter(python/packages/autogen-agentchat/src/autogen_agentchat/agents/_message_filter_agent.py, lines 154-167) hands the wrapped agent messages ordered by the position of each source inMessageFilterConfig.per_source, not by each message's actual chronological position:resultis built by iteratingper_sourceand appending each source's matches in the order the filters happen to be listed, so the emitted sequence's order reflects filter-config order, not message timeline order.This directly breaks the class's own documented example. The docstring describes a looping graph
A → B → A → B → Cwhere B is configured withper_source=[user-first(1), A-last(1), B-last(N)]specifically "to see the user message, last message from A, and its own prior responses (for reflection)". If A has spoken twice by the time B runs again (once before B's own prior turn, once after), the real chronological order isuser → A(older) → B(own) → A(newer), but_apply_filteremits[user, A(newer), B(own)], placing A's newer message before B's own reply, even though B's own reply chronologically happened first. The wrapped LLM agent receives a scrambled timeline with no error or warning.To Reproduce
Actual output:
Expected (chronologically ordered):
B's own prior response ("B's first review", which happened at t2) is placed after A's most recent message ("A's second attempt", from t3), even though it happened earlier. This inverts the actual conversation timeline as seen by the wrapped agent.
Expected Behavior
_apply_filter's output should preserve the original chronological order ofmessages, regardless of the order sources are listed inper_source.Which package was this bug in?
Python AgentChat
Model used
N/A — reproducible with pure message-list logic, no LLM call needed.
Additional context
There's no dedicated test file for
MessageFilterAgent, which likely explains why this went unnoticed; ordering isn't asserted anywhere.Suggested fix direction: collect matches with their original indices, then sort the final
resultby original index before returning (or do a single pass overmessagesin original order, keeping each message only if it falls inside its source's requested first-N/last-N window), so the emitted subsequence always preserves the original chronological order.