TL;DR:

  • Agent failures are usually not random — they repeat predictably under specific input conditions, which means they can be systematically found and fixed
  • Trace every tool call and every LLM response: what was sent, what came back, how long it took, and whether the agent’s next action matched what you’d expect
  • Hallucinations in agents are often reasoning failures triggered by ambiguous prompts, missing context, or tool outputs that don’t match what the model expected — not random model misbehaviour

The classic debugging workflow — reproduce the bug, add a print statement, run it again — does not transfer well to AI agents. You can’t step through an agent’s “execution” in a debugger. The model’s reasoning is opaque. Failures are often non-deterministic at the surface even when they’re deterministic underneath. And the failure mode you’re looking for might require a specific sequence of tool results to trigger.

So you need a different approach.

Start With Structured Traces, Not Print Statements

The first thing to get right is capturing what actually happened during a run. Not just the final output — every LLM call, every tool invocation, every result, in sequence.

This is what distributed tracing tools are designed for. LangFuse, Langsmith, and Phoenix all provide agent-native tracing that captures the full execution tree: which LLM was called, with what messages, what tokens were consumed, what the response was, and how that response was parsed into an action.

A minimal LangFuse setup with LangChain:

from langfuse.callback import CallbackHandler

handler = CallbackHandler(
    public_key="your-public-key",
    secret_key="your-secret-key",
)

# Pass to any LangChain chain or agent
result = agent.invoke(
    {"input": user_message},
    config={"callbacks": [handler]}
)

After this, every run appears in LangFuse with the full trace tree. You can see exactly which tool the agent called, what arguments it passed, and how the tool result influenced the next prompt. You can filter to failed runs, compare traces side-by-side, and see patterns across many executions.

If you’re not using LangChain, OpenTelemetry with the opentelemetry-instrumentation-openai package gives you similar visibility for direct API calls.

Log Tool Calls Explicitly

Generic tracing tools capture LLM I/O. But tool calls deserve their own structured logs, separate from the trace, because they represent the agent’s actual actions in the world.

For each tool invocation, log:

  • The tool name and arguments (serialised as JSON)
  • Whether the call succeeded or failed
  • The latency
  • The result (truncated if large)
  • The agent’s stated reason for calling the tool, if you can extract it
import logging
import json
import time

logger = logging.getLogger("agent.tools")

def logged_tool_call(tool_name: str, tool_fn, **kwargs):
    start = time.time()
    try:
        result = tool_fn(**kwargs)
        latency = time.time() - start
        logger.info(json.dumps({
            "event": "tool_call",
            "tool": tool_name,
            "args": kwargs,
            "status": "success",
            "latency_ms": round(latency * 1000),
            "result_length": len(str(result)),
        }))
        return result
    except Exception as e:
        logger.error(json.dumps({
            "event": "tool_call",
            "tool": tool_name,
            "args": kwargs,
            "status": "error",
            "error": str(e),
            "latency_ms": round((time.time() - start) * 1000),
        }))
        raise

Structured JSON logs make it easy to query: “show me all failed tool calls in the last hour” or “which tool has the highest error rate this week.”

The Three Main Agent Failure Patterns

Once you have traces and tool logs, most agent failures fall into recognisable categories:

1. Wrong tool, right intent

The agent understood the task but called the wrong tool, or called the right tool with wrong arguments. Usually caused by an ambiguous tool description or by the tool schema not matching what the model expects to pass.

Fix: rewrite the tool description to be more precise. Add examples to the schema description. If a tool takes a structured input, add a JSON schema example directly in the description.

2. Correct tool calls, wrong conclusion

The agent’s tool calls were all reasonable, but it drew the wrong conclusion from the results. This is the “hallucination” failure mode — but it’s usually not random. Look at the tool output that preceded the wrong conclusion. Was it ambiguous? Did it include irrelevant information that the model anchored on? Did a search result return a stale or incorrect document?

Fix: improve tool output formatting. Return clean, structured data rather than raw API responses. If a tool can return “no results”, make that explicit rather than returning an empty array that the model might misinterpret.

3. Getting stuck in a loop

The agent calls the same tool repeatedly, or oscillates between two tools, without making progress. Usually triggered by a tool that returns an error the agent doesn’t know how to handle, or by a goal that the available tools can’t actually achieve.

Fix: add a step counter to your agent loop and enforce a maximum. Log the last N tool calls and add logic to detect repetition. Ensure every tool has a clear failure mode that the agent can act on.

Replaying Failures

One of the most useful debugging techniques is building a replay harness: given a saved trace, re-run the agent with the same inputs and tool responses to reproduce the failure deterministically.

For most agent frameworks, this means:

  1. Capture the full tool call history during a failing run
  2. Build a mock tool layer that returns those exact responses
  3. Re-run the agent against the mock

This lets you isolate whether the failure is caused by the inputs, the tool outputs, or the model’s reasoning. You can also replay with a different model or modified system prompt to test fixes without needing the live environment.

Reading the System Prompt

The most common root cause of consistent agent failures is a system prompt that contradicts itself, leaves edge cases undefined, or doesn’t specify what the agent should do when something goes wrong.

When debugging, read the system prompt with fresh eyes, specifically looking for:

  • Conflicting instructions (“always use tool X” and “prefer tool Y for recent data”)
  • Undefined behaviour for tool errors (“if the search returns nothing, do X” — is X specified?)
  • Ambiguous scope (“summarise the conversation” — all of it? the last turn? the relevant parts?)

The model follows your instructions literally. If the instructions are ambiguous, the model picks an interpretation — and it might not be the one you intended.

Agent debugging is slower than conventional debugging, but it’s not fundamentally different: find the inputs that trigger the failure, understand the exact sequence of decisions that led to it, and identify which part of the system produced a wrong output. The tools are just different.