[BUG][Multi-Agent Structures][Every structure flattens the shared conversation into one user block, defeating the Agent-side transcript fix]
Summary
#1748 established that flattening a conversation into one user message breaks prompt caching and role attribution. The Agent side was fixed — swarms/structs/agent.py:1443-1490 now builds a Transcript and passes llm_kwargs["messages"] = transcript.messages.
That fix does not reach any multi-agent structure. Every structure still renders the shared conversation as "role: content" prose and hands it to agent.run(task=<one big string>). It lands at swarms/structs/agent.py:1373:
self.short_memory.add(role=self.user_name, content=task)so _transcript_from_memory (agent.py:2149-2171) faithfully rebuilds a well-formed transcript around a single user turn that contains every other speaker as prose. The structured path is defeated at the boundary.
Transcript has exactly two importers in the repo (transcript.py, agent.py). No structure imports it, and no call site anywhere passes messages=:
$ grep -rn "\.run(.*messages=" swarms/ # no matches
$ grep -rln "Transcript" swarms/structs/ # transcript.py, agent.pyReproduction
Intercepting litellm.completion to capture what actually goes on the wire.
A solo Agent, turn 2 — correct, four typed messages:
[system] You are Solo.
[user] First question.
[assistant] ANSWER_1
[user] Second question.A 3-agent SequentialWorkflow, step 3 — two messages:
[system] You are Writer.
[user] User: Explain interest rate hikes.
Researcher: ANSWER_1
Analyst: ANSWER_2The model cannot distinguish the human's instruction from the Researcher's findings from the Writer's draft. They are three paragraphs with English name prefixes inside one user message. Any tool calls those agents made, and the results they got, are gone entirely — agent_answer (context_utils.py:107) keeps only the final message.
Where each structure flattens
| Structure | What each agent receives | Site |
|---|---|---|
MajorityVoting |
Full get_str() to voters and consensus agent |
majority_voting.py:219, :253 |
GroupChat |
Whole room, [timestamp] Name: text, one blob |
groupchat.py:485 |
HierarchicalSwarm (sync) |
History: <delta> \n\n Task: <order.task> |
hiearchical_swarm.py:1092 |
HierarchicalSwarm (stream) |
Full get_str() — diverges from sync |
hiearchical_swarm.py:1997, :2091 |
AgentRearrange / SequentialWorkflow |
new_context_for joined string |
agent_rearrange.py:690, :593, :1158, :1203 |
MixtureOfAgents |
Aggregator gets full get_str(); workers get a "\n\n".join of the prior layer |
mixture_of_agents.py:251, :246-248 |
RoundRobinSwarm |
Full transcript + a hand-written prose role header | round_robin.py:253, :64-70 |
LLMCouncil |
N answers + N evaluations fused into one blob | llm_council.py:167, :221, :228 |
DebateWithJudge |
f-string sections | debate_with_judge.py:502-506 |
GraphWorkflow |
Parents "\n\n".joined into one string |
graph_workflow.py:1799 |
ConcurrentWorkflow is the exception — it passes the raw task (concurrent_workflow.py:479) and is correct by construction, though its agents never see each other in any encoding.
The core flattener is swarms/structs/context_utils.py:73-85:
lines.append(f"{prefix}{message.get('role')}: {message.get('content')}")
...
return "\n\n".join(lines) if lines else empty_messageConsequences
Identical to #1748, now multiplied across every orchestration:
- No prompt caching. The single user message changes every turn, so there is no stable prefix.
GroupChatandRoundRobinSwarmuseConversation(time_enabled=True), so[timestamp]prefixes guarantee the content differs even when the semantic history does not. - Role attribution is prose. A peer's output is identified only by a
Name:prefix after a blank line. Any answer containing a blank line followed byWord:is indistinguishable from a new speaker — markdown answers do this constantly. - Agents see their own output mislabelled as the user's. Structures reuse the same
Agentobjects across loops, so an agent's prior answer is already anassistantturn in itsshort_memory; re-injecting it inside auserblob is exactly the failurecontext_utils.py:10-11documents. - Quadratic nesting. Because
agent.runappends eachtasktoshort_memoryand it is never cleared, on turn N the agent's transcript holds N nested snapshots.
Blocked on
A structure cannot fix this on its own today — see the companion issue: Agent.run discards a caller-supplied messages=. That has to land first.
Suggested shape
Mirroring agent.py:1443-1490: each structure maintains a Transcript, maps the shared Conversation's free-form roles onto chat roles per recipient (recipient's own messages → assistant, everything else → user, one message per turn), and passes messages= through to agent.run instead of an f-string. context_utils.new_context_for should gain a message-list return alongside the string so all four AgentRearrange call sites convert at once.
Found while auditing the docs against the code at 3e89f27b (v14.0.2).
Source: kyegomez/swarms