I used to do the same thing everyone does the first time an agent goes off the rails:
- Watch OpenClaw do something weird
- Ask it what happened
- Read a very confident explanation
- Realize 20 minutes later that the explanation was basically fan fiction
That loop gets old fast.
If you're debugging OpenClaw, or really any agent that touches APIs, browsers, queues, files, Discord bots, n8n workflows, or custom automations, the model's self-explanation is not evidence. It's a reconstruction.
The fix is boring, which is why it works:
- emit OpenTelemetry traces
- send them to Jaeger on
localhost:4317orlocalhost:4318 - open Jaeger on
http://localhost:16686 - filter for
tool_callspans withERROR - also filter for spans longer than
30s
One Reddit user in r/openclaw said those two filters caught about 80% of their weird-agent failures.
That matches my experience.
The problem with asking the agent
The agent is usually telling you a plausible story based on whatever is still in context.
It is not replaying a ground-truth execution log unless you explicitly captured one and fed it back.
That matters because agent runs get distorted by all the usual stuff:
- retries
- summarization
- context compression
- tool wrappers
- partial failures
- timeouts
- memory truncation
So when GPT-5, Claude Opus 4.6, Grok 4.20, or any other model says:
"The browser step probably failed because authentication expired"
maybe that's useful as a guess.
But it's still a guess.
If you're running agents at any real volume, especially with long-lived automations, you need something outside the model's own memory of the run.
That's what traces are for.
Why OpenTelemetry is a better debugger
OpenTelemetry gives you a timeline of what actually happened.
A trace is made of spans. Each span has timing, status, metadata, and parent/child relationships.
That means if OpenClaw:
- plans a task
- calls a search tool
- retries an HTTP request
- opens a Playwright browser step
- hangs for 42 seconds
- then fails downstream
...you can see that sequence directly.
Not a summary. Not a story. The sequence.
The difference is huge.
Agent explanation:
"I encountered an issue during browsing."
Trace evidence:
tool_call.playwrightstarted at14:03:11, retried twice, returnedERROR, and blocked downstream spans for38.7s.
That's real debugging.
Jaeger is the fastest way to start
Jaeger is great because it's simple and local.
Run one container:
docker run --rm --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 5778:5778 \
-p 9411:9411 \
cr.jaegertracing.io/jaegertracing/jaeger:2.19.0
Then open:
http://localhost:16686
Jaeger accepts OTLP on:
-
4317for gRPC -
4318for HTTP
So if your OpenClaw service is already instrumented with OpenTelemetry, point it there.
Common env vars:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
Which one you use depends on your exporter and protocol.
The 2 filters that catch most failures
The most useful thing I found in that Reddit thread wasn't a big architecture diagram. It was a dead-simple heuristic:
- filter
tool_callspans withERROR - filter spans with duration
> 30s
That's it.
1) tool_call spans with ERROR
This catches the obvious failures that agents love to narrate around:
- bad API keys
- malformed payloads
- browser crashes
- rate limits
- file permission issues
- webhook 500s
- upstream timeouts
The model may smooth over those details.
The span won't.
2) spans longer than 30 seconds
This one catches the sneaky failures.
A run can look "confused" when it's actually just blocked.
Typical examples:
- Playwright hangs on a page load
- a retrieval call retries three times
- a wrapper waits on upstream I/O
- a queue-backed tool stalls before returning
Long spans are often the first sign that a run is going bad before you even get a hard error.
The other 20%: context overflow
A lot of weird behavior isn't a crash.
It's context rot.
Nothing failed at the tool layer, but the agent accumulated too much history across turns and started operating on a compressed version of reality.
That's when self-explanations get even less reliable.
One practical signal: watch input token counts climb across turns right before behavior gets sloppy.
This is also why pricing model changes behavior.
When you're not obsessing over per-token billing, you naturally let agents:
- run longer
- call more tools
- branch more often
- retry more aggressively
That's good for throughput.
But it also creates longer traces and weirder failure modes.
If you're building on a flat-rate API like Standard Compute, this matters even more. Unlimited compute is great for agents and automations, but once agents can run freely, you need observability that doesn't depend on the model remembering what it did.
Ask the agent, read logs, or trace everything?
Here's the practical version.
| Approach | What you actually get |
|---|---|
| Ask the agent what happened | Near-zero setup. Useful for hypotheses. Not trustworthy for root-cause analysis. |
| Structured logs only | Better than nothing. Fine for simple services. Gets painful once runs branch or retry. |
| OpenTelemetry + Jaeger | Moderate setup. Best option for multi-step agent debugging with real timelines and error visibility. |
I'm not anti-logs.
For a tiny local project, terminal logs are fine.
But once your OpenClaw runs involve multiple tools, retries, browser steps, or long sessions, logs become archaeology.
Traces give you the map.
A sane setup for OpenClaw
Option 1: local debugging
This is the easiest place to start:
- Run Jaeger locally
- Enable OpenTelemetry in your OpenClaw service
- Export OTLP to
localhost:4317orlocalhost:4318 - Open Jaeger on
16686 - Filter for
tool_call+ERROR - Then check spans over
30s
That alone answers most "why did it do that?" questions.
Option 2: put an OpenTelemetry Collector in the middle
If you're moving beyond local debugging, add an OpenTelemetry Collector.
That's the cleaner architecture.
Your agents emit OTLP once. The Collector forwards traces wherever you want later.
Minimal example:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
exporters: [debug]
metrics:
receivers: [otlp]
exporters: [debug]
logs:
receivers: [otlp]
exporters: [debug]
That setup is a lot easier to live with when your agents are spread across:
- OpenClaw
- n8n
- Make
- Zapier
- Node workers
- Python workers
- queue consumers
- custom internal tools
A quick Node example
If you're wiring up an OpenAI-compatible service or agent backend in Node, the shape usually looks something like this:
npm install @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-grpc
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4317'
})
});
sdk.start();
Then instrument your tool calls so spans are named clearly:
planner.steptool_call.searchtool_call.playwrighttool_call.httpmemory.retrieve
Good span names make Jaeger way more useful.
What I actually do now
I still ask the agent what happened sometimes.
But I treat the answer like a tired teammate explaining an incident from memory.
Potentially useful.
Definitely not authoritative.
My actual workflow is:
1. Find the trace
2. Look for ERROR spans
3. Look for long spans
4. Check retries and parent/child relationships
5. Only then read the agent's explanation
That order matters.
Because once you've seen a bad OpenClaw run laid out in Jaeger, it's hard to go back.
You see the broken tool_call, the retry loop, the 41-second stall, and the downstream cascade. At that point, asking the agent to explain itself feels like asking the suspect to write the incident report.
Practical takeaway
If you're debugging OpenClaw, stop treating the model's explanation as the source of truth.
Use it for hypotheses if you want.
Do root-cause analysis with traces.
Start local:
Jaeger UI: http://localhost:16686
OTLP gRPC: localhost:4317
OTLP HTTP: localhost:4318
Then start with two filters:
-
tool_callspans withERROR - spans longer than
30s
That simple setup will save you a lot of wasted time.
And if you're running agents at scale, especially on flat-rate infrastructure where you actually want them to run freely, this stuff stops being optional. Predictable compute is great. Predictable debugging matters too.
That's the real upgrade: less interrogating the model, more inspecting what actually happened.
If you're building high-volume agents and automations and you're tired of per-token pricing changing how aggressively you let them run, Standard Compute is worth a look. It's an OpenAI-compatible API with flat monthly pricing, which makes a lot more sense for always-on agent workflows than babysitting token spend all day.
Top comments (0)