Curriculum/Agent Architectures
Human in the Loop
Pause before irreversible tools. The human sees a frozen payload. They do not become a free-text second model.
HITL is a stop that waits for a person. Use it for:
- Money leaving the house
- Email to a real customer
- Deletes, deploys, permission changes
- Anything the policy table marked
needs_approval(tools track)
The agent must not keep chatting in the same loop while waiting. Persist the run, freeze the args, notify the human, resume on approve/deny. If you hold the HTTP request open, the socket dies and you either double-run or drop the approval. Long-running jobs (next part) are how you wait overnight. This lesson is the pause contract.
The human sees a form. They do not write the next thought.
Pause for a humanThe human’s job is approve / deny / edit a form, not “write the next ReAct thought.” If they type free prose into the loop, you just hired a slower, unpaid model. Thoughts are not a contract. A frozen copy of tool name plus arguments is a contract.
Pause, do not chatter
When the parser blesses refund and policy says needs_approval, you do not execute. You create a ticket:
- status
waiting_human - tool name
- frozen args (deep copy)
- run_id, step, hashes (next lesson)
Stop the worker. Save typed state with pending_approval set. Notify. The assembler should not keep calling the model in that phase except to tell the user “waiting.” Extra thoughts cannot mutate the frozen args. If they can, you did not freeze.
Approve / deny / edit
Deny stops. No tool run. Maybe handoff packet. Status denied.
Approve with the same args runs the executor. Status approved_run.
Edit is a new form submit: new frozen snapshot, new hash, maybe a second approve. It is not a silent extra key on resume.
The live box rejects mutation: if resume args differ from frozen, rejected_mutation. Raising 40 to 400 after a click is the incident. Catch it.
A blank check (“approve any follow-up tool”) is not HITL. It is turning the human into a rubber stamp for the rest of the furnace.
Humans are not a second model
Do not paste the chain of thought into a ticket and ask “what next?” Ask them to confirm a form: order 99, amount 40, tool refund. If they need context, show the last observation and the policy line, not a novel.
If they must choose among tools, give buttons, not a chat box that gets parsed as ReAct. Free text is how you get eval energy in a support queue.
Policy table vs pause
The tools track owns which names need approval. This lesson owns the pause. Never pass a live mutable dict into the worker after the human clicked — copy the frozen snapshot. The next lesson is the copy and the hash. Here: the control flow.
Irreversible tools without HITL are a product decision you should be afraid of. Computer-use “Pay” buttons are banned later; money tools go through this pause.
Same queue as handoff
Waiting_human and handoff are cousins. Both persist a packet. Both wake a person. Handoff may have no pending tool (unknown intent). HITL has a pending tool. Wire them to the same operator queue so you do not build two inboxes.
Multi-agent is not a substitute for HITL. Another specialist is not a human. Next track can send a packet to a specialist. Money still needs a person unless you like incidents.
What the human sees, and what happens while you wait
The form is tool, args, maybe last observation, maybe policy line, run_id, hash. That is enough. A week of traces is a link, not the body. If you paste thoughts, the human will argue with the thought instead of checking the amount. If you paste nothing, they will approve on vibes. Frozen args in a table (order_id 99, amount 40) is the product.
While waiting, the worker is asleep. Jobs set wake_when to the approval id. The assembler must not keep emitting new refunds. Typed state holds pending_approval. Phase stays apply (or a dedicated wait_human phase if you want it on the whiteboard). Search during wait is usually illegal: it tempts the model to change the story. If you allow reads while waiting, you still must not change frozen args.
Timeouts on humans are a policy: after N hours, handoff to a broader queue or deny. That is stop, not a model “nudge.” Nudging the same human with extra thoughts is chatter.
Deny should be easy. Approve should be the same args. Edit is a new freeze (next lesson). “Approve and also email the CEO” is extra keys — reject. The human is not a tool catalog.
Who is the human? A role (billing_ops), not “anyone in Slack.” The packet goes to a queue. The click is authenticated. This track does not build auth, but the loop should not resume because a chat message said “ok” without a signed form. Free-text “ok” is a second unpaid model.
Common mistakes
- Keep chatting while waiting.
- Human writes the next thought.
- Approve any future tool.
- Live dict mutation after pause.
- HITL only in the prompt (“please confirm”).
- Executing before the click because the model said “user would approve.”
Run to execute this in your browser. Nothing is sent to a server.
Deny stops. Approve with the same args runs. Approve that silently raises the amount is rejected_mutation. The pause dict is the ticket. NEEDS_APPROVAL is the policy set; this box does not branch on it, but a real worker would request approval only for those names.
dict(args) is a shallow copy. Nested dicts need a deeper freeze (next lesson uses JSON round-trip). If amount is a top-level int, shallow is enough to see the idea. Do not stop at shallow in production.
How agents use this
The tools track owns the policy table. This lesson owns the pause. Never pass a live mutable dict into the worker after the human clicked — copy the frozen snapshot.
Jobs: wake_when is approval. The worker loads state, sees pending, does not call the model for a new refund. Resume is a different entrypoint than step. Keep them straight.
Check your understanding