#2029·swarms

[BUG][Multi-Agent Structures][Every structure flattens the shared conversation into one user block, defeating the Agent-side transcript fix]

Author: kyegomezCreated Aug 25, 2026Updated Sep 8, 2026
Labelsbug

Summary

#1748 established that flattening a conversation into one user message breaks prompt caching and role attribution. The Agent side was fixedswarms/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:

python
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=:

bash
$ grep -rn "\.run(.*messages=" swarms/     # no matches
$ grep -rln "Transcript" swarms/structs/   # transcript.py, agent.py

Reproduction

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_2

The 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:

python
lines.append(f"{prefix}{message.get('role')}: {message.get('content')}")
...
return "\n\n".join(lines) if lines else empty_message

Consequences

Identical to #1748, now multiplied across every orchestration:

  1. No prompt caching. The single user message changes every turn, so there is no stable prefix. GroupChat and RoundRobinSwarm use Conversation(time_enabled=True), so [timestamp] prefixes guarantee the content differs even when the semantic history does not.
  2. 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 by Word: is indistinguishable from a new speaker — markdown answers do this constantly.
  3. Agents see their own output mislabelled as the user's. Structures reuse the same Agent objects across loops, so an agent's prior answer is already an assistant turn in its short_memory; re-injecting it inside a user blob is exactly the failure context_utils.py:10-11 documents.
  4. Quadratic nesting. Because agent.run appends each task to short_memory and 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).