Skip to content

[Bug] ConcurrentWorkflow silently drops one agent's answer, and which one changes every run - #1884

Open
ayaangazali wants to merge 1 commit into
kyegomez:masterfrom
ayaangazali:fix/all-except-first-drops-an-agent
Open

[Bug] ConcurrentWorkflow silently drops one agent's answer, and which one changes every run#1884
ayaangazali wants to merge 1 commit into
kyegomez:masterfrom
ayaangazali:fix/all-except-first-drops-an-agent

Conversation

@ayaangazali

Copy link
Copy Markdown
Contributor

Problem

Conversation.return_all_except_first slices [2:]:

def return_all_except_first(self):
    """Return all messages except the first one."""
    return self.conversation_history[2:]

The docstring says one; the code skips two. It hardcodes the assumption that every history begins [System, User, ...], which is not true in two ways:

  • Agent.short_memory adds the System row conditionally_initialize_new_conversation only calls add("System", ...) if self.system_prompt is not None.
  • Swarms build their own Conversation with no system prompt at all. ConcurrentWorkflow's history is [User, agent, agent, agent].

So on a swarm the slice eats the first agent's answer, not a preamble.

Impact

dict-all-except-first is ConcurrentWorkflow's default output_type, so this is default behaviour, not an opt-in path. Three agents in, two answers out:

class Fake:
    def __init__(s, n): s.agent_name = n
    def run(s, task=None, img=None, imgs=None, **k): return f"answer-from-{s.agent_name}"

cw = ConcurrentWorkflow(agents=[Fake("A"), Fake("B"), Fake("C")], autosave=False)
cw.run("q")
history  ['User', 'B', 'C', 'A']
returned [{'role': 'C', ...}, {'role': 'A', ...}]        # B is gone

Results are collected as they complete, so the answer that disappears is whichever agent finished first — a different agent on each run. No exception, no warning; the output is still well-formed, just missing one contributor.

AgentRearrange is affected conditionally: team_awareness=True inserts a System message and makes [2:] correct, team_awareness=False makes it drop an agent. An unrelated flag changes how much of the output survives.

Fix

Derive the offset instead of hardcoding it: skip a leading System row when one exists, then skip the first real message, which is the input task. Both slicing methods share one helper so they cannot drift apart.

Verification

swarm, 3 agents      returned ['A', 'B', 'C']   count 3      (was 2)
Agent-shaped history ['a1', 'a2']                            unchanged
no system prompt     ['a1', 'a2']                            task only dropped

The Agent-shaped case is the one that must not move: with a System row present it still drops both the System row and the task, exactly as before.

tests/structs/test_conversation.py — 51 passed. tests/structs/test_concurrent_workflow.py — 10 passed.

One file, 24 lines.

(Unrelated to this PR: black . --check currently fails on examples/mcp/agents/07_huggingface_model_search.py and 12_semgrep_security_scan.py, which arrived with 47d94eb. The file this PR touches is clean.)

Copilot AI lite review requested due to automatic review settings August 13, 2026 23:53
@ayaangazali
ayaangazali requested a review from kyegomez as a code owner August 13, 2026 23:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

`return_all_except_first` slices `[2:]`, hardcoding the assumption that
every history begins `[System, User, ...]`. Two things break that:

- `Agent.short_memory` only adds the System row when a `system_prompt`
  was given — `_initialize_new_conversation` makes it conditional.
- Swarms build their own Conversation with no system prompt at all.
  `ConcurrentWorkflow`'s history is `[User, agent, agent, ...]`.

So for a swarm the slice consumes the first agent's output. With
`dict-all-except-first` — `ConcurrentWorkflow`'s default `output_type` —
three agents return two answers:

    history  ['User', 'B', 'C', 'A']
    returned [{'role': 'C', ...}, {'role': 'A', ...}]

and because results are collected as they complete, the answer that
disappears is whichever agent happened to finish first: a different one
on every run, with no error.

Derive the offset instead: skip a leading System row when there is one,
then skip the first real message, which is the input. Agent histories
behave exactly as before (System and task both dropped); swarm histories
now keep every agent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants