How to tell if your AI agent is stuck or still working
I almost had two agents overwrite each other's work last week, and the thing that nearly caused it was a log file that said nothing.
I launched a headless build agent, gave it a chunk of work, and watched its log sit at zero bytes. One minute. Two. Four. No lines, no progress, no heartbeat. Every instinct I have built up running these things said the same thing: it hung, it died on startup, kill it and go again.
So I did the reasonable thing, which turned out to be the dangerous thing. I launched a second agent on the same repo to redo the work the first one clearly was not doing.
The first agent had been building the entire time.
Its output was buffered, not absent. Four minutes of silence in the log was four minutes of work that had not been flushed to where I could see it yet. And now I had two live agents pointed at one repository, which is the single most reliable way to lose a chunk of a build. Two writers, no lock, each one convinced it owns the tree. That is how you get a file half-written by one process and clobbered by the other, and how you spend the next hour trying to reconstruct which half was real.
the log is a proxy, not the work
Here is the mistake, named plainly, because it is one I had made confidently. I was treating the log as the liveness signal. Log is talking, agent is alive. Log is quiet, agent is dead. That reads like common sense and it is wrong, because the log is not the work. The log is a thing the process emits when it happens to flush, and flushing is buffered, batched, and delayed for reasons that have nothing to do with whether real work is happening.
A quiet process is not a stopped process. The silence told me nothing about the build. It only told me the buffer had not drained. I had picked a signal that was easy to watch instead of the signal that actually reflected the thing I cared about, and then I acted on it like it was ground truth.
The signal that reflected the work was sitting right there the whole time: the filesystem. Files were appearing on disk. The build was landing, commit by commit, artifact by artifact, whether or not the log said a word about it. If I had watched the directory instead of the log, there would have been no silence to misread. Work was appearing. That is what alive looks like.
the near-miss that went right
The part of this that kept it from becoming a data-loss story is worth more than the mistake that started it.
The second agent did not assume it was working on a blank slate. Partway through, it noticed files in the tree that it had not written. A less careful setup treats that as noise, or worse, as its own stale output to regenerate over. This one did not. It recognized that something else had been here, that the files were another worker's in-progress output, and it refused to overwrite them. Instead it read them, and it checked them against the spec.
The collision resolved into a verification pass instead of a wreck. The second agent became a reviewer of the first agent's work rather than a bulldozer through it. That behavior, verify before you overwrite, is the safe default any time you cannot be certain you are alone in a workspace, and it is the only reason I am telling this as a near-miss and not as the day I lost an hour of a build to my own impatience.
watch the signal that reflects the work
The lesson generalizes past agents and buffers, which is why I keep it as an invariant now rather than a war story I happen to remember. Observability is not "can I see output." It is "am I watching the signal that actually reflects the thing I care about." A log tells you a process is talking. A passing test tells you the behavior is correct. A file on disk tells you work landed. Those are different questions, and picking the convenient one over the true one is how you end up acting on a signal that was never load-bearing.
The failure mode I watch for now is the tempting proxy: the metric that is easy to read but only correlated with the thing you actually want to know. CPU load instead of progress. Log chatter instead of committed work. A green dashboard instead of a served request. Every one of them is a real signal, and every one of them will lie to you the moment its relationship to the underlying work breaks, which it eventually does. The discipline is to name, up front, the signal that is the work and not a shadow of it, and to instrument that one.
So now the first thing I set up on a headless run is not better logging. It is a watch on the output directory. Is the build appearing on disk. That question does not care whether the buffer flushed, and it has never once told me a process was dead when it was quietly doing exactly what I asked.
If you are orchestrating agents on real builds and you are not sure which of your signals are the work and which are just the easiest thing to watch, work with VibeKoded. Send me how you are running them now and where you have been burned by a signal that turned out to be a proxy, and I can scope a pass on what you should actually be instrumenting.
The agent that looked dead was the one doing the work. The one I trusted was the log, and the log was the last thing in the room that actually knew.
// part of the spec-first methodology topic
// grab the free starter kit that makes your AI stop forgetting and stop guessing: get it →
// building with AI? the field manual has the structured lessons.
// hitting this on a real build? this is what I fix →