Skip to content

fix(agent): track tool call args per ToolCallID for parallel calls (#33)#34

Merged
ezynda3 merged 1 commit into
masterfrom
fix/33-parallel-tool-args
May 20, 2026
Merged

fix(agent): track tool call args per ToolCallID for parallel calls (#33)#34
ezynda3 merged 1 commit into
masterfrom
fix/33-parallel-tool-args

Conversation

@ezynda3
Copy link
Copy Markdown
Contributor

@ezynda3 ezynda3 commented May 20, 2026

Description

Agent.GenerateWithCallbacks stored the most recent tool call's args in a single shared currentToolArgs variable, captured by closure across all of its streaming callbacks. When a provider emitted multiple tool_use blocks in a single step (Anthropic Claude, OpenAI parallel tool calls, etc.), every OnToolCall overwrote that variable before any OnToolResult fired — so every result-side callback received the args of the last tool call, not its own. Downstream consumers (UI labels, log lines, trace span inputs) inherited the bug verbatim through ToolResultEvent.ParsedArgs.

The fix replaces the shared variable with a map[string]string keyed by ToolCallID, written in OnToolCall and read+deleted in OnToolResult. A sync.Mutex guards the map in case the streaming layer dispatches callbacks from multiple goroutines. The tc.Input already in OnToolCall is forwarded unchanged (its callbacks were never broken — only the result-side lookup was).

Before / after for a step with two parallel load_skill calls:

before: OnToolResult(kit-A, args={"name":"gmail_trigger"})   ← wrong
        OnToolResult(kit-B, args={"name":"gmail_trigger"})
after:  OnToolResult(kit-A, args={"name":"scheduled_jobs"})   ← correct
        OnToolResult(kit-B, args={"name":"gmail_trigger"})

Fixes #33

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes generate no new warnings (go vet ./..., golangci-lint run clean)
  • I have added tests that prove my fix is effective
  • New and existing unit tests pass locally with my changes (go test -race ./...)
  • I have made corresponding changes to the documentation (no public surface change)

Additional Information

  • Modified: internal/agent/agent.go — swap shared currentToolArgs for a per-ToolCallID map with a sync.Mutex; add sync import.
  • Added: internal/agent/agent_parallel_tool_args_test.go — regression test that injects a fake fantasy.Agent emitting two parallel OnToolCalls before either OnToolResult and asserts each callback receives its own args.
  • Backward compatibility: no public API changes. The toolArgs parameter delivered to ToolResultHandler / OnToolExecution is now correct for parallel calls; consumers that were already receiving the (incorrect) last-call's args will now receive the right value.
  • The test was verified to fail on master and pass on this branch.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed argument routing for parallel tool executions, ensuring each tool call receives its correct input parameters instead of becoming mixed or overwritten.

Review Change Stack

Previously GenerateWithCallbacks stored the most recent tool call's args
in a single shared variable, which got clobbered when a provider emitted
multiple tool_use blocks in a single step. Every OnToolResult callback
then received the args of the last OnToolCall, regardless of which call
it was actually resolving — breaking any downstream UI, log, or trace
that derived its description from the toolArgs parameter.

- Replace the shared currentToolArgs with a map keyed by ToolCallID,
  guarded by a sync.Mutex in case the streaming layer dispatches
  callbacks from multiple goroutines.
- Delete each entry in OnToolResult so the map cannot accumulate
  across steps.
- Add a regression test driving the streaming wrapper with a fake
  fantasy.Agent that emits two parallel tool calls before either
  result, asserting each callback sees its own args.

Fixes #33
@mark-iii-labs-huly
Copy link
Copy Markdown

Connected to Huly®: KIT-35

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b86943f-6cc4-47df-ac57-b5625f7fc288

📥 Commits

Reviewing files that changed from the base of the PR and between 592f8dc and 488b0ad.

📒 Files selected for processing (2)
  • internal/agent/agent.go
  • internal/agent/agent_parallel_tool_args_test.go

📝 Walkthrough

Walkthrough

This PR fixes a concurrency bug where parallel tool calls emitted in a single step all received the last call's arguments in their result callbacks. The fix replaces a single shared currentToolArgs variable with a per-ToolCallID map guarded by mutex, ensuring each parallel call's arguments are tracked and retrieved independently.

Changes

Per-ToolCallID Argument Tracking for Parallel Tool Calls

Layer / File(s) Summary
Per-ToolCallID argument tracking implementation
internal/agent/agent.go
Adds sync import; replaces single currentToolArgs string with map[ToolCallID]string plus sync.Mutex. OnToolCall callback records input per ToolCallID under lock; OnToolResult callback locks, retrieves, and deletes the matching args entry before invoking callbacks with the correct per-call arguments.
Regression test for parallel tool argument isolation
internal/agent/agent_parallel_tool_args_test.go
Introduces fakeParallelAgent helper that emits two parallel tool calls with distinct JSON inputs, then their results. Test TestGenerateWithCallbacks_ParallelToolArgs wires callbacks to capture per-call arguments and asserts that each tool (kit-A and kit-B) receives its own original input rather than the last call's arguments.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops through parallel dreams,
Where tool calls once clobbered each other's schemes,
Now each call holds dear its own precious args,
In a map guarded well by mutex bars,
No more shared confusion—each tool gets its due!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main fix: replacing a single shared variable with per-ID tracking to handle parallel tool calls.
Linked Issues check ✅ Passed The PR fully implements the suggested fix from issue #33: replaces single currentToolArgs with a per-ToolCallID map guarded by sync.Mutex, stores args in OnToolCall, retrieves and deletes in OnToolResult, and adds a regression test.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the parallel tool call args issue: map-based tracking in agent.go, sync import, and regression test in agent_parallel_tool_args_test.go.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/33-parallel-tool-args

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ezynda3
Copy link
Copy Markdown
Contributor Author

ezynda3 commented May 20, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ezynda3 ezynda3 merged commit bd24f33 into master May 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parallel tool calls share a single currentToolArgs in Agent.GenerateWithCallbacksOnToolResult reports last call's args for every result

1 participant