Turns session/update notifications into OTel tool spans, for every runtime.
Fountain's claude-only stream tracer bridged one dialect into the turn
span; three of four runtimes produced no tool-level traces at all, and
nobody noticed because the gap is invisible from inside a conversation
(#637). ACP's tool_call / tool_call_update carry an id and a status for
every runtime, which is exactly what a tracer keys on — so this module is
dialect-free and one per protocol, not one per agent.
Span and attribute names carry a prefix, new/2's :prefix option,
"acp" by default. The names below are written with it.
Span mapping:
tool_callopens a<prefix>.tool_usechild span, keyed bytoolCallId, named by the same title-then-kind preference the render path uses (Blocks).tool_call_updatewith a terminal status closes the matching span;failedandcancelledmark it as an error. Non-terminal updates are progress, not an outcome, and touch nothing.agent_message_chunk/agent_thought_chunkaccumulate byte counts, surfaced atfinalize/1as<prefix>.text_bytes/<prefix>.thinking_byteson the turn span. Chunks are per-delta and a turn produces hundreds; one span event each — what the legacy tracer did per assistant message — would blow through OTel's default event limit on the first real turn.finalize/1closes any span still open asabandoned(the runtime exited or was interrupted before the matching update) and writes the accumulated totals.
What the legacy tracer had that this one drops, on purpose
Cost and token usage came from claude's proprietary result event. ACP's
session/prompt response carries a stop reason and, at protocol v1, an
unstable usage block that Managoat.ACP.Usage reads at turn end; the
tracer does not put it on the span — dropped explicitly rather than
silently (#637). If a dashboard needs it back, the source is the :done
report, not this module.
All functions no-op on nil, so the caller keeps a nil tracer for turns
that trace nothing, without branching. The OpenTelemetry API is the only
dependency: with no SDK started every span call is a no-op.
Summary
Functions
Close abandoned tool spans and write the accumulated totals to the turn span.
Feed one stored protocol line — the same ndjson the acp log stream holds.
Create a tracer attached to turn_span_ctx.
Types
Functions
@spec finalize(t() | nil) :: :ok
Close abandoned tool spans and write the accumulated totals to the turn span.
Call on every way a turn can end, before the turn span itself is ended.
Feed one stored protocol line — the same ndjson the acp log stream holds.
Anything that is not a session/update notification (responses, requests,
an adapter's stray non-JSON output) traces nothing.
Create a tracer attached to turn_span_ctx.
prefix: names the spans and attributes (<prefix>.tool_use,
<prefix>.tool_name, …); the default is "acp". A host with dashboards
built on another prefix passes its own.