Repository navigation
Does the goal survive a handoff? Compaction boundaries in long multi-agent runs #8287
Replies: 2 comments 1 reply
|
Your diagnosis of multi-agent handoffs is spot-on: naive multi-agent architectures do not eliminate context pressure — they convert an observable in-memory compaction problem into a silent, distributed boundary problem. When Agent A summarizes its state for Agent B in natural language, it performs lossy compaction across a process boundary. If Agent A compacts mid-task, it summarizes a state it has already partially forgotten, and Agent B receives an incomplete contract with zero ability to inspect what was omitted. Here is how this is addressed in modern AutoGen architectures ( 1. Does AutoGen preserve goals verbatim, or do you implement it yourself?In standard To guarantee the goal survives every handoff and summarization, production implementations use two mechanisms: A. System Message Invariance (Protected Channel)In AutoGen, an agent's # Pass the verbatim goal and hard constraints directly into the system envelope
agent = AssistantAgent(
name="coder",
model_client=model_client,
system_message=(
f"GLOBAL GOAL (NON-EVICTABLE):\n{verbatim_goal}\n\n"
f"ACCEPTANCE CRITERIA:\n{chr(10).join(f'- {c}' for c in criteria)}\n\n"
"You are the execution specialist. Do not deviate from these constraints."
)
)B. Pinned Context TransformFor dynamic multi-turn tasks, you can register a custom from autogen_core.models import ModelContextTransform, LLMMessage
class PinnedGoalTransform(ModelContextTransform):
def __init__(self, pinned_goal: str):
self._pinned_message = UserMessage(content=f"[SYSTEM INVARIANT]\n{pinned_goal}", source="system")
async def transform(self, messages: list[LLMMessage]) -> list[LLMMessage]:
# Compact or truncate the body
compacted = await self.compact_body(messages[1:])
# Always re-anchor the pinned goal immediately after the system message
return [messages[0], self._pinned_message] + compacted2. Is the compaction event observable?Out-of-the-box AutoGen emits OpenTelemetry spans for agent messages and tool calls, but does not emit a native "dropped messages" diff. However, you can make compaction transparent by wrapping your compaction transformer with a differential audit logger: class ObservableCompactionTransform(ModelContextTransform):
def __init__(self, inner_transform: ModelContextTransform, logger):
self.inner = inner_transform
self.logger = logger
async def transform(self, messages: list[LLMMessage]) -> list[LLMMessage]:
original_ids = {id(m): m for m in messages}
transformed = await self.inner.transform(messages)
kept_ids = {id(m) for m in transformed}
dropped = [m for mid, m in original_ids.items() if mid not in kept_ids]
if dropped:
self.logger.warning("context_compaction_boundary", extra={
"input_count": len(messages),
"output_count": len(transformed),
"dropped_count": len(dropped),
"dropped_previews": [str(m.content)[:80] for m in dropped],
})
return transformedWhen an agent strays on turn 40, querying 3. The "Pointer-to-Trace" Pattern (Lazy Handoff)Instead of forcing Agent A to summarize 50k tokens into a lossy paragraph for Agent B, use By-Reference Handoffs. In this pattern:
# Agent B starts with a pristine context window (< 500 tokens)
handoff_payload = {
"root_goal": verbatim_goal,
"current_status": "Implemented auth endpoints; failing on test_jwt_expiry",
"trace_id": "run_98234_step_2"
}
# If Agent B is confused about why an earlier design decision was made:
# Agent B calls: retrieve_trace_detail(query="JWT expiration configuration", trace_id="run_98234_step_2")This decouples task progression from context bloat: Agent B receives a pristine working window without inheriting Agent A's noise, but retains on-demand, non-lossy access to raw history. |
|
Uh oh!
There was an error while loading. Please reload this page.
Disclosure up front: I build Grunz, a coding agent running open-weight models. Single-agent, not multi-agent, so treat this as a view from outside the paradigm rather than a critique from inside it. I'd genuinely like to know whether this is a solved problem here and I've just missed how.
The thing I keep running into
Long agent runs don't usually fail at a tool call. They fail at a compaction boundary.
When the context window fills, something has to decide what to discard. If the goal statement isn't preserved verbatim through that step, it gets summarised into vagueness. The model then reads back its own compacted notes, no longer knows precisely what it was doing, and quietly restarts the plan — frequently re-deriving work it already completed.
From the outside this reads as "the model forgot my instructions." But they weren't buried in the middle of a long context. They were compressed out. Nothing errors, so it doesn't show up as a failure — it shows up as a run that took three times as long and ended somewhere adjacent to where you wanted.
Why I'd expect multi-agent to make this harder, not easier
The intuitive pitch for multi-agent is that decomposition solves context pressure: each agent holds less, so nobody fills their window. My experience is that it relocates the problem rather than removing it, for two reasons.
1. Every handoff is a lossy compaction with extra steps. When agent A summarises its state for agent B, that's the same operation as a compaction — something decides what survives — except now it also crosses an agent boundary, so B has no access to what A dropped and no way to know it's missing. A compaction at least leaves the raw history recoverable in principle. A handoff generally doesn't.
2. The boundaries don't line up. An agent's context can fill mid-subtask, meaning it compacts, loses fidelity on its own assignment, and then hands off a summary of a plan it had already partially forgotten. The orchestrator sees a completed handoff and a plausible summary, so nothing looks wrong at the supervision layer.
Net effect in my experience: the group can be confidently, collectively wrong in a way a single agent with a full trace usually isn't — because every participant's view is locally coherent.
What I'd like to know from people who actually run these
Does AutoGen preserve the original goal and acceptance criteria verbatim across handoffs and summarisation steps, or is that something you implement yourself per-workflow? Pinning the goal verbatim through every compaction was the single highest-leverage change I made, and I'd expect it to matter more in a group chat, not less.
Is the compaction event itself observable? Teams log tool calls and messages, but I rarely see anyone logging what got dropped at a summarisation step. That's usually where a long run silently went wrong, and without it you can explain every individual message and still not explain the run.
Is there a known pattern for a handoff that carries a pointer to the full prior context rather than only a summary — so the receiving agent can pull back detail it discovers it needs, instead of working from a lossy snapshot?
Entirely possible the answer to all three is "yes, here's the doc," in which case I'd be glad to read it. I'd rather find out I'm wrong about this than keep telling people multi-agent makes it worse.
All reactions