[SC-17274] Emit Atryum activity as OpenTelemetry traces - #144
Open
juanmleng wants to merge 6 commits into
Open
Conversation
Atryum now exports its activity over OTLP so any OTEL-native backend
(Langfuse, Datadog, Arize, ...) can consume it with no vendor-specific
code. Each backend is just an endpoint plus headers in config.
Span tree per request:
HTTP root (otelhttp) -> atryum.invocation -> atryum.ai_evaluation
-> chat {model}
Both ingestion paths open an atryum.invocation span: Invoke (MCP proxy)
and Submit (the external harness-hook twin, tagged atryum.external).
Each records the server/source, tool, agent id, matched rule and
resulting disposition.
The judge span follows the OTEL GenAI conventions (gen_ai.system,
gen_ai.request.model, gen_ai.usage.*) so Langfuse renders it as a
generation, plus atryum.judge.verdict/.confidence. Capturing token
counts required widening the OpenAI and Anthropic response structs,
which previously read only the message text and discarded usage.
New [otel] config section takes a list of [[otel.exporters]]; multiple
entries fan the same spans out to several backends at once. Langfuse
authenticates via a public/secret key pair (the pair selects the
project), other backends via raw headers. deployment.environment reuses
atryum's existing instance identity - atryum_instance, else
public_base_url - so a deployment self-tags with the environment it
serves; Datadog reads it as the env tag.
Disabled by default: when [otel].enabled is false the tracer provider
is a no-op and the instrumentation costs nothing.
Span tests share a single recorder because the OTEL global only wires
its delegate on the first SetTracerProvider call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The VM (ValidMind-hosted) evaluator recorded only verdict and confidence on the
ai_evaluation span, so its judge calls appeared in OTEL backends with no model,
tokens, latency, or content — unlike the local evaluator.
Widen EvaluateResponse with model, usage, latency_ms, prompt and completion
(returned by the backend evaluate endpoint). On the VM path, synthesize a
"chat {model}" gen_ai generation span, backdated by the reported latency and
carrying the same gen_ai.* attributes the local path already emits, so both
evaluators render identically in any OTEL backend.
Attach the judge prompt and completion as gen_ai.prompt / gen_ai.completion on
both paths, gated by [otel].capture_content (default on) so deployments can keep
charter text and tool arguments out of external telemetry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shorten the [otel] block, add the capture_content key with a note, and frame Langfuse/Datadog/Arize as examples rather than built-in providers. Replace the stale Langfuse host with a placeholder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Install an otel.SetErrorHandler so lazy-exporter runtime failures surface in Atryum's logs instead of the SDK's default stderr, and correct a copy-paste span name in the content-capture test message. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juanmleng
marked this pull request as ready for review
July 22, 2026 13:46
| trace.WithSpanKind(trace.SpanKindClient), | ||
| trace.WithTimestamp(start), | ||
| trace.WithAttributes( | ||
| attribute.String("gen_ai.system", "openai"), |
Contributor
There was a problem hiding this comment.
it's possible we could use something not from open ai in the future. Maybe we need a lookup table based off of model name?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Description
What and why?
Atryum now reports its activity — every agent tool-call it governs and every AI-judge decision — as OpenTelemetry (OTEL) traces. OTEL is the industry-standard observability format, so this activity can flow into any compatible tool (Langfuse, Datadog, LangSmith, and others) with no vendor-specific code in Atryum.
Why it matters: there was previously no straightforward way to see, in an observability tool, what Atryum's governance layer was doing — which tool calls agents attempted, how the AI judge ruled, and what each judgment cost in time and tokens.
Before: governance activity was not exported anywhere.
After: each governed invocation produces a single trace showing:
This works the same whether the judge runs inside Atryum (standalone / bring-your-own-key) or is hosted by the ValidMind backend — both paths render identically. Sending traces to a new backend is pure configuration (endpoint + credentials in
atryum.toml); the same traces can fan out to several backends at once.Configuration
Everything lives in one
[otel]section ofatryum.toml— no environmentvariables, no code changes. Off by default. Providers are not hardcoded: any
OTLP backend works, and Langfuse/Datadog below are just examples.
Turn it on (or off)
Add a provider — one
[[otel.exporters]]block per destination. Each is anOTLP endpoint plus how to authenticate:
Rule of thumb: auth goes in
headers;public_key/secret_keyis just ashortcut for Basic-auth backends. List several blocks to fan the same traces to
several tools at once. Restart Atryum — the startup log prints
otel tracing enabled.Keep sensitive text out (optional)
When off, model / tokens / latency / verdict still export, but the judge's prompt
and answer text (which contain your charter/policy and the agent's tool arguments)
do not.
That's the whole surface:
enabled, one or more[[otel.exporters]], andcapture_content.How it connects
Every governance decision Atryum makes is now emitted as a standard OpenTelemetry (OTEL) trace, so the same data can flow to any observability tool — we proved it reaching Langfuse and Datadog at the same
time, with no code change to add the second.
The shape of one trace
A single permission check produces one nested trace. Each layer is a "span":

Datadog
Langfuse
How to test
atryum.toml, enable[otel]and add an exporter pointing at any OTLP-capable backend (endpoint + credentials).otel tracing enabled.service:atryum: each invocation appears as a trace, and any AI-judged call shows achat {model}generation with model, tokens, latency, and (unless content capture is disabled) the prompt and verdict.Verified locally end-to-end against a real OTLP backend for both judge paths, including token counts, latency, and input/output.
What needs special review?
internal/invocation/service.go— the VM-path generation span is synthesized after the fact and backdated by the reported latency so its duration is the real LLM call time; confirm the timestamps and nesting underatryum.ai_evaluation.[otel].capture_content, default on) — confirm it fully suppresses prompt/completion when off while still emitting model/tokens/latency/verdict.Dependencies, breaking changes, and deployment notes
[otel]is enabled; when disabled the tracer is a no-op.capture_content = false, judge prompt/response text) to the configured backend.Data impact
Justification: Additive, opt-in telemetry plus a new optional config key (
[otel].capture_content). No existing configuration, stored data, or API response is modified; export is inert unless[otel]is enabled.Release notes
enhancement— Atryum can now emit its governance activity (tool calls and AI-judge decisions, including model, tokens, latency, and — optionally — prompt/response) as OpenTelemetry traces to any OTLP-compatible backend, configured per exporter inatryum.toml.Checklist