LLMOps: Instrumenting Your Multi-Agent Stack for Production
LLMOps: Instrumenting Your Multi-Agent Stack for Production When your agent calls a tool that calls another agent that calls an LLM and something breaks, where do you look?
Primary Focus
ai developmentAI Tools Covered
What You'll Learn
- ✓The Observability Gap in Agentic Systems
- ✓Why Standard Logging Fails for Agents
- ✓What Good Observability Looks Like
- ✓The Alerting Gap
- ✓Tracing Agent Calls with OpenTelemetry
- ✓Core Concepts: Traces, Spans, Baggage
Guide Curriculum
The Observability Gap in Agentic Systems
Learn key concepts
- •The Observability Gap in Agentic Systems10m
- •Why Standard Logging Fails for Agents10m
- •What Good Observability Looks Like10m
- •The Alerting Gap10m
Tracing Agent Calls with OpenTelemetry
Learn key concepts
- •Tracing Agent Calls with OpenTelemetry10m
- •Core Concepts: Traces, Spans, Baggage10m
- •Instrumenting an Agent Run10m
- •Propagating Context Across Sub-Agent Boundaries10m
- •Semantic Conventions for LLM Spans10m
Tools: Langfuse, Braintrust, and Weights & Biases
Learn key concepts
- •Tools: Langfuse, Braintrust, and Weights & Biases10m
- •Langfuse: Open Source, Self-Hostable, Framework-Agnostic10m
- •Braintrust: Eval-Integrated, Production Feedback Loop10m
- •Weights & Biases (W&B): ML-Native, Experiment Tracking First10m
- •Decision Framework10m
Alerting on Agent Failures: Latency, Cost, and Correctness
Learn key concepts
- •Alerting on Agent Failures: Latency, Cost, and Correctness10m
- •Alert Category 1: Latency Anomalies10m
- •Alert Category 2: Cost Anomalies10m
- •Alert Category 3: Correctness (The Hard One)10m
- •The Alert Hierarchy10m
Preview: First Lesson
The Observability Gap in Agentic Systems
The Observability Gap in Agentic Systems
The Observability Gap in Agentic Systems
Traditional software observability is built on a simple model: a request comes in, some deterministic code runs, a response goes out. You log inputs and outputs, track latency, alert on errors. This model breaks when you introduce agents.
Start learning with this comprehensive guide
This guide includes:
About the Author
Hiram Clark is the founder and managing editor of vybecoding.ai and sets editorial direction for the guides and news published here. Articles are drafted with AI assistance and edited before publication. He works hands-on with the AI development tools, workflows, and infrastructure covered on the site.
Full Guide Content
Complete lesson text — start the interactive course above for exercises and progress tracking.
Module 1The Observability Gap in Agentic Systems
1.1The Observability Gap in Agentic Systems
The Observability Gap in Agentic Systems
Traditional software observability is built on a simple model: a request comes in, some deterministic code runs, a response goes out. You log inputs and outputs, track latency, alert on errors. This model breaks when you introduce agents.
1.2Why Standard Logging Fails for Agents
Agentic systems have four properties that make conventional logging insufficient:
1. Non-deterministic control flow. An agent decides at runtime which tools to call, in what order, how many times. Your logs might show 3 tool calls for one user request and 11 for another that looks identical from the outside. Without tracing the agent's reasoning chain, you can't tell whether 11 calls is correct behavior or a loop. 2. Cascading LLM calls. A single user-visible action might trigger an LLM call that triggers a tool call that triggers a sub-agent that makes two more LLM calls. Each step has its own latency, cost, and potential failure mode. Log lines from each step are scattered across time, with no shared identifier linking them. 3. The chain-of-custody problem. When something goes wrong in step 7 of a 12-step agent run, you need to know: what was the full context the agent had at step 7? What did it see? What did it decide? A stack trace tells you where the code failed. It says nothing about why the agent made the choices it made up to that point. 4. Cost is a first-class signal. In traditional software, an expensive request is a performance problem. In agentic systems, cost and correctness are coupled — a loop that's racking up token costs is often also a correctness failure. You can't separate cost monitoring from quality monitoring.1.3What Good Observability Looks Like
For an agentic system, you need three layers:
Trace layer: Every agent run produces a single trace with a root span. Every LLM call, tool invocation, sub-agent handoff, and retry is a child span linked to that root. You can reconstruct the full execution path for any run. Semantic layer: Spans carry not just timing but meaning — which model was called, what was the prompt, what was the response, what was the token count, what was the decision. This is what turns a timing diagram into an explanation. Eval layer: A subset of production traces are scored — by LLM-as-judge, by deterministic checks, or by human review — so you know not just whether the agent finished, but whether it finished correctly.As of 2026, the industry has largely converged on OpenTelemetry as the portable instrumentation standard for the trace layer. The semantic conventions for LLM spans are stabilizing, and every major LLMOps platform (Langfuse, Braintrust, Arize Phoenix) ingests OTEL traces natively. You instrument once and route to any backend.
1.4The Alerting Gap
Most teams alert on exceptions and HTTP 5xx errors. In agentic systems, the real failures are often silent:
- An agent that completes successfully but gives the wrong answer
- An agent that takes 45 seconds instead of 4 because it got into a retry loop
- An agent that's routing all requests to Opus when most could run on Haiku
- A cache hit rate that dropped from 80% to 20% after a prompt change
None of these show up as errors. They show up as cost spikes, latency degradation, and eventually as user complaints. Module 4 covers what to actually alert on.
Module 2Tracing Agent Calls with OpenTelemetry
2.1Tracing Agent Calls with OpenTelemetry
Tracing Agent Calls with OpenTelemetry
OpenTelemetry (OTEL) is the CNCF standard for distributed tracing. It's the right choice for LLMOps instrumentation because it's backend-agnostic — you write instrumentation once and export to Langfuse, Braintrust, Jaeger, Datadog, or any other OTEL-compatible backend. No vendor lock-in at the instrumentation layer.
2.2Core Concepts: Traces, Spans, Baggage
- The overall agent run (root span)
- Each LLM call
- Each tool invocation
- Each sub-agent handoff
- Retries (as child spans of the failed span)
2.3Instrumenting an Agent Run
from opentelemetry import trace
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
tracer = trace.get_tracer("my-agent")
def run_agent(user_message: str, user_id: str, feature: str) -> str:
with tracer.start_as_current_span("agent.run") as root_span:
root_span.set_attributes({
"agent.user_id": user_id,
"agent.feature": feature,
"agent.input_length": len(user_message),
})
result = agent_loop(user_message)
root_span.set_attributes({
"agent.output_length": len(result),
"agent.steps_taken": agent_step_count,
})
return result
def call_llm(prompt: str, model: str) -> dict:
with tracer.start_as_current_span("llm.call") as span:
span.set_attributes({
"llm.model": model,
"llm.input_tokens": count_tokens(prompt),
"llm.request_type": "chat",
})
response = client.messages.create(
model=model, max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
span.set_attributes({
"llm.output_tokens": response.usage.output_tokens,
"llm.cache_read_tokens": getattr(response.usage, 'cache_read_input_tokens', 0),
"llm.stop_reason": response.stop_reason,
})
return response
def invoke_tool(tool_name: str, tool_input: dict) -> dict:
with tracer.start_as_current_span("tool.call") as span:
span.set_attributes({
"tool.name": tool_name,
"tool.input_size": len(str(tool_input)),
})
result = execute_tool(tool_name, tool_input)
span.set_attributes({
"tool.result_size": len(str(result)),
"tool.success": result.get("success", True),
})
return result2.4Propagating Context Across Sub-Agent Boundaries
When your orchestrator calls a sub-agent over HTTP (or A2A), inject the trace context into the request headers so the sub-agent's spans attach to the parent trace:
# Orchestrator: inject context into outgoing request
from opentelemetry.propagate import inject
headers = {}
inject(headers) # adds traceparent, tracestate headers
response = httpx.post(specialist_agent_url, json=payload, headers=headers)
# Sub-agent: extract and continue the trace
from opentelemetry.propagate import extract
def handle_request(request):
ctx = extract(request.headers)
with tracer.start_as_current_span("sub-agent.run", context=ctx):
# this span links to the orchestrator's trace
...
This is what gives you end-to-end visibility across a multi-agent call tree — every span, from every agent, in one trace.
2.5Semantic Conventions for LLM Spans
The OpenTelemetry community is standardizing LLM span attributes under the gen_ai.* namespace. Key attributes to follow:
gen_ai.system— the provider (e.g.anthropic)gen_ai.request.model— the requested modelgen_ai.response.model— the actual model usedgen_ai.usage.input_tokens/gen_ai.usage.output_tokensgen_ai.operation.name—chat,text_completion,embeddings
Using standard attribute names means your traces are immediately readable by any OTEL-native dashboard without custom parsing.
Module 3Tools: Langfuse, Braintrust, and Weights & Biases
3.1Tools: Langfuse, Braintrust, and Weights & Biases
Tools: Langfuse, Braintrust, and Weights & Biases
Once you're emitting OTEL traces, you need a backend to store, visualize, and evaluate them. Three platforms dominate indie dev and small team usage in 2026. The right choice depends on your priorities, not brand preference.
3.2Langfuse: Open Source, Self-Hostable, Framework-Agnostic
Langfuse is MIT-licensed, has 19,000+ GitHub stars, and can be self-hosted with a single Docker Compose file. It ingests OTEL natively and also provides its own SDK with a @observe decorator:
from langfuse.decorators import observe, langfuse_context
@observe() # automatically traces this function as a span
def run_agent(user_message: str) -> str:
langfuse_context.update_current_observation(
input=user_message,
metadata={"user_tier": "pro"}
)
result = agent_loop(user_message)
langfuse_context.update_current_observation(output=result)
return result
Langfuse's SQL export via BigQuery integration lets data science teams run custom analysis on trace data — cost breakdowns by feature, quality trends over time, outlier detection.
Pricing: Free to self-host. Cloud starts at $29/month. Trade-offs: The UI is less polished than Braintrust. Evaluation workflow requires more manual setup.3.3Braintrust: Eval-Integrated, Production Feedback Loop
Braintrust's distinctive feature is closing the production-to-test loop: production traces automatically become test cases. When a user-facing interaction fails (or is flagged by LLM-as-judge), it lands in your eval dataset. Your next deployment is benchmarked against it automatically.
Integration via SDK, OTEL, or the AI Proxy (a drop-in proxy layer that intercepts LLM calls with zero code changes):
import braintrust
# AI Proxy: just change the base URL, zero other changes
client = anthropic.Anthropic(
base_url="https://braintrustproxy.com/v1",
api_key=braintrust.api_key()
)
# Every subsequent call is automatically logged and traceable
Pricing: Free (1M spans/month). Pro at $249/month.
Trade-offs: Not self-hostable. Production data goes to Braintrust's cloud.3.4Weights & Biases (W&B): ML-Native, Experiment Tracking First
W&B added LLM tracing to Weave, its data management platform. If you're already using W&B for training runs and model evaluation, Weave integrates naturally.
import weave
weave.init("my-agent-project")
@weave.op() # traces this function
def call_agent(prompt: str) -> str:
return run_agent(prompt)
Trade-offs: More opinionated toward ML workflows than pure LLMOps. Less agent-specific tooling than Langfuse or Braintrust.3.5Decision Framework
| Priority | Tool |
|---|---|
| Self-host / data residency | Langfuse |
| Eval-regression loop | Braintrust |
| Existing W&B / ML stack | Weights & Biases |
| Framework-agnostic, OTEL-first | Langfuse |
| Minimal setup, proxy integration | Braintrust |
| Budget: $0 cloud | Langfuse (self-host) |
For new projects with no existing tooling: start with Langfuse self-hosted. The OTEL-native instrumentation you write works with any backend, so switching costs are low if your needs change.
Module 4Alerting on Agent Failures: Latency, Cost, and Correctness
4.1Alerting on Agent Failures: Latency, Cost, and Correctness
Alerting on Agent Failures: Latency, Cost, and Correctness
Alerting on agentic systems is different from alerting on APIs. The failure modes are subtler, the signals are distributed, and "error rate" as a metric misses most of what actually goes wrong. Here's what to actually alert on.
4.2Alert Category 1: Latency Anomalies
Agent latency is bimodal by design — fast runs (happy path) and slow runs (retries, tool failures, long reasoning chains). The metric you want is p99 latency by feature, not mean latency.
Specific alerts worth setting:
- Step count outlier: Alert when an agent run exceeds 2x the median step count for that task type. This catches infinite loops and pathological retry behavior before they drain cost budgets.
- Total run duration > threshold by task type: Different tasks have different expected durations. A research task at 90 seconds is normal; a classification task at 90 seconds is a bug.
- Sub-agent timeout rate: If your A2A sub-agents are timing out more than 1% of the time, something upstream is broken — wrong input format, overloaded specialist, auth failure.
4.3Alert Category 2: Cost Anomalies
Cost alerts are your circuit breaker for agentic systems. Three specific patterns:
Token burn rate by model: Alert when Opus token consumption over a rolling 1-hour window exceeds 2x the 7-day average. A routing bug or a runaway loop shows up here within minutes. Output token outliers per feature: If your support agent's p95 output token count doubles overnight, something changed — either in the prompt, the model, or user behavior. Track output token p95 per feature with a 10% deviation alert. Cost per successful outcome: If your agent completes 1,000 tasks at $0.03/task, and that drifts to $0.09/task over two weeks, you've accumulated technical debt in your prompts or routing logic. Track this metric even if your absolute spend stays flat.4.4Alert Category 3: Correctness (The Hard One)
Latency and cost are measurable from observability data. Correctness requires evaluation. Three practical approaches, from lightest to heaviest:
Structural validation (free): For agents that return structured output, validate the schema on every response.from jsonschema import validate, ValidationError
def validate_agent_output(output: dict, schema: dict) -> bool:
try:
validate(instance=output, schema=schema)
return True
except ValidationError as e:
metrics.increment("agent.output.schema_error", tags={"field": e.path})
return False
LLM-as-judge sampling (medium cost): Run a fast LLM judge (Haiku 4.5) over a sample of production traces — say, 5% of volume — to score output quality:
def score_output(input_text: str, output_text: str) -> float:
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=10,
system="Score this agent output 0.0-1.0 for correctness and helpfulness. Respond with only the number.",
messages=[{"role": "user", "content": f"Input: {input_text}\n\nOutput: {output_text}"}]
)
return float(response.content[0].text.strip())
Human review queue (ground truth): Route flagged traces to a human review queue. This is your ground truth signal for evaluating whether your LLM judge is itself calibrated correctly.4.5The Alert Hierarchy
- Agent error rate above 10% in a 5-minute window
- Token burn rate 5x daily average (loop or routing catastrophe)
- Complete sub-agent unavailability (100% timeout rate)
- Cost per outcome drifting 25% over 7-day baseline
- Cache hit rate below 40% (prompt change broke caching)
- p99 latency above 2x baseline for any feature
- LLM judge quality scores by feature and model
- Model distribution shift (requests migrating up-tier unexpectedly?)
- Schema validation failure rate trends
The goal isn't to be paged on everything — it's to catch the class of failures that are invisible to error-rate monitoring but visible to users and your finance team.