Skip to content

Hook input has no identifier grouping tool calls within one assistant response #618

Description

@stig

What I'm trying to do

Enforce "one git write per assistant response" with a preToolCall hook. Tool calls within a single response run concurrently, so a git write batched with any other git command contends on index.lock, and a read-back issued alongside its write can report pre-write state. Both fail silently, which is what makes a hook the right tool: the failure is invisible in the transcript, so a rule in the system prompt doesn't survive contact with an instruction to batch independent calls.

The gap

To decide whether two calls belong to the same response, the hook needs to know which response each came from. Nothing in the input says.

Documented chat-scoped input gives chat_id, session_id, agent, behavior, full_model, variant, and preToolCall adds tool_name, server, tool_input, tool_call_id, approval. tool_call_id is per call, chat_id is per conversation. There is no identifier at the granularity in between.

Why a timestamp heuristic doesn't substitute

Correction, see the comment below. The 3562ms row in this table was not a measurement and is retracted. The real picture is worse for heuristics, not better: same-response gaps reach 10717ms, because the gap includes however long the model spent emitting the second call. The two distributions overlap completely rather than nearly touching, so no threshold exists at all.

I tried a time window, on the assumption that calls within one response dispatch milliseconds apart while consecutive responses are separated by an LLM round-trip. Measured gaps between consecutive preToolCall firings, one session:

calls same response gap
two eca__git reads yes 62ms
two eca__shell_command yes 64ms
git commit then git log yes 3562ms
assorted no 5008ms, 6628ms, 9007ms

Same-response gaps span 62ms to 3562ms and cross-response gaps start at 5008ms, so the distributions nearly touch. Any threshold either misses real batches or blocks the first call of the next response. I confirmed the concurrency is real rather than an artefact: two eca__shell_command calls each running sleep 3 overlapped for the full three seconds.

Proposal

Add a per-response identifier to chat-scoped hook input, something like request_id or turn_id, stable across every hook firing within one assistant response and different for the next. A hook can then group calls exactly, with no heuristic.

This would also give tool hooks a reliable key for per-turn state. agent distinguishes a subagent from the primary agent, but not one of its turns from the next, so any hook keeping state across calls currently has the same problem.

Alternative considered

read-chat can reach the chat history from a hook, but it runs synchronously inside a 30s budget on every matching tool call, which is a lot of work to answer "which response is this". A field in the payload is far cheaper.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions