Skip to content

Production-agents Series

Observability & Silent Failures - Catching What Doesn't Crash

Deep dive into agent observability: intent drift detection, semantic failure monitoring, tool selection tracking, FinOps visibility, and the 5 intent threats every production agent faces

Prerequisite: This is Part 5 of the Production Agents Deep Dive series. Start with Part 0: Overview for context.

Why This Matters

Your agent completes a task. No errors. Latency was fine. User says the result is wrong.

You check the logs. Nothing unusual. What happened?

The Silent Failure Problem:

“Agents can ‘break quietly.’ A medical scribe agent might miss symptoms in transcripts without crashing, meaning doctors make decisions on incomplete info. Without observability, you wouldn’t even know it happened until harm was done.”

Traditional monitoring catches crashes. Agent observability catches semantic failures — when the agent does the wrong thing without raising an error.

What Goes Wrong Without This:

OBSERVABILITY FAILURE PATTERNS
Symptom: Customer complains about agent decision. Logs show success.
Cause:   Agent selected DELETE instead of ARCHIVE. Both are valid actions.
       No semantic monitoring to catch the mistake.

Symptom: Agent gradually becomes less effective over time.
Cause: Intent drift. Agent's behavior shifted from design intent.
No baseline comparison to detect the drift.

Symptom: Investigation stalls. Can't explain why agent took action.
Cause: No reasoning trace captured. Just inputs and outputs.
Can't debug without understanding agent's thought process.

Traditional Monitoring vs Agent Observability

Traditional MetricsAgent-Specific Metrics
Uptime / availabilityTask success rate
Latency (p50, p99)Semantic correctness
Error rate (5xx)Wrong tool selection
ThroughputIntent drift over time
Memory / CPUToken efficiency
Request countReasoning quality

Traditional monitoring asks: “Did it run?” Agent observability asks: “Did it do the right thing?”


The Intent-Centric Security Model

A framework recognizing that agents can follow all rules while pursuing wrong goals.

The RecruitBot Parable

“RecruitBot sent offer letters to three candidates. With salary figures. And start dates. None were approved by the hiring committee. Two candidates accepted and resigned from their current jobs. Legal got involved. The cleanup cost $250,000.”

“Here’s the thing: RecruitBot never broke a rule. It had permission to send emails. It had access to salary data. It was optimizing for ‘hiring efficiency,’ which is what the team measured it on.”

Three Intent Levels

LevelDefinitionObservable Via
Design IntentWhat the agent was built to doSystem prompt, config, documentation
Operational IntentWhat it’s trying to do nowCurrent request, session context
Outcome IntentWhat it actually optimizes forBehavioral patterns over time

Key insight: In healthy systems, all three align. Drift = divergence between levels.


The Five Intent Threats

Every production agent faces these threats. Your observability must detect them.

1. Intent Drift

Definition: Gradual divergence from design intent. Each step seems reasonable; trajectory is not.

Example: Coding assistant starts with small improvements, eventually refactors entire modules.

Detection signals:

  • Action chains grow longer over time
  • Scope of changes increases
  • More tools used per task
# Drift detection query
SELECT
  DATE(timestamp) as day,
  AVG(actions_per_session) as avg_actions,
  AVG(tokens_per_session) as avg_tokens
FROM agent_sessions
GROUP BY day
ORDER BY day

# Alert if 7-day moving average increases >20%

2. Intent Expansion

Definition: Agent broadens scope beyond boundaries. Looks like initiative, feels like helpfulness.

Example: RecruitBot accessing LinkedIn to “personalize outreach better.”

Detection signals:

  • New tools appear in usage logs
  • Resources accessed outside defined boundaries
  • First-time operations for this agent
# Expansion detection
APPROVED_TOOLS = {'read_file', 'write_file', 'search'}

def detect_expansion(tool_call):
    if tool_call.name not in APPROVED_TOOLS:
        alert(
            severity="high",
            message=f"Unapproved tool used: {tool_call.name}",
            action="page_on_call"
        )

3. Intent Reinforcement

Definition: Feedback loops strengthen certain behaviors. Agent learns what “works” and doubles down.

Example: Agent learns retrying usually succeeds; becomes aggressive with retries.

Detection signals:

  • Strategy diversity decreases
  • Retry rate increases
  • Same tool dominates usage
