← All posts

Park the Decision, Not the Task

When an agent hits a question only I can answer, the obvious move is to stop the task. That's backwards — the decision has to outlive the task that raised it, or it dies when the workspace is torn down.

  • agents
  • supervision
  • escalation
  • workflow

An agent of mine finished an investigation, wrote a solid report, and surfaced one genuine question I had to answer myself — a product call about which of two behaviors was actually correct. Then the investigation finished, its temporary workspace got cleaned up, and the question went with it.

I found out because I went looking for it two days later and there was nothing to find. The report survived. The question didn’t.

This is a small bug with an annoying property: it only shows up when the system is working. A stuck agent leaves a stuck agent behind, and you notice. An agent that completes cleanly and drops one decision on the floor leaves nothing behind at all.

The thing I had backwards

My first fix was the obvious one. If the agent hits a question for a human, stop the task. Leave it open. Then the question can’t disappear, because the task holding it is still there.

That works right up until it doesn’t, and it stops working for a reason I should have seen sooner: the task and the decision have completely different lifetimes.

The task is short. It runs, it produces a report, it’s done. Holding it open past that point means an agent’s workspace, its branch, and its whole scratch state hang around indefinitely, purely as a container for one sentence. Multiply that by a fleet and you’re paying rent on dead workspaces to store unanswered questions.

The decision is long. It might sit for a week. It might depend on a conversation I haven’t had yet. It has to survive the workspace being deleted, the branch being pruned, the agent being long gone.

So the rule inverted:

The task finishes. The decision gets parked somewhere that outlives it.

Concretely: an unresolved captain decision becomes its own durable item in the backlog — the same backlog that holds real work — with a stable identity derived from the task that raised it and a key for the specific question. The originating task is then free to complete. Teardown can proceed. The question is now a first-class object with an owner, and it’s in the one place I already look every day.

Three details that turned out to be load-bearing

I got the shape right and then got bitten three times by the details. These are the ones worth stealing.

A stable identity, so retries don’t multiply. The parked decision’s id is <origin-id>-decision-<key> — derived, not generated. Run the same escalation twice and you update one item instead of creating two. This matters more than it sounds: agents retry, supervisors retry, and a fresh random id on every attempt turns one open question into six identical ones and trains me to ignore the whole surface. Deriving the id from facts that already exist is the cheapest idempotency you can buy.

It also rejects the failure modes you’d want it to: a collision on the same identity with a different title is an error, and reopening an already-resolved decision is refused outright. A resolved question should stay resolved even if some retry loop three layers down thinks otherwise.

“I found nothing” has to be said, never inferred. When an agent reports which decisions it surfaced, an empty result and a crashed result look identical from the outside — both produce no items. So an empty inventory isn’t accepted as absence. The agent has to pass an explicit “none” flag: a positive claim that it looked and there was nothing, as opposed to silence, which could mean anything.

This is the same instinct behind reconcile-or-refuse and behind not reading a reviewer’s silence as a yes. Inferred absence is the most expensive assumption in an automated system, because it’s the one that costs nothing to make and never announces itself.

Resolving is a multi-step write, so it has to be re-runnable. Answering a parked decision isn’t one operation. It records the decision, points the dependent tasks at it, clears the blocking edges, and only then marks the decision done. Any of those can fail halfway.

The rule I settled on: a failed step leaves the decision open, and re-running the exact same resolution finishes the job. Re-running a different resolution is rejected. So a partial write is always safe to retry and never silently overwritten by a second, different answer. Same idea as not letting a loop decide it’s finished — the terminal state is only reached when the writes that define it have actually landed.

What the agent is explicitly not allowed to do

The part I care about most isn’t mechanical. It’s that the agent never answers the question.

It would often be right. That’s the tempting part — an agent that has read the whole codebase frequently has a better-informed opinion than I do about which of two behaviors is correct. But “better informed” and “mine to decide” are different axes, and a system that lets competence creep into authority is one I stop being able to predict.

So the escalation path is narrow on purpose: the agent identifies that a decision belongs to a human, parks it with enough context to be answerable, and moves on to work that doesn’t depend on the answer. It does not guess, does not pick the safer-looking option, and does not route the question to some other agent that will guess on its behalf. That’s the same boundary as requires-the-human being a promise rather than a shrug and staying in your lane and filing the issue.

Why this is the interesting half of agent supervision

Everyone building with agents eventually writes the escalation path. Far fewer write the storage for what got escalated, and that’s where mine was leaking.

An escalation isn’t an event. Treating it as one — a notification, a message, a line in a log — means it exists only at the instant it’s raised, and anything that misses that instant misses it forever. An escalation is a piece of state with a lifetime measured in days, and it needs a home that doesn’t evaporate when the agent that noticed it finishes its shift.

The one-line version, which is what I’d tell someone starting on this:

An unanswered question is work. Put it where you keep work.

Not in a notification. Not in a report body. Not in an agent’s memory. In the backlog, with an id, with an owner, where it will still be sitting on Thursday whether or not anyone remembered it on Monday.