Skip to content

perf: complete multi-tool code-mode blocks in one exec turn instead of a wait per await#109290

Merged
steipete merged 1 commit into
mainfrom
claude/priceless-lumiere-3ed6f0
Jul 16, 2026
Merged

perf: complete multi-tool code-mode blocks in one exec turn instead of a wait per await#109290
steipete merged 1 commit into
mainfrom
claude/priceless-lumiere-3ed6f0

Conversation

@steipete

@steipete steipete commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Closes #109288

What Problem This Solves

Fixes an issue where code mode agents would spend one wait tool round-trip per awaited bridge tool call: a guest block awaiting N tools.search/describe/call operations took N+1 model turns, making code mode ~3x slower and 2-4.5x more token-expensive than direct tool exposure on identical live tasks (measured across OpenAI, Anthropic, and Google). It also fixes three issues found while stress-testing the same surface: cancelled exec/wait calls kept hostile guest code running until the interrupt deadline and parked orphaned snapshots in the process-global 64-slot table until TTL; the exec description lured models into probing an empty API/MCP surface on runs with no MCP tools; and API.list("mcp/") rejected the trailing-slash prefix models actually type.

Why This Change Was Made

Bridge tool calls run through the same policy-checked host executor whether the model awaits them one at a time or in a batch, so the host now resolves them inline within the exec deadline and resumes the QuickJS snapshot without a model turn - the contract namespace calls already had. That made the per-call budget the real boundary, so one shared wall-clock deadline now bounds the initial worker, inline drain rounds, and the resumed guest interrupt budget; a near-deadline settle parks the snapshot (recoverable via wait) instead of burning the remaining budget on VM restore; and abort terminates the worker, skips resume, and drops suspended runs. maxPendingToolCalls stays a per-batch concurrency cap and no longer doubles as a sequential-round cap.

User Impact

Agents running with tools.codeMode complete multi-tool code blocks in one exec turn instead of a wait per await: live A/B across OpenAI/Anthropic/Google shows ~45% fewer turns, ~45% fewer input tokens, and ~50% lower latency at equal task success. Cancelling a run now stops guest code promptly and cannot exhaust the shared suspended-run capacity. No config changes; waiting/wait semantics remain for yield_control() and calls that outlive the deadline.

Evidence

Live bench (real Agent runtime, 72-tool catalog with adversarial decoys, 4 validated tasks, code mode enabled, defaults):

provider code-mode turns before -> after input tokens before -> after latency before -> after success
openai gpt-5.4-mini 39 -> 22 28.5k -> 14.6k 94s -> 48s 4/4 -> equal (see A/B)
anthropic claude-sonnet-5 30 -> 18 (prompt-cached) 120s -> 56s 4/4 -> 4/4
google gemini-3-flash-preview 48 -> 30 107.7k -> 59.6k 115s -> 93s 4/4 -> 4/4

Success-rate A/B on the weakest model (gpt-5.4-mini, 20 runs per arm): before 14/20 pass, avg 7.9 turns; after 15/20 pass, avg 4.3 turns - equal success within noise, turns nearly halved. Final live sanity with the landed code: Anthropic 4/4, Google 4/4, OpenAI 3/4 (the one miss is the model answering without checking sensors, a failure mode present in the before arm too).

Deterministic proof: pnpm test src/agents/code-mode.test.ts src/agents/tool-search.test.ts src/agents/tool-search.stream-errors.test.ts test/scripts/mcp-code-mode-gateway-client.test.ts - 136 tests green on Blacksmith Testbox (lease tbx_01kxp6xtqf78ykjhtr0xryssjt) on the rebased head, including all replay-safe and abort-classification tests that landed on main meanwhile, plus new tests: five sequential tools.call awaits complete in a single exec with maxPendingToolCalls: 2; a pre-aborted exec fails fast with no parked run; a live abort terminates a hostile while(true) guest promptly; API.list("mcp/") equals API.list("mcp"); exec description drops/keeps MCP guidance by actual catalog content. pnpm tsgo and pnpm build green on the same Testbox; node scripts/report-test-temp-creations.mjs green locally (the delegated warning-only lane cannot diff origin/main...HEAD on the Testbox's shallow sync - infra, not this change).

Reviewed with autoreview (codex gpt-5.6-sol, xhigh) through seven cycles until clean; review-driven fixes included the shared deadline, abort-drop semantics, the resume-budget floor, and not misreading a missing catalog ref as an empty catalog. The change was then re-ported onto current main - which had meanwhile gained replaySafe threading and an aborted failure code - preserving replay-safe no-drain semantics and reusing the existing abort classification; a fresh autoreview pass on the ported diff was clean, and the live sanity re-ran green (Anthropic 4/4, Google 4/4, OpenAI within its measured flake band).

Code mode previously suspended to the model with a waiting result for every
awaited tools.search/describe/call, forcing one wait tool turn per await
(turns = tool calls + 1). Bridge calls now resolve inline within the exec
deadline and resume the QuickJS snapshot host-side, matching the namespace
auto-drain path. Restart-safe runs keep their existing no-drain semantics.
Live proof across OpenAI/Anthropic/Google: ~45% fewer turns and ~45% fewer
input tokens at equal success rate.

Also landed with the same contract:
- one shared wall-clock deadline per exec/wait call (initial worker, inline
  drain rounds, and resumed guest interrupt budget all share it)
- park the snapshot instead of resuming when the remaining budget could not
  survive VM restore, so near-deadline settles stay recoverable
- abort now terminates the guest worker, skips resume, and drops suspended
  runs instead of pinning process-global slots until TTL, reported with the
  existing aborted failure code
- exec description omits API.list/MCP/namespace guidance when the run catalog
  has no MCP tools or namespaces, so models stop probing an empty surface
- API.list tolerates a trailing-slash prefix like API.list("mcp/")
@steipete
steipete force-pushed the claude/priceless-lumiere-3ed6f0 branch from 877732f to 9e89830 Compare July 16, 2026 19:51
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation agents Agent runtime and tooling size: M maintainer Maintainer-authored PR labels Jul 16, 2026
@steipete
steipete merged commit b39e1cf into main Jul 16, 2026
328 of 337 checks passed
@steipete
steipete deleted the claude/priceless-lumiere-3ed6f0 branch July 16, 2026 20:30
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 17, 2026
openclaw#109290)

Code mode previously suspended to the model with a waiting result for every
awaited tools.search/describe/call, forcing one wait tool turn per await
(turns = tool calls + 1). Bridge calls now resolve inline within the exec
deadline and resume the QuickJS snapshot host-side, matching the namespace
auto-drain path. Restart-safe runs keep their existing no-drain semantics.
Live proof across OpenAI/Anthropic/Google: ~45% fewer turns and ~45% fewer
input tokens at equal success rate.

Also landed with the same contract:
- one shared wall-clock deadline per exec/wait call (initial worker, inline
  drain rounds, and resumed guest interrupt budget all share it)
- park the snapshot instead of resuming when the remaining budget could not
  survive VM restore, so near-deadline settles stay recoverable
- abort now terminates the guest worker, skips resume, and drops suspended
  runs instead of pinning process-global slots until TTL, reported with the
  existing aborted failure code
- exec description omits API.list/MCP/namespace guidance when the run catalog
  has no MCP tools or namespaces, so models stop probing an empty surface
- API.list tolerates a trailing-slash prefix like API.list("mcp/")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code mode forces one wait round-trip per awaited bridge tool call

1 participant