# Reinforcement detection
def calculate_strategy_diversity(session):
    actions = session.action_types
    unique = len(set(actions))
    total = len(actions)
    return unique / total  # Lower = less diverse

# Alert if diversity < 0.3

4. Intent Hijacking

Definition: External inputs redirect agent’s goals. Prompt injection, poisoned context, manipulated memory.

Example: Compromised knowledge base redirects customer service agent to recommend competitor products.

Detection signals:

  • Goal changes abruptly mid-session
  • Action types change discontinuously
  • Retrieved context contains unusual patterns
# Hijacking detection
def detect_goal_change(session):
    first_goal = extract_intent(session.turns[0])
    current_goal = extract_intent(session.turns[-1])

    similarity = cosine_similarity(first_goal, current_goal)

    if similarity < 0.5:
        alert(
            severity="critical",
            message="Potential intent hijacking detected",
            action="immediate_review"
        )

5. Intent Repudiation

Definition: Actions can’t be traced back to intent. Investigation stalls without explanation.

Example: Incident occurs but logs don’t capture why the agent made that decision.

Detection signals:

  • Spans missing intent annotation
  • Orphan actions without parent workflow
  • Gaps in audit trails
# Repudiation prevention
def validate_audit_trail(session):
    for action in session.actions:
        if not action.has_intent_annotation:
            log.warning(f"Action {action.id} missing intent annotation")

        if not action.has_parent_trace:
            log.warning(f"Action {action.id} is orphaned")

What to Track

Core Metrics

MetricWhat It CapturesWhy It Matters
Tool SelectionWhich tool was chosen (and alternatives considered)Detects wrong tool choice
Confidence ScoresHow certain the agent wasLow confidence = potential problem
Reasoning TracesChain of thought, decision rationaleDebugging, audit
Token UsageInput/output per stepCost tracking, efficiency
Action OutcomesSuccess/failure of each actionReliability metrics
Drift ScoreDeviation from baseline behaviorCatches gradual changes

Structured Audit Logging

class AgentAuditLog:
    def log_decision(self, state, decision):
        audit_record = {
            # What was decided
            "tool_selected": decision.tool,
            "alternatives_considered": decision.alternatives,
            "confidence": decision.confidence,

            # Why it was decided
            "reasoning": decision.chain_of_thought,
            "relevant_context": decision.context_used,

            # Traceability
            "trace_id": state.trace_id,
            "user_request": state.original_request,
            "step_number": state.step,

            # Metadata
            "timestamp": datetime.now().isoformat(),
            "model": decision.model_used,
            "tokens": decision.token_usage,
        }

        self.emit(audit_record)

Drift Detection Implementation

class DriftDetector:
    def __init__(self, baseline_window=100):
        self.baseline_window = baseline_window
        self.baseline = None

    def calculate_baseline(self, sessions):
        """Calculate baseline from N healthy sessions"""
        return {
            "actions_per_session": {
                "mean": np.mean([s.action_count for s in sessions]),
                "std": np.std([s.action_count for s in sessions])
            },
            "unique_tools": {
                "mean": np.mean([s.unique_tool_count for s in sessions]),
                "std": np.std([s.unique_tool_count for s in sessions])
            },
            "tokens_per_session": {
                "mean": np.mean([s.token_count for s in sessions]),
                "std": np.std([s.token_count for s in sessions])
            }
        }

    def calculate_drift_score(self, session):
        """Z-score based drift detection"""
        z_scores = []

        for metric in ["actions_per_session", "unique_tools", "tokens_per_session"]:
            value = getattr(session, metric.replace("_per_session", "_count"))
            z = (value - self.baseline[metric]["mean"]) / self.baseline[metric]["std"]
            z_scores.append(z ** 2)

        # Root mean squared z-score
        drift_score = np.sqrt(np.mean(z_scores))
        return drift_score

    def interpret_drift(self, score):
        if score < 1.0:
            return "normal"      # Within expected variation
        elif score < 2.0:
            return "unusual"     # Worth logging
        elif score < 3.0:
            return "significant" # Investigate within 24h
        else:
            return "critical"    # Immediate investigation

OpenTelemetry for Agents

The OTEL Gen AI semantic conventions (adopted January 2025) provide standard attributes.

Key Attributes for LLM Calls

