Agent Observability: Why console.log Won't Cut It for Production AI
Your agent works great in demos. Then it hits production and starts doing things you can't explain.
The Observability Gap
Traditional application observability has a mature stack: metrics (Prometheus), logs (ELK), traces (Jaeger/Datadog). You instrument your endpoints, track latency, set alerts on error rates. It works because your application is deterministic — the same input follows roughly the same code path.
Agents break every assumption in that model:
Non-deterministic execution paths. The same input can produce wildly different chains of reasoning, tool calls, and outputs. There's no "expected path" to compare against.
Invisible reasoning. The most important decisions happen inside the LLM — inside a black box. Your logs show inputs and outputs, but the why is hidden.
Cascading failures are semantic, not structural. A traditional service fails with a 500 error. An agent "fails" by confidently giving the wrong answer. Your monitoring shows green across the board while users get garbage.
State is conversational. An agent's behavior at step 8 depends on everything that happened in steps 1-7. You can't understand a failure without replaying the full session.
What console.log Actually Gives You
Let's be honest about what basic logging looks like in a production agent:
import logging
logger = logging.getLogger("agent")
def run_agent(query: str):
logger.info(f"Starting agent for query: {query}")
plan = llm.plan(query)
logger.info(f"Plan: {plan}")
for step in plan.steps:
logger.info(f"Executing step: {step.name}")
result = step.execute()
logger.info(f"Step result: {result[:200]}")
answer = llm.synthesize(results)
logger.info(f"Final answer: {answer[:200]}")
return answerWhen something goes wrong, you're grepping through text logs trying to reconstruct what happened. You can see that the agent called a search tool, but not why it chose search over database lookup. You can see the final answer was wrong, but not where the reasoning went off track.
And that's the good case — where you even know something went wrong. Most agent failures are silent: the agent returns a plausible-sounding answer that happens to be incorrect. No error, no exception, no alert.
What Agent Observability Actually Requires
1. Session-Level Tracing
Every agent invocation is a session — a tree of LLM calls, tool executions, and decisions. You need to capture the full session as a structured trace, not a flat log:
import agentops
agentops.init()
@agentops.trace
def research_agent(query: str):
# Every LLM call, tool use, and sub-agent invocation
# is automatically captured as spans in a trace tree
plan = planner.create_plan(query)
for task in plan.tasks:
result = executor.run(task)
evaluator.check(result)
return synthesizer.compile(results)The trace shows you: planner took 2.3s and used 4K tokens → executor ran 3 tasks in parallel → task 2 retried twice because the tool returned an error → evaluator flagged task 3's output as low confidence → synthesizer used 12K tokens to compile.
2. LLM Call Inspection
Every call to a language model should capture:
- Full prompt (system + user + history) — not truncated
- Full response — including tool call decisions
- Model and parameters — which model, temperature, max tokens
- Token counts — input, output, total
- Latency — time to first token, total completion time
- Cost — calculated from model pricing and token counts
// What you want to see for every LLM call:
{
"span_id": "llm-call-7",
"parent_span": "research-task-2",
"model": "gpt-4o",
"temperature": 0.1,
"input_tokens": 3847,
"output_tokens": 512,
"latency_ms": 1823,
"cost_usd": 0.0142,
"tool_calls": [
{"name": "search_docs", "args": {"query": "refund policy"}}
]
}When a user reports a bad answer, you pull up the session trace, find the LLM call where the reasoning went wrong, and see exactly what the model was given and what it produced. Five-minute investigation, not a five-hour guessing game.
3. Tool Execution Tracking
Agents are only as good as their tools. You need visibility into:
- Which tools were called, in what order
- Input arguments and return values
- Latency and error rates per tool
- Whether the agent used the tool result correctly
// Tool calls captured automatically with Canary
const tools = {
search_knowledge_base: async (query: string) => {
// Canary captures: input args, response, latency, errors
const results = await kb.search(query, { limit: 5 });
return results;
},
create_ticket: async (title: string, body: string) => {
const ticket = await jira.create({ title, body });
return { id: ticket.id, url: ticket.url };
}
};Common failure pattern: the search tool returns relevant results, but the agent ignores them and hallucinates an answer anyway. Without tool-level tracing, you'd never know the information was available.
4. Quality and Correctness Signals
This is where agent observability diverges most from traditional monitoring. You need signals for semantic correctness:
- User feedback — thumbs up/down, corrections, follow-up questions
- Evaluator scores — automated LLM-as-judge or heuristic checks
- Confidence signals — how certain was the agent in its answer?
- Regression detection — same query, worse answer than last week
session = agentops.start_session(tags=["support-agent"])
answer = agent.run(user_query)
eval_result = evaluator.score(
query=user_query,
answer=answer,
ground_truth=retrieved_docs,
)
session.record_metric("quality_score", eval_result.score)
session.record_metric("hallucination_risk", eval_result.hallucination_score)
session.end(
result="success" if eval_result.score > 0.7 else "low_quality"
)Now you can alert on quality degradation, not just errors. "Support agent quality score dropped from 0.85 to 0.62 over the past 24 hours" is the kind of signal that prevents your agent from silently getting worse.
5. Replay and Debugging
When something goes wrong, you need to replay the full session — every prompt, every response, every tool call, every decision. Not "here are the logs," but a step-by-step reconstruction of what the agent did and why.
An engineer opens the trace, sees the full conversation tree, clicks into the specific LLM call where the agent decided to ignore the search results, reads the prompt, and immediately identifies the issue: the system prompt didn't tell the agent to prioritize retrieved documents over its training data.
Fix the prompt, deploy, verify the fix in the trace. Total debugging time: 15 minutes instead of 3 hours.
The Maturity Model
Where most teams are vs. where they need to be:
Level 0: Blind. console.log("agent ran"). No cost tracking. Debug by reading stdout.
Level 1: Logging. Structured logs with LLM call details. Can reconstruct sessions manually. Cost tracked via API dashboard.
Level 2: Tracing. Full session traces with span trees. Per-session cost attribution. Tool-level visibility. Can replay failures.
Level 3: Observability. Quality metrics, anomaly detection, regression alerts. Cost guardrails. Automated evaluations. Dashboard showing agent health at a glance.
Most production teams are at Level 0 or 1. If your agents are serving real users, you need to be at Level 2 minimum.
Getting to Level 2 in 30 Minutes
You don't need to build this from scratch. Canary gets you to Level 2 with minimal instrumentation:
import agentops
# Initialize once
agentops.init(api_key="your-key")
# Your existing agent code works unchanged
# LLM calls, tool usage, and costs are captured automatically
agent.run(user_query)Session traces, LLM call inspection, tool tracking, cost attribution, and a dashboard to see it all. Works with LangChain, CrewAI, AutoGen, and raw API calls.
Start observing your agents for free →
Your agents are making thousands of decisions a day. It's time to see what they're doing.