Back openDesk Edu for a sovereign, open-source education — every vote counts.
Vote nowSave products you love by clicking the heart icon.
Infrastructure as Code (IaC) is powerful but error-prone. This article benchmarks Large Language Models on their ability to generate secure Terraform configurations, addressing the critical question: can AI generate production-ready infrastructure that doesn't compromise security?
When your application can make its own decisions, traditional monitoring isn't enough. Welcome to the era of AI-native observability.
DevOps has always been about understanding system state. Traditional applications are relatively straightforward: request → process → response. The three pillars of observability — metrics, logs, traces — have served us well.
But agentic systems are fundamentally different. They:
This creates an observability gap — our traditional tools can't easily answer questions like:
Agentic systems require an evolution of observability. We need to extend the three pillars and add new dimensions:
Traditional Observability Agentic Observability
──────────────────────── ────────────────────────
┌─────────────────┐ ┌───────────────────────────┐
│ METRICS │ │ METRICS │
│ - Request rate │ │ - Token usage │
│ - Error rate │ │ - Inference latency │
│ - Latency │ │ - Confidence scores │
│ │ │ - Decision metrics │
└─────────────────┘ │ - Stateful resource │
▲ │ usage │
│ └───────────────────────────┘
│ ▲
│ │
├─── Traditional ├─── Still important
│ │
┌─────────────────┐ ┌───────────────────────────┐
│ LOGS │ │ LOGS │
│ - stdout │◀─────────────│ - LLM prompts │
│ - stderr │ │ - LLM completions │
│ - Application │ │ - Agent decisions │
│ events │ │ - Tool calls │
└─────────────────┘ │ - Context snapshots │
▲ │ - Knowledge retrievals │
│ └───────────────────────────┘
│ ▲
│ │
└─────── Traditional ├─── CRITICAL for agents
issues logs
┌─────────────────┐ ┌───────────────────────────┐
│ TRACES │ │ TRACES + REASONING │
│ - Request flow │◀─────────────│ - End-to-end agent │
│ - Service │ │ execution flow │
│ dependencies │ │ - LLM reasoning chains │
└─────────────────┘ │ - Tool call chains │
│ - Decision trees │
│ - Cross-agent traces │
└───────────────────────────┘
▲ ▲
│ │
└──────────────────────────────┘
STILL IMPORTANT
NEW DIMENSIONS
┌───────────────────────────┐
│ CONTEXT │
│ - Current agent state │
│ - Memory contents │
│ - Knowledge graph │
│ snapshots │
└───────────────────────────┘
┌───────────────────────────┐
│ EVALUATIONS │
│ - Human feedback │
│ - Automated quality │
│ scoring │
│ - Safety classifications │
└───────────────────────────┘
For agentic systems, context is the fourth pillar of observability. Context includes:
Without context, you can't explain why an agent made a particular decision. Consider this scenario:
Question: "Why did the agent recommend Product A instead of Product B?"
With Context:
Without Context:
class AgentContextTracker:
def __init__(self):
self.event_log = []
self.current_state = {}
self.memory_snapshots = []
def capture_state(self, agent):
"""Capture current agent state for observability."""
snapshot = {
"timestamp": time.time(),
"step": agent.current_step,
"goal": agent.current_goal,
"context": agent.context,
"memory_hash": hash(agent.memory),
"active_tools": agent.active_tools,
}
self.memory_snapshots.append(snapshot)
return snapshot
def capture_event(self, event_type, data):
"""Capture a significant event."""
event = {
"timestamp": time.time(),
"type": event_type,
"data": data,
"step": self.current_state.get("step"),
}
self.event_log.append(event)
return event
def capture_llm_call(self, prompt, completion, model, tokens, latency):
"""Capture LLM interaction details."""
return self.capture_event("llm_call", {
"prompt": prompt[:500] + "...", # Truncate for storage
"completion": completion[:500] + "...",
"model": model,
"input_tokens": tokens.get("input"),
"output_tokens": tokens.get("output"),
"latency_ms": latency,
"temperature": model_config.get("temperature"),
})
def capture_tool_call(self, tool_name, arguments, result, duration):
"""Capture tool usage."""
return self.capture_event("tool_call", {
"tool": tool_name,
"arguments": arguments,
"result_summary": str(result)[:500] + "...",
"duration_ms": duration,
"status": "success" if result else "failure",
})
def capture_decision(self, decision, confidence, reasoning):
"""Capture reasoning process and decision."""
return self.capture_event("decision", {
"decision": decision,
"confidence": confidence,
"reasoning": reasoning,
})
Traditional distributed tracing follows requests through services. Reasoning traces follow LLM thinking through decisions.
Reasoning traces capture the chain of thought that leads to an agent's decision, including:
{
"trace_id": "agent_abc123_20260831_102345",
"agent_id": "customer-support-agent",
"session_id": "session_xyz789",
"user_id": "user_42",
"start_time": "2026-08-31T10:23:45Z",
"end_time": "2026-08-31T10:25:12Z",
"total_tokens": 4520,
"total_latency_ms": 87000,
"final_decision": "Refund customer $50 for defective product",
"confidence": 0.92,
"steps": [
{
"step_index": 1,
"type": "llm_thought",
"content": "The user wants a refund. I need to check if they're eligible and for how much.",
"tokens": 25,
"latency_ms": 2000,
"timestamp": "2026-08-31T10:23:47Z"
},
{
"step_index": 2,
"type": "tool_call",
"tool": "get_order_history",
"arguments": {"user_id": "user_42"},
"result": {
"order_id": "ORD-9876",
"product": "Bluetooth Headphones",
"purchase_date": "2026-07-15",
"price": 149.99,
"return_window": "30 days",
"status": "returned_defective"
},
"duration_ms": 1500,
"timestamp": "2026-08-31T10:23:50Z"
},
{
"step_index": 3,
"type": "llm_thought",
"content": "User has a defective product returned within 30 days. Full refund applicable. Standard policy is full refund for defective items within return window.",
"tokens": 40,
"latency_ms": 3000,
"timestamp": "2026-08-31T10:24:00Z"
},
{
"step_index": 4,
"type": "tool_call",
"tool": "check_refund_policy",
"arguments": {"product_category": "electronics", "reason": "defective"},
"result": {"refund_percentage": 100, "processing_fee": 0, "notes": "Full refund for defective items"},
"duration_ms": 800,
"timestamp": "2026-08-31T10:24:05Z"
},
{
"step_index": 5,
"type": "tool_call",
"tool": "calculate_refund",
"arguments": {"original_price": 149.99, "refund_percentage": 100},
"result": {"refund_amount": 149.99},
"duration_ms": 500,
"timestamp": "2026-08-31T10:24:10Z"
},
{
"step_index": 6,
"type": "decision",
"decision": "Refund customer $149.99",
"reasoning": "Customer returned defective Bluetooth Headphones (ORD-9876) within 30-day window. Policy allows full refund for defective items. Refund amount: $149.99.",
"confidence": 0.92,
"timestamp": "2026-08-31T10:25:12Z"
}
],
"metrics": {
"steps_per_decision": 6,
" avg_llm_tokens_per_step": 80,
"avg_tool_latency_ms": 933,
"total_cost_usd": 0.0452
}
}
Several companies and open-source projects are working on OpenReasoning, a proposed standard for reasoning trace format. Similar to OpenTelemetry for traditional observability, OpenReasoning would provide:
This would enable interoperability between different agent frameworks and observability tools.
Traditional metrics need to be reimagined for agentic systems:
| Metric | Description | Why it matters | Target |
|---|---|---|---|
| Total tokens | Tokens consumed across all LLM calls | Cost tracking | Monitor for budget |
| Input tokens | Tokens sent to LLM (prompt + context) | Context window usage | Optimize for efficiency |
| Output tokens | Tokens generated by LLM | Response complexity | Monitor for quality |
| Tokens per step | Average tokens per reasoning step | Reasoning efficiency | Lower = more efficient |
| Token cost | Total cost in USD | Budget planning | Track vs. budget |
| Metric | Description | Target |
|---|---|---|
| LLM latency | Time spent on LLM inference | < 2s |
| Tool latency | Time spent waiting for tools | < 500ms per call |
| Reasoning latency | Time to complete reasoning chain | < 10s for simple queries |
| End-to-end latency | Complete agent response time | < 30s for complex queries |
| Latency by step | Latency per reasoning step | Identify bottlenecks |
| Metric | Description | How to measure |
|---|---|---|
| Confidence score | Agent's self-assessed confidence | 0-1 scale |
| User satisfaction | How satisfied are users with answers | Surveys, feedback buttons |
| Answer accuracy | Are answers factually correct? | Manual review, automated assessment |
| Completeness | Does answer address user's intent? | User feedback, follow-up rates |
| Task success rate | Does agent complete intended task? | Success/failure tracking |
| Metric | Description | How to measure |
|---|---|---|
| Decisions per session | Number of decisions made | Track agent decisions |
| Decision reversals | How often decisions are changed | Manual review, user overrides |
| Decision impact | Business value of decisions | Revenue, cost savings, efficiency |
| Decision risk | Risk level of decisions made | Classification by agent |
| Decision explanation quality | Quality of reasoning provided | User feedback, NLP scoring |
| Metric | Description | Target |
|---|---|---|
| Memory usage | Agent memory state size | Monitor for growth |
| Knowledge queries | Queries to knowledge base | Track usage patterns |
| Tool invocations | Number of tool calls | Monitor for efficiency |
| API calls | External API invocations | Track costs and quotas |
| Cache hit rate | Cache effectiveness | > 70% |
LLM Interactions
Agent Decisions
Tool Calls
Memory Changes
Knowledge Access
User Interactions
| Level | Usage |
|---|---|
| DEBUG | Full LLM prompts and completions (for development) |
| INFO | Agent decisions, tool calls, significant events |
| WARN | Low confidence decisions, potential issues |
| ERROR | Decision failures, tool errors |
| CRITICAL | Safety violations, security events |
Privacy Considerations: LLM prompts and completions may contain sensitive data. Always sanitize logs before storage. Consider hashing, redaction, or differential privacy techniques for production systems.
Traditional distributed traces follow requests through services. Agent traces follow decisions through reasoning:
User Request ──▶ Agent Entry Point
│
├─▶ LLM Call 1 (Understand request)
│ │
│ └─▶ Tool Call 1 (Get user history)
│ │
│ └─▶ Database Query
│
├─▶ LLM Call 2 (Formulate response strategy)
│ │
│ └─▶ Tool Call 2 (Check inventory)
│ │
│ └─▶ API Call
│ │
│ └─▶ LLM Call 3 (Process API response)
│
└─▶ Agent Response
Extend OpenTelemetry to propagate agent context:
# Traditional OpenTelemetry span
span = tracer.start_span("http.request")
# Agent context extensions
agent_span = tracer.start_span(
"agent.reasoning",
attributes={
"agent.id": "customer-support-1",
"agent.version": "2.1.0",
"agent.session_id": "sess_123",
"agent.current_step": 3,
"agent.current_goal": "process_refund",
}
)
For systems with multiple collaborating agents, traces need to capture:
{
"trace_id": "multi_agent_001",
"agents": [
{
"agent_id": "router",
"role": "request_router",
"actions": ["Delegated to customer-support agent"]
},
{
"agent_id": "customer-support",
"role": "customer_support",
"actions": ["Analyzed refund request", "Called order_history tool", "Made refund decision"]
},
{
"agent_id": "billing",
"role": "billing_processor",
"actions": ["Processed refund", "Updated accounting ledger"]
}
],
"message_flow": [
{"from": "user", "to": "router", "content": "I want a refund"},
{"from": "router", "to": "customer-support", "content": "Refund request forwarding"},
{"from": "customer-support", "to": "billing", "content": "Refund approval: $149.99"},
{"from": "billing", "to": "user", "content": "Refund processed"}
]
}
Traditional dashboards show system metrics. Agent dashboards need to show agent activity:
┌─────────────────────────────────────────────────────────────┐
│ AI/CD DASHBOARD │
├──────────────┬──────────────┬──────────────┬──────────────┤
│ AGENT STATS │ TOKEN USAGE │ PERFORMANCE │ DECISIONS │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ Active: 42 │ Today: │ Success: │ Total: │
│ Total: 128 │ 2.4M │ 94% │ 1,247 │
│ Failures: 3 │ This hour: │ Avg conf: │ High risk: │
│ │ 180K │ 0.87 │ 42 │
├──────────────┴──────────────┴──────────────┴──────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ REAL-TIME ACTIVITY │ │
│ │ │ │
│ │ customer-support-1 Contents processing refund for J │ │
│ │ inventory-agent-2 ═ Check stock levels │ │
│ │ deployment-agent-3 ● Deploying build #456 │ │
│ │ +--------------------------------+ │
│ │ [████████░░░░░░░░] Embedding search │ │
│ │ LLM call Processing 2.4s │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ REASONING TRACE │
├─────────────────────────────────────────────────────────────┤
│ │
│ User Query: "Why was my order delayed?" │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ LLM │────▶│ Tool │────▶│ LLM │ │
│ │ Thought │ │ Call: │ │ Thought │ │
│ │ │ │ order │ │ │ │
│ │ "Need to │ │ history │ │ "Order │ │
│ │ check │ │ │ │ was │ │
│ │ order │ │ Result: │ │ delayed │ │
│ │ status"│ │ 3 │ │ but │ │
│ └──────────┘ │ days │ │ maybe │ │
│ │ └────┬─────┘ │ │ │
│ │ │ ▼ │ │
│ │ ┌────▼─────┐ ┌──────────┐ │
│ │ │ Tool │────▶│ Decision │ │
│ │ │ Call: │ │ │ │
│ └──────────┘│ shipping │ │ "Order │ │
│ │ status │ │ delay │ │
│ └──────────┘ │ due to │ │
│ │ shipper │ │
│ │ delay" │ │
│ └──────────┘ │
│ │
│ Total time: 8.2s Total tokens: 4,230 Confidence: 0.93 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ AGENT HEALTH │
├─────────────────────────────────────────────────────────────┤
│ │
│ AGENT: customer-support-v2 STATUS: ✅ Healthy │
│ │
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
│ │ RESOURCE │ CURRENT │ TREND │ LIMIT │ │
│ ├─────────────┼─────────────┼─────────────┼─────────────┤ │
│ │ Memory │ 2.4 GB │ ↑ 5% │ 8 GB │ │
│ │ Tokens/day │ 1.8M │ ↗ 12% │ 10M/day │ │
│ │ Latency p95 │ 2.8s │ ↘ 8% │ 10s │ │
│ │ Error rate │ 0.3% │ → 0% │ 1% │ │
│ └─────────────┴─────────────┴─────────────┴─────────────┘ │
│ │
│ RECENT INCIDENTS: │
│ ├─ █ 15m ago: Low-confidence response (confidence: 0.62) │
│ ├─ █ 2h ago: Tool call timeout (retry succeeded) │
│ └─ █ 1d ago: Memory growth spike (resolved with restart) │
│ │
│ DEPENDENCIES: │
│ ├─ LLM: gpt-4o ✅ Healthy (latency: 1.2s) │
│ ├─ Vector DB: Qdrant ✅ Healthy (latency: 45ms) │
│ └─ Tools: 12/12 ✅ All operational │
│ │
└─────────────────────────────────────────────────────────────┘
Add observability directly to your agent framework:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
# Set up tracing
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(ConsoleSpanExporter())
)
tracer = trace.get_tracer(__name__)
class ObservableAgent:
def __init__(self, name, version):
self.name = name
self.version = version
self.tracer = tracer
def run(self, user_query):
with self.tracer.start_as_current_span(
f"agent.{self.name}.run",
attributes={
"agent.name": self.name,
"agent.version": self.version,
"user.query": user_query[:100], # Truncate
}
) as span:
# Agent logic
try:
result = self._execute_workflow(user_query)
span.set_attribute("agent.result", str(result)[:100])
span.set_status(trace.Status[trace.StatusCode.OK])
return result
except Exception as e:
span.record_exception(e)
span.set_status(trace.Status[trace.StatusCode.ERROR])
raise
def _execute_workflow(self, query):
reasoning_steps = []
with self.tracer.start_as_current_span("agent.reasoning_chain") as chain_span:
# Step 1: Understand query
llm_response = self._call_llm(f"Understand this query: {query}")
reasoning_steps.append({"type": "llm", "content": llm_response})
# Step 2: Call tools
tool_result = self._call_tool("get_relevant_info", query)
reasoning_steps.append({"type": "tool", "result": tool_result})
# Step 3: Generate response
final_response = self._call_llm(
f"Based on: {llm_response}\nTool result: {tool_result}\nGenerate final answer:"
)
reasoning_steps.append({"type": "llm", "content": final_response})
chain_span.set_attribute("reasoning.steps", len(reasoning_steps))
return reasoning_steps
def _call_llm(self, prompt):
with self.tracer.start_as_current_span("llm.call") as span:
span.set_attribute("llm.prompt", prompt[:100])
# Actual LLM call
response = llm_client.generate(prompt)
span.set_attribute("llm.response", response[:100])
span.set_attribute("llm.tokens.input", response.usage.input_tokens)
span.set_attribute("llm.tokens.output", response.usage.output_tokens)
span.set_attribute("llm.latency_ms", response.latency_ms)
span.set_attribute("llm.model", response.model)
return response.text
def _call_tool(self, tool_name, arguments):
with self.tracer.start_as_current_span(f"tool.{tool_name}") as span:
span.set_attribute("tool.name", tool_name)
span.set_attribute("tool.arguments", str(arguments)[:100])
result = tool_registry[tool_name](**arguments)
span.set_attribute("tool.result", str(result)[:100])
span.set_attribute("tool.duration_ms", getattr(result, 'duration_ms', 0))
return result
┌──────────────────┐
│ │
│ Agent Framework │
│ (LangGraph, │
│ CrewAI, etc.) │
│ │
└────────┬─────────┘
│
│ (OpenTelemetry traces/spans)
▼
┌──────────────────┐
│ │
│ OpenTelemetry │
│ Collector │
│ │
└────────┬─────────┘
│
├───▶ Prometheus (metrics)
│
├───▶ Jaeger/Tempo (traces)
│
├───▶ Loki (logs)
│
└───▶ Neo4j (reasoning traces + recommendations)
│
▼
┌──────────────────┐
│ │
│ Observability │
│ Backend │
│ (Grafana, etc.) │
│ │
└──────────────────┘
Store agent-specific data for deep analysis:
# Separate storage for different observability needs
observability_stores = {
"metrics": PrometheusClient(),
"traces": JaegerClient(),
"logs": LokiClient(),
"reasoning_traces": Neo4jGraph(), # For reasoning chains
"context_snapshots": VectorDB(), # For agent state
"evaluations": PostgreSQL(), # For quality metrics
}
Observability can be expensive for high-volume agent systems:
class SmartSampler:
def __init__(self, sample_rate=0.1, always_sample_patterns=None):
self.sample_rate = sample_rate
self.always_sample_patterns = always_sample_patterns or []
def should_sample(self, context):
# Always sample these
for pattern in self.always_sample_patterns:
if re.match(pattern, json.dumps(context)):
return True
# Sample a portion
return random.random() < self.sample_rate
def should_sample_full_prompt(self, context):
# Only sample full prompts for 1% of requests (privacy)
return random.random() < 0.01
# Usage
sampler = SmartSampler(
sample_rate=0.5, # Sample 50% of agent sessions
always_sample_patterns=[
".*error.*",
".*failure.*",
".*critical.*",
".*low.*confidence.*"
]
)
if sampler.should_sample(context):
# Full observability
capture_full_trace(agent_execution)
else:
# Minimal observability
capture_basic_metrics(agent_execution)
| Tool | Purpose | Maturity |
|---|---|---|
| OpenTelemetry | Unified observability data collection | Production-ready |
| Prometheus | Metrics collection and alerting | Production-ready |
| Grafana | Visualization and dashboards | Production-ready |
| Jaeger/Tempo | Distributed tracing | Production-ready |
| Loki | Log aggregation | Production-ready |
| LangSmith | LLM-specific tracing | Beta |
| Phoenix | AI observability | Early access |
| Arize | AI monitoring | Production-ready |
| Tool | Purpose | Agent-Specific? |
|---|---|---|
| Datadog | Full-stack monitoring | Adding AI features |
| New Relic | APM and observability | Adding AI features |
| Dynatrace | AI-powered monitoring | Yes |
| Honeycomb | Event-based observability | Yes |
| LangGraph | LLM application framework | Native support |
| Traceloop | LLM observability | Native support |
Agentic systems generate massive amounts of data:
Mitigations:
Observability data often contains:
Mitigations:
Storing and querying observability data at scale is expensive.
Mitigations:
Agent observability adds significant complexity to already-complex systems.
Mitigations:
Observability for agentic systems isn't just an evolution of traditional observability — it's a revolution. As AI agents increasingly drive critical business decisions and operations, we need fundamentally new ways to understand and track their behavior.
The traditional three pillars (metrics, logs, traces) remain important, but they're no longer sufficient. We need:
The DevOps teams that embrace this new observability paradigm will be the ones that can safely deploy, manage, and scale agentic systems at production scale. Those that don't will find themselves flying blind in the age of AI.
The observability gap is real. But with the right approach, it's also bridgeable.
Want to learn more about modern DevOps practices? Check out our articles on eBPF vs Service Mesh for Observability and Self-Hosted Observability Stacks.
For AI-native monitoring, explore LLM Runtime Monitoring with OpenTelemetry.