Project Version
9.5.0
Bug Description
ConsensusTool uses mutable instance-level state (self.original_proposal, self.models_to_consult, self.accumulated_responses) for its multi-step workflow. Because the tool is instantiated once as a singleton in server.py (line ~240: TOOLS = {"consensus": ConsensusTool(), ...}), all callers share the same state.
This is fine for a single sequential caller. However, when multiple callers invoke consensus concurrently — for example, Claude Code Agent Teams where multiple sub-agents each run their own consensus debate in parallel — the shared state gets overwritten between callers.
What happens
- Agent A calls
consensus step 1 → sets self.original_proposal = "Evaluate foundation candidates..."
- Agent B calls
consensus step 1 → overwrites self.original_proposal = "Evaluate neutral candidates..."
- Agent A calls
consensus step 2 → _consult_model reads self.original_proposal and sends Agent B's proposal to Agent A's models
- Models 2-5 in Agent A's debate see the wrong context
Affected state in tools/consensus.py
# These are set in __init__ and mutated in execute_workflow — shared across all callers
self.original_proposal: str | None = None
self.models_to_consult: list[dict] = []
self.accumulated_responses: list[dict] = []
Root cause
_consult_model() reads self.original_proposal (line ~680):
prompt = self.original_proposal if self.original_proposal else self.initial_prompt
This is the correct proposal for the last caller who set step 1, but wrong for any earlier caller still mid-workflow.
Suggested Fix
Key the workflow state by continuation_id instead of storing it on self:
def __init__(self):
super().__init__()
# ... existing fields kept for backward compat ...
self._sessions: dict[str, dict] = {} # keyed by continuation_id
# In step 1:
self._sessions[continuation_id] = {
"original_proposal": request.step,
"models_to_consult": request.models or [],
"accumulated_responses": [],
}
# In step 2+:
session = self._sessions[continuation_id]
prompt = session["original_proposal"] # isolated per-caller
The continuation_id mechanism already exists in the protocol — it just needs to be used as the isolation key for workflow state. Callers generate a UUID and pass it on every step. Sessions are cleaned up on workflow completion.
This is backward compatible: callers without continuation_id fall through to the existing instance-level state (single-caller behavior unchanged).
Relevant Log Output
No crash — the bug is silent. Models receive the wrong prompt and produce responses for the wrong topic. Only detectable by inspecting model output content.
Operating System
macOS
Reproduction
- Run two
consensus workflows concurrently (e.g., via Claude Code Agent Teams spawning multiple sub-agents)
- Each uses different
models arrays and different step prompts
- Observe that models in the first workflow receive the second workflow's proposal text
Impact
This makes consensus unusable in any concurrent/parallel agent scenario — which is increasingly common with Claude Code Agent Teams, n8n workflows, and multi-agent orchestration patterns.
Project Version
9.5.0
Bug Description
ConsensusTooluses mutable instance-level state (self.original_proposal,self.models_to_consult,self.accumulated_responses) for its multi-step workflow. Because the tool is instantiated once as a singleton inserver.py(line ~240:TOOLS = {"consensus": ConsensusTool(), ...}), all callers share the same state.This is fine for a single sequential caller. However, when multiple callers invoke
consensusconcurrently — for example, Claude Code Agent Teams where multiple sub-agents each run their own consensus debate in parallel — the shared state gets overwritten between callers.What happens
consensusstep 1 → setsself.original_proposal = "Evaluate foundation candidates..."consensusstep 1 → overwritesself.original_proposal = "Evaluate neutral candidates..."consensusstep 2 →_consult_modelreadsself.original_proposaland sends Agent B's proposal to Agent A's modelsAffected state in
tools/consensus.pyRoot cause
_consult_model()readsself.original_proposal(line ~680):This is the correct proposal for the last caller who set step 1, but wrong for any earlier caller still mid-workflow.
Suggested Fix
Key the workflow state by
continuation_idinstead of storing it onself:The
continuation_idmechanism already exists in the protocol — it just needs to be used as the isolation key for workflow state. Callers generate a UUID and pass it on every step. Sessions are cleaned up on workflow completion.This is backward compatible: callers without
continuation_idfall through to the existing instance-level state (single-caller behavior unchanged).Relevant Log Output
No crash — the bug is silent. Models receive the wrong prompt and produce responses for the wrong topic. Only detectable by inspecting model output content.
Operating System
macOS
Reproduction
consensusworkflows concurrently (e.g., via Claude Code Agent Teams spawning multiple sub-agents)modelsarrays and differentsteppromptsImpact
This makes
consensusunusable in any concurrent/parallel agent scenario — which is increasingly common with Claude Code Agent Teams, n8n workflows, and multi-agent orchestration patterns.