You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
MAF-Go emits no spans for tool calls. An agent that spends 91 seconds making 14 MCP calls is a single opaque box in the trace. You can see that it was slow. You cannot see which tool, how many, or whether one call ate most of the time — which is the only question you have when you open a trace.
The OpenTelemetry GenAI semantic conventions define this operation, and the constant is already in the semconv package this repo imports:
That immediately shows the tool results are 5-20KB each and are re-sent on every turn, which is exactly why the input-token count is high — a conclusion that is unreachable from the agent-level span alone.
Why this belongs in the framework, not in user code
I implemented it as a tool.Tool decorator that wraps Call. It works, but it has the classic footgun: you must remember to wrap every toolkit, and a tool you forget emits nothing, silently. A missing span is indistinguishable from a tool that was never called.
The framework already owns the tool-invocation path and could open the span there, once, for everyone. That is the difference between instrumentation that is on by default and instrumentation that is on when you remembered.
Suggested shape
Wherever the agent loop invokes a tool.FuncTool, wrap the call:
span name: "execute_tool <name>"
gen_ai.operation.name = execute_tool (semconv.GenAIOperationNameExecuteTool)
gen_ai.tool.name = <name> (semconv.GenAIToolName)
error status on failure
Two notes from doing it in anger:
Record result SIZE, not result content. Tool results routinely contain user data (a wiki page, a DB row). Putting bodies in spans turns a trace file into an accidental data-exfiltration surface. Size answers the question that matters at trace level.
I have a working implementation and am happy to contribute it, but the placement is a design call that is yours — say where you would want it and I will open a PR against that shape rather than guessing.
Summary
MAF-Go emits no spans for tool calls. An agent that spends 91 seconds making 14 MCP calls is a single opaque box in the trace. You can see that it was slow. You cannot see which tool, how many, or whether one call ate most of the time — which is the only question you have when you open a trace.
The OpenTelemetry GenAI semantic conventions define this operation, and the constant is already in the
semconvpackage this repo imports:What a traced run looks like today
Only
invoke_agentspans exist, one per agent:What it looks like with tool spans (implemented locally as a wrapper)
That immediately shows the tool results are 5-20KB each and are re-sent on every turn, which is exactly why the input-token count is high — a conclusion that is unreachable from the agent-level span alone.
Why this belongs in the framework, not in user code
I implemented it as a
tool.Tooldecorator that wrapsCall. It works, but it has the classic footgun: you must remember to wrap every toolkit, and a tool you forget emits nothing, silently. A missing span is indistinguishable from a tool that was never called.The framework already owns the tool-invocation path and could open the span there, once, for everyone. That is the difference between instrumentation that is on by default and instrumentation that is on when you remembered.
Suggested shape
Wherever the agent loop invokes a
tool.FuncTool, wrap the call:Two notes from doing it in anger:
Record result SIZE, not result content. Tool results routinely contain user data (a wiki page, a DB row). Putting bodies in spans turns a trace file into an accidental data-exfiltration surface. Size answers the question that matters at trace level.
An MCP tool can fail while returning no Go error — see mcptool: CallToolResult.IsError is never checked — a refused tool call returns err == nil and the agent believes it succeeded #492. A tool span that only watches the Go error will paint a refused call green, which is worse than no span at all. If tool spans land, they should also respect
CallToolResult.IsError. The two issues are related and I would suggest fixing mcptool: CallToolResult.IsError is never checked — a refused tool call returns err == nil and the agent believes it succeeded #492 first.I have a working implementation and am happy to contribute it, but the placement is a design call that is yours — say where you would want it and I will open a PR against that shape rather than guessing.