DEV Community

Qingfeng
Qingfeng

Posted on

The Log Format That Made My Agent Debuggable

An agent that fails silently is worse than no agent: you lose the trust that made you delegate in the first place. After a month of running one, the difference between "I can fix this" and "I have no idea what happened" came down to what I logged.

Here is the format that survived contact with reality.

One row per action, not per chat message

The unit of debugging is an action, not a turn. Each row answers four questions:

  1. What was attempted - the exact command, selector, URL, or tap target, verbatim.
  2. What the world looked like before - a hash of the relevant state, not the whole screenshot.
  3. What came back - exit code, stderr, matched node, byte count.
  4. What the verifier said - the predicate that had to become true, and whether it did, with timing.

That fourth field is the one people skip. Without it, a log tells you what the agent did, never whether it worked.

Log the predicate, not the picture

Storing screenshots for every step sounds great until you have 40 GB of them and none is the one you needed. Store instead: the selector used, the text matched, the node count, the hash of the tree. When something breaks, replay those three numbers and you usually see the bug immediately - the node count dropped from 84 to 12, the matched text was "Send draft" instead of "Send", the tree hash changed mid-task when a modal appeared.

Failures need more detail than successes

A successful step is one line. A failed step should carry the whole context: the last successful action, the elapsed time, the tree dump at failure, and the retry count. My rule is one line for success, up to forty for failure. You will read the failure rows a hundred times and the success rows almost never.

Record the refusals

Every action the agent proposed and did not take (tier 2, no approval; destructive; outside scope) gets a row with the reason. Two weeks in, that log told me my own policy was wrong three times - not too strict, but scoped to the wrong dimension.

Make it greppable, not pretty

Plain text one-line-per-event, key=value pairs, UTC timestamps. No nested JSON that requires a viewer. When something goes wrong at 2am, you want grep ERROR run-2026-10-21.log | tail, not a dashboard that takes twenty seconds to load.

That is the whole trick. Actions over messages, predicates over screenshots, detail weighted toward failure, refusals recorded, and a format you can grep at 2am.

If you have built something like this, what do you log that I am missing - and what did you stop logging once you realised it never helped?

Top comments (0)