Skip to content

fix(sdk): tell a tool its call id, and let a debounce callback see user input - #35

Merged
matej21 merged 2 commits into
mainfrom
fix/tool-context-call-id
Sep 13, 2026
Merged

matej21 merged 2 commits into
mainfrom
fix/tool-context-call-id

Conversation

@matej21

@matej21 matej21 commented Sep 13, 2026 •

Copy link
Copy Markdown
Member

Problem

Agent.executeToolCall reads agentState before it emits tool_started, and the tool_completed / tool_failed reducers clear executingToolCall. So inside execute, context.agentState.executingToolCall is always undefined.

A tool that needs its own call id cannot get it. In a production consumer, a snapshot tool used this id to anchor "only what changed since your last capture" results. The memo was never written, so every repeat capture re-sent the full page into the agent's context. The feature had been shipped for weeks and never ran.

Change

  • ToolContext is now AgentContext & { toolCallId: ToolCallId }. executeToolCall sets it from the call it actually runs (after a beforeToolCall replace, that is the replaced call).
  • ToolContext is built in exactly one place, so this is additive for tools. Code that constructs a ToolContext itself (tests) must add the field.
  • Export the DebounceCallback, DebounceContext and DebounceDecision types from the package root, so a preset can type a custom debounceCallback.
  • DebounceContext gains hasPendingInput. messages / totalPending read the mailbox alone, but a user chat message arrives through the user-chat plugin's dequeue. A callback that waits for a child's report therefore could not see that the user had written, and held the message until its timeout. The agent already computed this flag before calling the callback; now it passes it on. waitForResponseDebounceCallback processes immediately when the flag is set.

Tests

  • New integration tests in agent-loop.integration.test.ts: two tool calls in one turn each see their own id, and a debounce callback sees a user message that is not in the mailbox. Both fail without their agent.ts line.
  • agent-config.test.ts: waitForResponseDebounceCallback processes a pending non-mailbox input immediately.
  • bun test packages/sdk/src: 1500 pass, 0 fail. bun run ts:build and bun run lint are clean.

I left out a test for the replace path: on main, a replace that changes the id stalls the turn. #31 fixes that (keep the tool_use identity across a beforeToolCall replace).

🤖 Generated with Claude Code

https://claude.ai/code/session_012cebkT9kA2XbNtbdLM3HFt

matej21 and others added 2 commits September 13, 2026 11:39
`Agent.executeToolCall` captures `agentState` before it emits `tool_started`,
and the reducer clears `executingToolCall` when a call completes. A tool that
read `context.agentState.executingToolCall?.toolCallId` therefore always got
`undefined`. Any tool that keys state on its own call id (for example, to
point a later result back at an earlier one in the conversation) silently
never did so.

`ToolContext` now carries `toolCallId` explicitly, set from the call that is
actually executed.

Also export the `DebounceCallback`, `DebounceContext` and `DebounceDecision`
types, so a preset can type its own `debounceCallback` without reaching into
`src/`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cebkT9kA2XbNtbdLM3HFt
`DebounceContext.messages` and `totalPending` read the mailbox alone. A user
chat message is delivered through the user-chat plugin's dequeue, so a
callback deciding to wait for a child agent's report could not see that the
user had written, and held the message until its own timeout ran out.

The context now carries `hasPendingInput`, the same check the agent already
makes before calling the callback, and `waitForResponseDebounceCallback`
processes immediately when it is set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cebkT9kA2XbNtbdLM3HFt
@matej21 matej21 changed the title fix(sdk): tell a tool which call it is executing fix(sdk): tell a tool its call id, and let a debounce callback see user input Sep 13, 2026
@matej21
matej21 merged commit 61d1130 into main Sep 13, 2026
1 check 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.

1 participant