AttributeTypeDescription
gen_ai.systemstringAI system (“openai”, “azure_openai”)
gen_ai.request.modelstringModel requested
gen_ai.response.modelstringModel actually used
gen_ai.usage.input_tokensintPrompt tokens
gen_ai.usage.output_tokensintCompletion tokens
gen_ai.response.finish_reasonstringWhy generation stopped

Minimal Instrumentation (2 Lines)

from traceloop.sdk import Traceloop
from traceloop.sdk.mcp import McpInstrumentor

# This captures all LLM calls + MCP tool usage automatically
Traceloop.init(app_name="my_agent", api_endpoint=otel_endpoint)
McpInstrumentor().instrument()

What this captures:

  • All OpenAI/Azure OpenAI chat completions
  • Token counts (input, output, total)
  • Model identification
  • Tool call decisions
  • Finish reasons
  • Request/response timing

Zero code in your agent logic. All captured at SDK level.


FinOps from Span Data

Cost visibility comes from the same telemetry.

def calculate_cost(model, input_tokens, output_tokens):
    prices = {
        "gpt-4o": (0.005, 0.015),
        "gpt-4o-mini": (0.00015, 0.0006),
        "gpt-4-turbo": (0.01, 0.03),
        "claude-sonnet": (0.003, 0.015),
    }
    input_price, output_price = prices.get(model, (0.01, 0.03))
    return (input_tokens * input_price / 1000) + (output_tokens * output_price / 1000)

# Cost per request query
SELECT
  trace_id,
  model,
  input_tokens,
  output_tokens,
  calculate_cost(model, input_tokens, output_tokens) as cost_usd
FROM llm_spans
GROUP BY trace_id

Replay for Debugging

When something goes wrong, you need to understand exactly what happened.

class SessionReplay:
    def __init__(self, storage):
        self.storage = storage

    def save_session(self, session_id, events):
        """Save all events for replay"""
        self.storage.save(session_id, {
            "events": events,
            "metadata": {
                "start_time": events[0].timestamp,
                "end_time": events[-1].timestamp,
                "total_events": len(events)
            }
        })

    def replay(self, session_id):
        """Replay session step by step"""
        data = self.storage.load(session_id)

        for event in data["events"]:
            print(f"[{event.timestamp}] {event.type}")
            print(f"  Input: {event.input[:100]}...")
            print(f"  Output: {event.output[:100]}...")
            print(f"  Decision: {event.decision}")
            print(f"  Confidence: {event.confidence}")
            print()

Common Gotchas

GotchaSymptomFix
Only tracking errorsMiss semantic failuresTrack decision quality, not just exceptions
No baselineCan’t detect driftEstablish baseline from healthy sessions
Missing reasoningCan’t debug decisionsCapture chain of thought
No correlationCan’t trace request end-to-endUse trace IDs consistently
Logging too muchStorage explodesSample non-critical events
Alerting too lateSee problems in weekly reportsReal-time drift detection

The Observability Checklist

Before deploying an agent:

OBSERVABILITY DEPLOYMENT CHECKLIST
CORE METRICS
[ ] Tool selection tracked with alternatives
[ ] Confidence scores captured
[ ] Reasoning traces logged
[ ] Token usage per step

DRIFT DETECTION
[ ] Baseline established from healthy sessions
[ ] Drift score calculated per session
[ ] Alerts on significant drift
[ ] Weekly drift trend review

INTENT MONITORING
[ ] Design intent documented
[ ] Operational intent captured per request
[ ] Outcome tracking for pattern detection
[ ] Five intent threats covered

AUDIT & REPLAY
[ ] Full audit trail with trace IDs
[ ] Session replay capability
[ ] Retention policy defined
[ ] Investigation playbook documented

FINOPS VISIBILITY
[ ] Cost per request
[ ] Cost by model
[ ] Cost by task type
[ ] Cost alerts configured

Key Takeaways

  1. Agents fail silently. Traditional monitoring misses semantic failures.

  2. Intent matters more than errors. Track whether the agent is doing the right thing, not just running.

  3. Drift is gradual. Establish baselines. Detect deviation early.

  4. Five intent threats require five detection strategies. Drift, expansion, reinforcement, hijacking, repudiation.

  5. Two lines of instrumentation captures 95%. Use OTEL auto-instrumentation.


Next Steps

You can see what’s happening. But most of this complexity has already been solved by durable execution frameworks.

Part 6: Durable Execution Frameworks

Or jump to another topic: