This is the part that stops it being a toy, and it is where the real engineering in project #4 lives.
Part 1 Β· The assumption you didn't know you'd made
Project #3's approval model has an assumption buried in it so obviously true that it is invisible:
The agent that hit the gate is the agent you are talking to.
The loop stops, the request ends, your browser shows a card, you click, a new request restarts that same loop. One agent, one card, one resume. Every part of that design is correct and none of it survives delegation.
Because now the agent that hits the gate is:
- two levels down in a tree
- running inside a
Promise.all - alongside two siblings that are still working
- with nobody talking to it and no browser tab of its own
And the naive fix is worse than not delegating
If each worker asks separately, you get three cards at three different moments,
out of order, each with no context. "Approve empty_jar {id:38}?" β from
which worker? Based on what evidence? What about the other two?
Part 2 Β· So the pause travels up
Part 3 Β· Three details, and each one is a bug if you skip it
| The detail | What breaks without it |
|---|---|
origin on every bubbled call | The card says "approve empty_jar {id:38}?" and you have no idea which of three workers wants it or what it found. You would be rubber-stamping decisions you cannot see β worse than not asking, because it looks like oversight. |
partial_results | Clicking Approve re-runs the two workers that had already finished. You pay for them twice, they may answer differently the second time, and any side effect happens again. |
paused_children | The stopped worker's entire conversation vanishes when the HTTP request ends, and there is nothing left to say yes to. |
Take the second one seriously for a moment. Without partial_results, approval
is not idempotent in the world β it re-executes work that already happened.
For jar inspections that means paying twice. For anything with a side effect it
means doing it twice.
Part 4 Β· The screen
That single approval standing in front of three agents' worth of work is the picture the whole project is aiming at:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βΈ ONE APPROVAL, 9 DESTRUCTIVE CALL(S)
9 of them came from 3 sub-agent(s) that are frozen mid-task.
Frozen sub-agents: "jars 1-20", "jars 21-40", "jars 41-60"
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βΈ pantry__empty_jar {"id":46,"reason":"Seal broken (lid gasket β¦"}
asked by : sub-agent "jars 41-60"
because : Seal broken (lid gasket displaced) - not merely scuffed,
no AUTHORIZED note present.
host rule: empty_jar permanently destroys every cookie in that jar.Nine calls. Three frozen agents. One decision.
Note the three lines under each call, because between them they are the whole
argument for origin:
| Line | Comes from | Answers |
|---|---|---|
asked by | origin | which worker wants this |
because | the worker's own reasoning, carried up | why it thinks so |
host rule | the gate's reason | why I am being asked at all |
Together they are enough to decide without going and reading three traces β which is the actual test of whether an approval UI works.
Part 5 Β· And it survives a partial no
Eight were approved and the ninth denied.
The worker that got the "no" read it as an ordinary tool result, adapted, and reported honestly β exactly as the deny branch intends. And the orchestrator surfaced it to the human rather than quietly dropping it:
One jar flagged but NOT emptied: Jar 59 β it does meet the tampering criteria, but the sub-agent's attempt to empty it was declined. This one still needs your attention.
Part 6 Β· Rewinding a tree came free
Nested replay was the stretch goal. It cost one extra select and one
expandable row.
Not because it was over-estimated, but because of a decision in
lib/run-driver.ts: a sub-agent gets a real row in runs, with a
parent_run_id, and its own stream of trace_events under its own sequence
numbers.
A sub-agent is not a special kind of thing needing special handling. It is a
run with a parent. So replaying one is GET /api/runs?id=<childRunId> β the
same handler, recursing exactly the way the loop does.
When the new feature turns out to be an instance of the old one, you get the tooling for free.
What you now know
- Project #3's gate assumed the agent that stopped is the one you are talking to, and delegation destroys that.
- Three separate cards would hand the details back to you β worse than not delegating.
- The pause bubbles up and freezes the whole tree:
origin,partial_results,paused_children. - Without
partial_results, approving re-executes finished work. - The card needs who asked, why they think so, and why you are being asked β enough to decide without reading traces.
- Verify against the database, not the model's summary.
- A sub-agent is a run with a parent, which is why nested replay was nearly free.