MagenticOS

What "Completed" Means: Task Verification for AI Agents

Mutagenic Labs · · 7 min

What “Completed” Means: Task Verification for AI Agents

We ran a task against our own runtime with the network to the model provider deliberately unreachable. The task reported Completed.

Nothing had happened. No model was contacted. The plan that ran was a fallback the system had written for itself, consisting of one local search that returned zero results. The zero results were not an error — an empty result set is a perfectly valid result set — so nothing objected, the loop reached its end, and the status was set.

That is not a bug in the sense of a typo. It is the exact behaviour the code describes. And it is worth writing about, because we suspect the same shape is sitting inside a great many agent systems right now, including ones with far more polish than ours.

Falling off the end of a loop is not success

Here is the structure, stripped of our specifics, because it is not specific to us.

An agent runtime executes a plan as a sequence of steps. Each step either succeeds or returns an error. Errors are handled: the step is marked failed, the task is marked failed, the error propagates. That part is usually fine — error handling is the part everyone writes.

Then the loop ends. And at the bottom of the loop, after the last step, something sets the status to Completed.

Read that again, because the important word is after. The terminal state is not a conclusion drawn from the work. It is a consequence of arriving at the end of the iteration. The status means “no step raised an error” while appearing to mean “the goal was achieved.” Those are wildly different claims and the code makes only the first one.

In our case the gap was wide enough to drive a truck through: the step output was written to an event stream and then dropped. It was never inspected, never compared to the goal, never consulted in any way by the thing that decided the task was done. The agent had the evidence and did not look at it.

The failure compounds, because “no error” is easy to achieve

A system that equates no error with success is not merely imprecise. It is biased, in one direction, toward reporting success — because the easiest way to avoid errors is to do less.

Watch how that played out. When the model provider could not be reached, the planner call failed. That failure was discarded — one .ok() in the source, which converts a result into an optional and throws the reason away. With no plan from the model, the system fell back to a hardcoded single-step plan. That step was a local file search. It found nothing. Finding nothing is not an error. Loop ends. Completed.

Every individual link in that chain is defensible. Discarding a failed optional enrichment is a normal thing to do. Having a fallback plan is good engineering. An empty search result genuinely is a valid result. Yet the composition of four reasonable local decisions is a system that confidently reports success while provably doing nothing at all.

This is what makes it worth a blog post rather than a commit message. There is no villain in the code. There is a chain of small permissive choices, each of which looks correct in isolation, that together dissolve the meaning of the only word the user actually reads.

Why the tests didn’t catch it

They couldn’t have. We had a test named for exactly this path — a live vertical slice through the task loop, recording events, the sort of test whose existence makes you feel covered.

It stubs the planner. It replays a recorded transcript instead of calling a provider.

So the one test exercising the agent loop end-to-end exercises it with the model removed. Which means it can only ever confirm that the loop runs. It can never confirm that the loop reaches anything, because in the test there is nothing to reach.

The general lesson is uncomfortable and we’d rather state it plainly than let someone else discover it in our code: if every test of your agent path stubs the model, you have a test suite that describes your fallback behaviour and says nothing about your product. The stub is not a shortcut around a slow test. It is a change of subject.

What a terminal state should be allowed to assert

We think the honest framing is that a status is a claim, and a system should not be permitted to make a claim it has no basis for. That gives a ladder, and it is worth being explicit about which rung you are on rather than implying a higher one.

Rung one: the loop terminated without raising. This is where a lot of agent tooling actually sits. It is a real fact and it is worth reporting — but it should be called something like Ran, not Completed. The word should not oversell the evidence.

Rung two: the work was non-vacuous. At least one step produced output that carries something. This is a low bar and it is startling how much it catches: it is the difference between “we searched and found nothing, so we’re done” and an honest failure. It is also cheap — a guard on the terminal state and a narrow definition of what counts as empty. It does not require intelligence. It requires looking at the output you already have.

Rung three: the goal was verified. The system compares what happened against what was asked and decides. This is the real thing, and it is genuinely hard — it is an observe-and-verify iteration, not a check bolted onto the end of a linear pass. It is a rewrite, not a patch, and anyone who tells you they added it in an afternoon is on rung one.

The failure mode we are describing is not being on rung one. It is being on rung one and printing rung three.

The part that generalises

If you are building on agents, the diagnostic is short and you can run it this afternoon:

None of this requires a better model. It is not an AI problem. It is an operating-systems problem — what a process is allowed to report about itself, and what the system is obliged to check before it believes it. Which is broadly the argument we have been making about where agent trust has to live: not in the model’s self-report, but in the layer underneath it that has no incentive to flatter.

It also sits next to, but is not the same as, inspectability. Inspectability is about being able to reconstruct what happened after the fact. Verification is about whether the system was entitled to say Completed at the time. You can have a perfect, replayable, timestamped log of an agent doing nothing and then declaring victory. We did.

Where we are

MagenticOS is in alpha and we would rather you heard this from us. The runtime currently sits on rung one. We know precisely where — we read it out of our own source with line numbers — and the fix divides cleanly: honest failure when a provider call fails, and a guard so a terminal state cannot be reached on empty output, are both small and are being wired now. Real goal verification is not small, and we are not going to pretend it is, or rush it to hit a date.

We publish this kind of thing on purpose. An alpha runtime that only tells you about its wins is asking you to trust a self-report from a system whose whole failure mode is unearned self-reports.

If you are building in this space and want to compare notes on where your terminal state actually gets set, we would like to hear from you.