Back to Feed
article 3m read

Tool-call tags pulled out of the prompt, and the replacement join double-books

The first join between agent hook events and the transcript needed a tag inside the conversation. The fingerprint matcher that replaced it the same day leaves the prompt alone, but a start and its end now fight over one record.

Article DepthPractice

bar chart

The same command run twice

A worked example traced by hand through the matcher, not a measurement.

The join I built needed my coding agent's chat to carry a tag it shouldn't carry. The same day, I swapped it for one that can give a tool call's record to the wrong event.

Each time the agent runs a tool, I get two kinds of data. A hook sends one event when the call starts and one when it ends. The agent also writes its own log of the chat, and every tool call in that log has its own id. I wanted each event to point at the right line in the log.

A tag in the chat

The first try let the sender pick an event id and then searched the chat log for it. Most of the places it searched were text the model reads. I can't see the code that put the tag there, but the search expected to find it in the prompt, and the prompt is the wrong place for wiring. The model can repeat it or get confused by it. Worse, any log line with a valid-looking id could update any event, in any session. The next commit removed all of that.

A fingerprint and a queue

Now each tool event gets a fingerprint: a hash of the tool name plus its input, cleaned up so the same call always hashes the same way. When a session ends, the same hash runs over every tool call in the log. Then each event, oldest first, takes the oldest unused log line with a matching fingerprint.

With the same command run twice, there are two log lines and four events. The first start and end take them in order, so the start gets one line and the end gets the other, and the second start and end get nothing.

Two events, one line

A fingerprint says what kind of call it was, not which call. The same commit made end events carry the tool input too, when the payload includes it. When it does, a start and its end have the same fingerprint, which gives two claimants for one log line. Run the same command twice and the first call's end event grabs the second call's line, and the second call gets nothing.

A wrong link is worse than a missing one, because it looks right. Nothing stops it from happening again on a rerun either. The list of used lines lives only in memory, and the database doesn't require each log line to be used once. The sender also gives up fast, so one lost event shifts every later match.

If you build something like this:

  • pair each start with its end before matching
  • make the database reject a log line used twice
  • count events left without a link
  • test with the same command run twice

If I've read the matcher right, that last test should break it in a single run.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward