#2202·swarms

[BUG][PlannerGeneratorEvaluator] The entire shared state file is pasted into every agent call, O(steps² × retries) (#2053 follow-up)

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

Summary

Split out of #2053, which was closed by #2189 while this file was still outstanding. PlannerGeneratorEvaluator pastes its entire, ever-growing shared state file into every agent call as one user string. Of the structures listed in #2053 this is the worst offender for both context size and cost, and it is the only one with no open PR.

Sites

swarms/structs/planner_generator_evaluator.py

Line Caller Expression
:381-391 _run_planner --- SHARED STATE ---\n{shared_state}\n--- END SHARED STATE ---planner_agent.run(task=...)
:474-484 _negotiate_contract (generator) same
:496-503 _negotiate_contract (evaluator) same, re-read after the generator appended
:554-574 _execute_step same → generator_agent.run(task=...)
:606-615 _evaluate_step same → evaluator_agent.run(task=...)

None of the five calls passes messages=. self.conversation is cleared and seeded with the task at :812-815 but is otherwise not what the agents see; the shared-state file is.

Growth

_append_to_shared_state (:340-352) appends after the plan, after every contract proposal, after every contract review, after every generator output and after every evaluation, including retries. Each call then re-reads the whole file (:333-338) and pastes it in. The generator and evaluator are single instances created once (:309, :321) and reused for every step, so their own memory grows alongside the file.

Per step the prompt carries the full history of every previous step, so total tokens are O(steps² × retries). And because the pasted blob is different on every call, the cached prefix is never reused. This is exactly the structure where prompt caching would matter most.

Credit where due: all three agents are output_type="final" (:305, :317, :329), so what gets appended is an answer, not a transcript.

Fix

Two parts.

  1. Send history as turns. Record each planner / generator / evaluator output into self.conversation under its agent name, and replace the --- SHARED STATE --- interpolation with

    python
    from swarms.structs.context_utils import messages_for, split_last_turn
    
    prior, task = split_last_turn(messages_for(agent.agent_name, self.conversation))
    agent.run(task=task, messages=prior)

    The file can stay as an on-disk audit log; it should stop being the prompt.

  2. Scope what each call sees. planner_worker_swarm.py (#2181) is the model: workers reset memory and receive only the dependency context they need. Here the generator on step N needs the plan, its contract, and the evaluator's feedback on its last attempt at step N, not every prior step's contract negotiation. That is the part that turns O(steps²) into O(steps).

Part 1 alone gives the cache a stable prefix. Part 2 is what makes long runs affordable.

Found at 7b709c95b.