Skip to content

Proxy emits at most one tool call per turn: parallel tool calls are structurally impossible #123

Description

@griffinmartin

Symptom

Asked a cursor-acp/* model to dispatch three subagents in parallel in a single message, one task call each. One dispatched; the other two were silently abandoned — no error, no retry, just absent from the turn.

> build · cursor-grok-4.5-high-fast
✓ General Task — Report today's date

subagent_type            result
explore (file count)     not dispatched
general (today's date)   succeeded
explore (git branch)     not dispatched

Root cause

The proxy response builders accept a single tool call and wrap it in a one-element array. src/proxy/tool-loop.ts:

export function createToolCallCompletionResponse(meta: ToolLoopMeta, toolCall: OpenAiToolCall) {   // :199
  ...
  tool_calls: [toolCall],        // :211
  finish_reason: "tool_calls",
}

export function createToolCallStreamChunks(meta: ToolLoopMeta, toolCall: OpenAiToolCall) {          // :219
  ...
  tool_calls: [{ index: 0, ...toolCall }],   // :230, index hardcoded
}

Those are the only sites in src/ that emit tool_calls — every other occurrence (src/plugin.ts, src/proxy/incremental-prompt.ts) reads inbound calls. So the OpenAI-compatible response this proxy produces structurally cannot carry more than one tool call per assistant turn.

The bridge-JSON envelope reinforces it from the prompt side — src/proxy/bridge-json.ts:15 and :21 both instruct "respond with exactly one JSON object and no prose", and extractBridgeToolCallFromStreamOutput returns on the first detected call.

Impact

Any fan-out workflow is capped at one dispatch per turn. Best case it degrades to serial (dispatch, await result, dispatch again); observed case is worse — the model emits one and drops the rest, so a skill that fans out N reviewers silently does 1/N of its work with no error surfaced.

This affects agent workflows that dispatch multiple subagents concurrently, and more generally any turn where the model wants two or more parallel tool calls (a common pattern for independent reads/greps).

Repro

  1. Configure a cursor-acp/* model as the main model.
  2. Prompt: dispatch three subagents in parallel via the task tool, issuing all three calls in one message.
  3. Observe one dispatch; the rest are absent.

Notes

Distinct from #122 (subagentType argument normalization), which fixes the schema mismatch on a single task call. With that patch applied the surviving call validates correctly — the missing parallelism here is a separate, structural limit.

Filing rather than patching because plumbing N calls through looks like it touches the response builders, the stream chunk indices (index is fixed at 0), and the cursor-agent event to tool-call conversion — a bigger change than I want to guess at. Happy to attempt it if you can say which shape you would prefer.

Environment: 2.5.4, main @ 013ff8e, cursor-agent 2026.08.04-aaa8809, macOS.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions