The uncomfortable part of building agentic systems is not making them act. It is figuring out what happened after they did.
I had one of those debugging sessions where the logs make you feel better for about thirty seconds.
The agent had completed the request.
No exception. No timeout. No crashed service. The tool calls had succeeded. The model had returned a response.
And the answer was wrong. So I opened the logs.
planner startedresearch agent startedtool call succeededresearch agent completedfinal agent completedstatus = success
Wonderful.
Except I still couldn’t answer the simplest question:
Why did the system produce this answer?
That was the moment I stopped thinking about observability as “logs for LLM calls” and started thinking about something bigger:
observability for autonomous behavior.
Traditional distributed systems are complicated, but they are usually predictable. A request comes in, a service calls another service, a database is queried, and a response comes back.
Agents break that assumption.
The code gives an agent capabilities. The model helps decide which capability to use and what to do next.
For the same request, one run might search the web and stop. Another might call a specialist agent. A third might retry a tool, retrieve more context, or ask for human approval.
There’s a temptation to solve this by capturing every bit of the model’s chain of thought.
I don’t think that’s the right abstraction.
There are obvious privacy and security reasons not to turn private reasoning into a permanent telemetry stream. There’s also a technical problem: a generated explanation isn’t necessarily a faithful causal explanation of the model’s internal computation.
As an engineer, I want something more concrete.
I want to know what context was available, which decision was made, what action followed, what came back, and what happened next.
decision = delegatetarget = financial-agentreason_code = FINANCIAL_ANALYSIS_REQUIREDaction = tool_calltool = market_datastatus = successevaluation = rejectednext_action = retry
That tells me how the system behaved without pretending I can inspect a model’s private mind.
Once you have multiple agents, tools, retrieval, memory and protocols, a trace can’t just be “prompt in, answer out.”
This is also why I care about versioning. If yesterday’s agent scored 92% and today’s scores 74%, I want the trace to tell me which agent version, prompt version, model, tool version and policy version were involved.
I’ve been looking at Langfuse because it solves a large part of this problem without requiring a team to build an observability product from scratch.
Langfuse v4 uses an observations-first model: LLM calls, tool executions and agent steps are first-class queryable objects, while a trace groups observations using a shared trace_id. It also supports sessions, user tracking, agent graphs, evaluation and OpenTelemetry-based ingestion.
That changes how you can investigate a system.
You don’t just ask, “Show me trace 123.”
You can ask, “Show me failed tool observations,” or “Which agent step is expensive?” or “Which observations are associated with poor evaluation scores?”
And if you’re using LangGraph, Langfuse has a dedicated integration and can render the graph for the trace.
If I am building a platform, I do not want every agent to know how Langfuse works.
I want the application to emit standard telemetry, and I want the observability layer to decide where that telemetry goes.
That is where OpenTelemetry fits.
OpenTelemetry gives you common semantic conventions for describing operations and telemetry across traces, metrics and logs. Its GenAI conventions are evolving in a separate repository, and already cover concepts such as agent identity, model information, token usage, retrieval and evaluation-related attributes.
This is where many “LLM tracing” designs become too shallow.
Suppose the final answer is wrong. You look at the model call and see a perfectly reasonable response to the context it received.
So the real question becomes: why did it receive that context?
I want references to prompt and policy versions, memory records, retrieved documents, retrieval scores, context size, truncation and state transitions. I do not necessarily want every raw piece of data copied into the telemetry store.
That distinction matters. Observability is not an excuse to create a second uncontrolled copy of your production data.
This is another thing I would change in a serious agent platform.
An agent calling another agent is not the same operation as an agent calling a tool server.
For A2A, I want delegation, target agent, task lifecycle, status, latency and authorization context.
For MCP, I want server, tool, arguments, result, schema/version, authorization and failure details.
The exact attributes can evolve with the protocols. The important part is not losing those boundaries in a generic “tool call” span.
Stateful agents make this even more interesting.
With a framework such as LangGraph, execution can be persisted and resumed. A workflow can for human input, continue later, or recover from an error. LangGraph’s persistence is explicitly designed to support state recovery and human-in-the-loop workflows.
So I want to know not only what happened inside a run, but which state checkpoint it started from, what changed, and whether the run was a fresh execution, retry or resume.
Otherwise you can end up with a perfectly detailed trace that still doesn’t explain the current state of the agent.
Production agents will eventually encounter actions that require policy checks or human approval.
That should be visible as part of the execution, not hidden in another system.
Agent ↓Policy check ↓Approval required ↓Human approves ↓MCP action ↓Continue
The trace should tell us which policy version ran, why the action was blocked or approved, who approved it, and how long the workflow waited.
That last number matters more than it looks. A five-second model call and a nine-hour human approval are both “latency”, but they mean very different things operationally.
This is probably the biggest conceptual difference from infrastructure monitoring.
Your CPU can be fine while your agent is terrible.
Your API can return 200 while the customer receives a useless answer.
Your tool can return 200 while returning stale information.
So I want evaluation tied directly to execution.
There are two loops here.
Offline: run controlled datasets against different prompts, models, tools or agent versions.
Online: evaluate production executions using deterministic checks, model-based evaluators, user feedback and human review.
Langfuse already supports evaluation workflows, datasets, experiments, scores and feedback around traces.
The important part is connecting the score back to the execution. A score of 0.72 is much less useful than “0.72 because retrieval failed for these queries after version 14 of the retriever.”
Agents can fail economically without failing technically.
A task that normally needs three model calls can suddenly need fifteen because a planner keeps delegating, a tool keeps timing out, or a context window keeps growing.
Nothing crashes. The answer eventually arrives.
But the cost has gone from cents to dollars.
That is an observability problem.
I want to be able to ask: Why was this run 12x more expensive than normal?
Token usage, latency, retries, model routing and tool calls should all be visible in the same trace. Langfuse supports token and cost tracking as part of its tracing model.
When an agent gives a bad answer, “hallucination” is often just the symptom.
The actual cause might be bad retrieval, stale memory, context truncation, a wrong tool choice, an A2A delegation problem, an MCP schema mismatch, a prompt regression, model routing, a policy decision or an agent loop.
That is why I want the trace to be navigable from symptom to evidence to cause.
Consider:
Researcher → Analyst → Researcher → Analyst → Researcher → ...
Nothing technically fails.
The system is just working forever.
Your infrastructure dashboard might look perfectly healthy.
Your token bill won’t.
So agent observability needs behavioral signals: unusual step counts, repeated calls, abnormal delegation patterns, retry storms, growing context and execution depth.
Those aren’t proof of failure. They’re signals that should trigger investigation or runtime limits.
GenAI telemetry can contain prompts, completions, tool arguments and tool results. OpenTelemetry’s GenAI guidance treats content capture carefully because it can be sensitive and expensive to store.
So I would put a deliberate telemetry boundary between the application and the backends:
application ↓instrumentation ↓classify / redact / enrich ↓sampling + tenant policy ↓OpenTelemetry Collector ↓observability backends
Some environments may allow raw content for a tightly controlled debugging team. Others may prohibit it. The architecture should support both without forcing the same retention policy everywhere.
If I were starting an enterprise agent platform today, I would not build another Langfuse.
I’d build on standards and specialize where agent behavior creates genuinely new problems.
**OpenTelemetry : **The common telemetry foundation and semantic vocabulary across the application.
**Langfuse : **AI-native observations, traces, agent graphs, evaluation, prompts, cost and feedback.
**Agent telemetry contract : **A platform-level semantic layer for identity, decisions, state, A2A, MCP, policy and outcomes.
**AgentOps : **Behavioral alerts, quality trends, anomaly detection, governance and continuous improvement.
The thing I’d build myself isn’t another trace viewer.
It’s the semantic contract.
Every agent should speak the same language:
Identity. Context. State. Decision. Action. Protocol. Policy. Economics. Outcome. Evaluation.
Once those concepts are consistent, you can change models, frameworks, agents and observability backends without losing the story of what happened.
I started this with a wrong answer and a pile of green logs.
The lesson wasn’t “we need better logs.”
It was that we were monitoring the wrong thing.
For traditional applications, we mostly ask:
Is the system working?
For agentic systems, that is only the beginning.
We need to ask:
What did it do?
What information did it have?
Which path did it take?
Which agents and tools were involved?
What state did it change?
How much did it cost?
Did a policy or human intervene?
Did it actually accomplish the task?
And finally:
Can I trust the result?
That’s why I don’t think agent observability is simply “LLM logging with a nicer UI.”
The software itself has changed.
When the system can decide what to do next, observability has to evolve from watching services to understanding behavior.
We’re getting very good at making agents do things.
Now we need to get equally good at understanding what they did.
My current mental model:OpenTelemetry gives the platform a common telemetry language; Langfuse gives engineers an AI-native place to explore and evaluate that telemetry; and AgentOps turns those signals into operational decisions.
Technical references
• Langfuse v4 — observations-first architecture:[langfuse.com/docs/v4]
• Langfuse agent graphs:[langfuse.com/docs/observability/features/agent-graphs]
• Langfuse observability overview:[langfuse.com/docs/observability/overview]
• Langfuse LangGraph integration:[langfuse.com/guides/cookbook/integration_langgraph]
• OpenTelemetry GenAI semantic conventions:[opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai]
• OpenTelemetry semantic conventions:[opentelemetry.io/docs/concepts/semantic-conventions]
Your Agent Is Working. So Why Can’t You Explain What It Did? was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.