From ae976e71593f117883a519514a1934362688275b Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 13 May 2026 18:05:39 +0000 Subject: [PATCH] parallel tool execution: enable opencode `batch` + nudge agents to parallelize (#719) opencode: opt into `experimental.batch_tool` (anomalyco/opencode#2983) so the `batch` tool registers and the model can bundle 1-25 independent calls into one round trip. edit calls are excluded upstream. instructions.ts: add a "Parallel tool execution" section to the SYSTEM Workflow block, agent-specialized via ctx.agentId. uses Anthropic's canonical wording ("invoke all relevant tools simultaneously...") so Claude reliably emits multiple tool_use blocks per message; tells OpenCode about the new `batch` affordance. verified end-to-end against haiku-class models (sonnet for claude, default for opencode) with a "read 3 files and report first lines" fixture. results: - opencode used `batch` with 3 nested reads AND emitted 3 native parallel read calls in the same assistant turn - claude went from 3 serial turns (1 read each) to 1 message with 3 parallel Read tool_use blocks --- agents/opencode.ts | 10 ++++++++++ utils/instructions.ts | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/agents/opencode.ts b/agents/opencode.ts index abce0bf..f6e3692 100644 --- a/agents/opencode.ts +++ b/agents/opencode.ts @@ -65,6 +65,7 @@ type OpenCodeConfig = { permission?: Record; provider?: Record; agent?: Record; + experimental?: Record; model?: string; enabled_providers?: string[]; [key: string]: unknown; @@ -120,6 +121,15 @@ function buildSecurityConfig(ctx: AgentRunContext, model: string | undefined): s [pullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }, }, agent: buildReviewerAgentConfig(), + // opt into opencode's experimental `batch` tool (added in + // anomalyco/opencode PR #2983, opt-in via `experimental.batch_tool`). it + // exposes a single `batch` tool that runs 1-25 independent tool calls + // (read/grep/glob/bash/etc.) concurrently in one assistant turn, which + // collapses the dominant grep→20×read pattern into a single round trip. + // edits are explicitly disallowed inside the batch upstream. paired with + // the "Parallel tool execution" guidance in utils/instructions.ts so the + // model actually reaches for it. see wiki/prompt.md. + experimental: { batch_tool: true }, provider: { google: { models: Object.fromEntries( diff --git a/utils/instructions.ts b/utils/instructions.ts index 07a48fc..c7b6419 100644 --- a/utils/instructions.ts +++ b/utils/instructions.ts @@ -283,6 +283,21 @@ ${getStandaloneModeInstructions(ctx.payload.event.trigger, t, ctx.outputSchema)} Trust the tools — do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. Exception: right before \`${t("push_branch")}\`, ensure the working tree is clean — that tool rejects dirty trees, and tests you ran earlier often leave untracked output. +### Parallel tool execution + +For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously in a single assistant turn rather than sequentially. The dominant failure mode is grep → read → read → read → read across separate turns when one round trip would do. Always parallelize when calls are independent: +- reading multiple files (especially after a grep returns candidates) +- multiple greps with different patterns +- glob + grep + read combos +- listing multiple directories +- inspecting multiple MCP tools or resources + +Do NOT parallelize operations that depend on prior output (e.g. create a file then read it), or ordered stateful mutations. Edits are not parallelizable — sequence those normally.${ + ctx.agentId === "opencode" + ? `\n\nOn OpenCode you also have a \`batch\` tool that bundles 1-25 independent calls into one wrapper call. Reach for it whenever you have >=2 independent calls. Native parallel tool_use and \`batch\` both achieve one round trip instead of N — use whichever your provider supports best.` + : `\n\nEmit multiple \`tool_use\` blocks in the same assistant message for independent calls — the runtime executes them concurrently. Do not wait for one tool result before issuing the next independent call.` + } + ### Command execution Never use \`sleep\` to wait for commands to complete. Commands run synchronously — when the shell tool returns, the command has finished.