You can't explain a tool choice you never traced

When an agent calls the wrong tool, the answer is in the trace: which tool, what arguments, what came back. Without standardized spans it's a guess. With them it's a query.

B

Balagei G Nagarajan

5 MIN READ


Agent failures are often impossible to even reproduce. Because the same input can produce a different run, agentic systems fail in ways you cannot replay, so without structured traces you debug by guessing (Beyond Black-Box Benchmarking: Observability of Agentic Systems, arXiv:2503.06745, 2025). When your production agent calls the wrong tool, the explanation is in a trace you either captured or lost.

A branching tree of luminous spans where one branch is highlighted, its attributes legible, while the surrounding raw log lines fade into an unreadable blur

Key facts.

  • OpenTelemetry's GenAI semantic conventions define an execute_tool span with standardized attributes including gen_ai.tool.name, gen_ai.tool.call.arguments, and gen_ai.tool.call.result, plus operation and model attributes, giving tool invocations one interoperable shape (OpenTelemetry GenAI agent and framework spans, 2025).
  • Span naming follows an operation-then-name pattern, for example execute_tool web_search, so GenAI-aware backends recognize and group tool calls automatically (OpenTelemetry GenAI spans, 2025).
  • Observability vendors now consume these conventions natively, so standardized GenAI spans flow into existing tracing without proprietary instrumentation (Datadog LLM Observability, native OTel GenAI support, 2025).

Why is a wrong tool call so hard to debug?

Without execute_tool spans, a wrong tool choice hides in logs; a more capable model leaves the same blind trace. (arXiv:2503.06745)

Because by default the decision leaves no structured trace. The model read a catalog of tool descriptions, chose one, and wrote arguments, and all you have afterward is a log line or a framework-specific blob. You cannot easily ask which tools were even in context, why a similar-sounding one was passed over, what arguments actually went out, or what the tool returned before the agent moved on. So you reproduce by hand and guess. The information existed at the moment of the call. The problem is that nothing captured it in a form you can query, which is the difference between an incident you close in minutes and one you stare at for an afternoon.

What do standardized tool spans give you?

A common shape for every tool decision. OpenTelemetry's GenAI conventions wrap each tool execution in an execute_tool span and attach named attributes: the tool name, the arguments sent, the result returned, the operation, the model (OpenTelemetry). Because the span is named execute_tool followed by the tool name, backends can group and chart tool calls without custom parsing (OpenTelemetry). The planning and reasoning spans are the parents, so you can walk from the agent decided X to it called tool Y with these arguments and got this back. That turns the three questions you always ask during a tool incident, which tool, what arguments, what response, into attributes you filter and aggregate across thousands of runs.

# One execute_tool span, standardized attributes
span: execute_tool create_invoice
 gen_ai.tool.name = "create_invoice"
 gen_ai.tool.call.arguments = { "customer_id":"cus_123", "amount_cents":4900 }
 gen_ai.tool.call.result = { "status":"error", "code":"missing_currency" }
 gen_ai.operation.name = "execute_tool"
# "every call that returned missing_currency" is now a query, not a grep.

How do you instrument tool calling without a rewrite?

Emit GenAI-convention spans from the layer that already runs your tools. Wrap each tool execution in an execute_tool span, set the tool name, sanitized arguments, and result, and let it nest under the agent and chat spans so the causal chain is intact. Because the conventions are standard, the spans flow into observability backends that support them natively, so you get tool-level traces inside the same system you already use for application performance (Datadog). Sanitize sensitive arguments at the span boundary, add a custom attribute for tool-selection rationale where your framework exposes it, and keep results attached so a failure shows what the tool actually returned. The payoff is that the next wrong tool call is something you query and explain, not something you reconstruct.

A span tree with a root agent span, a child chat span, and a highlighted execute_tool span whose standardized attributes, tool name, arguments, result, are listed, showing the parent-to-child causal chain from decision to execution

The same incident, two ways

Question during an incidentUnstructured logsexecute_tool spans
Which tool ran?Grep and hopegen_ai.tool.name attribute
What arguments went out?Often not loggedgen_ai.tool.call.arguments
What did the tool return?Scatteredgen_ai.tool.call.result
Why this tool over a similar one?UnknowableParent reasoning span + rationale attribute
Trend across runsManualFilter and aggregate spans

The pattern is that a tool decision you did not trace is a tool decision you cannot explain, and at production scale that gap is where wrong-tool incidents go to hide. Standardized execute_tool spans capture which tool, what arguments, and what result, under one shape every backend understands, so diagnosing a tool failure is a query rather than an excavation. Building that trace of every tool decision into the loop, so the boundary explains itself, is what VibeModel builds as the Pattern Intelligence Layer.

Frequently asked questions

Do I need a new observability vendor?
No. The GenAI conventions are standard, and major backends consume them natively, so the spans land in the tracing system you already run alongside your application traces.

Won't logging arguments leak sensitive data?
Sanitize at the span boundary: redact or hash sensitive fields before they are attached. You keep the shape of the call for debugging without storing the secret values.

What single attribute helps most?
The result. Capturing gen_ai.tool.call.result is what lets you find every call that failed the same way and explain a wrong-tool incident in one query.


Share this post

Join the discussion

Have a take, a war story, or a question? Sign in with GitHub to comment and react. Comments are powered by GitHub Discussions, ad-free and yours to moderate.

Continue Reading

Find where your agent breaks, before you build it

Faultmap maps where your agent will fail from the goal and your data, then hands you the first test suite it has to pass.