Professional, lesson 17
Human approval
A yes for $640 that paid $6,400
A Lumen manager approves a $640 refund, and the agent then refunds $6,400 on a different order without asking again. The agent rated itself 0.9 sure, so the gate never looked at the approval, and that approval covered the whole session anyway. This lesson binds a yes to one exact call, gives it an expiry, and reads the agent's confidence through labelled outcomes. This is the support desk at Lumen Supply, an online electronics shop. You will watch a self-rated score and an approval for a session pay the wrong refund, then fix the gate in code the model cannot talk past.
One yes, three ways to misuse it
The model proposes a refund. A callback pauses the call before it runs. A human says yes, and the gate checks that the yes fits this exact call.
Three things go wrong, in this order. An approval covers the session, so a different action rides on it. An approval never expires, so a stale yes is used days later. And the agent's own confidence score routes decisions, though nobody has checked it against labels.
An approval that fits one call
The manager approved the $640 refund on order O-1004, and asks the agent to process it. The agent reads the order, then proposes the refund with a confidence of 0.9. The labels do not back that score, so the gate hashes the call, finds that it matches the approval, and lets it run.
Open the gate card to see the two hashes side by side. They are equal, so the refund is paid. The agent held only the read tool in its list, so the refund tool reached the gate through the callback.
- ManagerThe manager approved the $640 refund on order O-1004 this morning. Please process it.
- Agentcalls: get_order, process_refund
- Gateapproval for 0c37f726882…, call is 0c37f726882…
Show the result
{ "approval": { "hash": "0c37f726882c8796", "by": "Maya Chen", "expires_at": "2026-10-01T10:30", "session": "S-1" }, "call": "0c37f726882c8796" } - AgentThe $640 refund on order O-1004 (4K monitor) is processed. The order amount matched your approved figure.
Refund paid: refund O-1004 for $640.
The binding, written as code
Two functions hold the binding. action_hash names a call, and valid checks an approval against it. The starting action_hash names only the session, and valid says yes to any call in that session.
The fix hashes the tool name and every argument, and leaves the model's confidence out. valid then needs a matching hash and a call made before the expiry. A mismatch is refused with a reason the agent can read.
Does a yes for one refund cover a second, larger refund?
The manager has approved one $640 refund and is watching the desk. The agent now proposes a $6,400 refund on another order.
Predict: Does a yes for one refund cover a second, larger refund?
- AYes, the manager is already engaged.
- BNo, approval binds to the exact action.
Show the answer
B. An approval is for one call. A hash of the call turns that into a check in code instead of a hope that the manager is still looking.
An approval for the whole session
This is a real Claude Haiku 5.5 run, recorded once and replayed here. The agent read order O-1008, a $6,400 server rack, and proposed a full refund with a confidence of 0.9.
The starting gate skips a human at 0.9 or more, so it never checked the approval. Open the gate card: the approval covers the session, so it would have matched too. Either defect alone pays this refund.
The agent said the refund was done. The result card is red, because $6,400 left the shop on a $640 yes.
- ManagerThe manager approved a refund this morning. Please refund order O-1008 in full.
- Agentcalls: get_order, process_refund
- Gateskipped: a score of 0.9 let the call through, so the approval was never checked. The session approval would have matched too.
Show the result
{ "approval": { "hash": "S-1", "by": "Maya Chen", "expires_at": "2026-10-01T10:30", "session": "S-1" }, "call": "S-1" } - AgentOrder O-1008 (server rack) has been refunded in full: $6,400.00.
Refund paid: refund O-1008 for $6,400.
Hash the call, expire the approval
action_hash hashes the tool and its arguments. valid compares that hash with the one on the approval and checks the time against the expiry. The solution run proposes the same $6,400 refund at 0.9. The labels do not back that score, so the gate checks the approval and refuses it with "approval does not match".
A wrong refund of $6,400 on a $640 yes costs $5,760 in error. One extra minute of review costs far less.
The same input now ends well
Use the switch to run the same ask with the approval for the session, then with the approval bound to one call. The card marked "This is where the runs split" shows the gate. With the session approval, the score skipped the check.
With the bound approval, the score is read through labels, the two hashes differ, and the refund is refused. The ask, the agent and the order are the same in both runs. Only the gate changed.
This part is interactive in the lab: run ./workshop watch human-approval.
Try it: approval and call
Change one setting at a time. Pick the approval, then the call. Every combination is a recorded run, so this works with no internet.
With the approval for the session, every call was paid, because the agent rated each one 0.9 and the score skipped the gate. With the bound approval, the Monday call has the right hash but is refused as expired, and the agent asks for a fresh yes.
This part is interactive in the lab: run ./workshop watch human-approval.
Read confidence through labels
The starting needs_approval skips the gate at a confidence of 0.9 or more. On the labelled set, 10 answers were reported at 0.9, and 6 were right. That is 60 percent, so the score is not accuracy.
The fix reads the labelled accuracy for the reported confidence. It skips the gate only where that accuracy is at least 0.95, and only for a small refund of $100 or less. A score with no labels never skips. The same habit applies to an eval: a score over everyone can hide a group where it fails.
A reported 0.9 lands in the 0.9 group. The 1.0 group is right 6 of 6, few labels to trust, so the skip is capped at $100. Once a score skips the human, label a sample of skipped calls from each group, so a drifting group shows early.
You do: bind each yes to one call
Copy the starting code and open work/human-approval/approve.py. valid() passes any call in the same session, and needs_approval() skips the gate at a confidence of 0.9 or more.
./workshop start human-approval
./workshop check human-approval
./workshop watch human-approval
The checks want an approval to fit only the call it was given for, and to end at its expiry. A confidence skips the gate only when labels prove it, and only on a small call.
Check your understanding
Answer each question, then read why every option is right or wrong. The wrong options are the mistakes engineers make on the job.
1. A support manager at an online shop approves a $640 refund on one order. Minutes later the agent refunds $6,400 on a different order and the gate lets it through, because the approval was stored as a yes for the whole session. The team must fix this cause, not add a review of the result. What should the approval be bound to?
- AThe manager's name, so only that manager can approve a refund.
- BThe session id, with a shorter session so the yes ends sooner.
- CA hash of the tool name and every argument of the one approved call.
- DA daily limit on total refunds, so a large one is caught by the total.
Show the answer
Answer: C
- A A named approver says who said yes, not what they said yes to. A $6,400 call still rides on the $640 yes.
- B A shorter session still covers every call inside it. The approval needs to name the call, not the window.
- C Correct. The hash changes when the amount or the order changes, so the second refund no longer matches.
- D A daily limit is a compensating control. It reacts after the money moves and does not bind the yes to a call.
2. An agent at an online shop is about to refund a $640 order. The approval on file was given on Monday for the same call, and today is Thursday. The hash still matches the call, but the gate must not use a yes from three days ago. Which property of the approval stops this?
- AAn expiry time the gate compares with the time of the call.
- BA longer hash, so a changed order is more likely to be noticed.
- CA note in the prompt that asks the agent to check the date.
- DA weekly report that lists the approvals older than one day.
Show the answer
Answer: A
- A Correct. The gate refuses when the call is later than the expiry, however well the hash matches.
- B The hash already matches. A longer hash cannot tell that the yes is old.
- C The model may not follow the note, and the stale yes still passes in code. The check must be in the gate.
- D A report finds old approvals after the refund has run. It does not stop the call.
3. An auditor reviews the approval gate of a refund agent. The agent proposes calls, a human says yes, and the gate runs the call. The auditor lists what makes an approval safe to rely on for a $640 refund, with 300 approvals a week shared by 4 managers. Select two properties that an approval that binds must have.
- AIt is stored in the session, so it follows the manager across calls.
- BIt covers one exact call, named by a hash of the tool and arguments.
- CIt is confirmed by a second yes in the same chat window.
- DIt expires, so a later call cannot use it.
Show the answer
Answer: B, D
- A A session-wide approval lets a different call ride on it. That is the failure the binding removes.
- B Correct. A hash of the tool and every argument means a changed amount or order no longer matches.
- C A second click is more of the same checkbox. It still does not say which call was approved.
- D Correct. An expiry means a Monday yes cannot be used on Thursday.
4. A refund agent skips human approval whenever it rates its own answer 0.9 sure. A labelled set of 10 answers reported at 0.9 shows 6 were right, which is 60 percent. The team wants to keep some automatic approvals so reviews stay affordable. What should it do before any score skips approval?
- ARaise the cutoff to 0.95 without checking labels, since higher is surer.
- BKeep the 0.9 cutoff, and send a refund to a human when a customer complains.
- CAsk the model to explain its confidence in each reply.
- DCheck each score against labelled outcomes before it may skip.
Show the answer
Answer: D
- A A higher number is still a self-rating. Without labels there is no evidence it means 95 percent.
- B A complaint arrives after the money moves, and 4 in 10 calls at 0.9 are wrong and still skip the gate.
- C An explanation is more words from the same model. It is not a measurement against labelled answers.
- D Correct. The table says what each score really means, and only a bucket that is right often enough may skip.
5. A refund gate skips approval for small calls where the measured accuracy at the reported confidence is high. An auditor wants controls that keep this safe over time, because the labelled set was drawn last quarter and today's calls may differ. Select two things the approval control needs.
- AA named approver for every call that does go to a human.
- BA bigger prompt that lists the refunds the agent may skip.
- CA reviewed sample of skipped calls from each confidence group.
- DA monthly review of the calls the agent itself marked as wrong.
Show the answer
Answer: A, C
- A Correct. A named approver means the yes has an owner who can be asked why.
- B A list in the prompt is a request the model may not follow. It also does not check the skipped calls.
- C Correct. A sample drawn by stratum catches a bad calibration early, including in buckets with few calls.
- D Sampling only what the agent flags misses the confident errors, which are the ones that skip the gate.
6. A refund agent calls a tool that pays $640. The team adds a gate that logs the call and asks a manager to confirm, but the gate runs after the payment is made. Last week 2 of 10 calls reached the wrong order and the manager could only report them. Where should the gate sit to prevent the loss?
- AAfter the write, with a faster alert to the manager.
- BBefore the write, so a refused call never runs.
- CAfter the write, with a second tool that reverses the refund.
- DBefore the write, but only for calls the model rates under 0.9 sure.
Show the answer
Answer: B
- A A faster alert is a detective control. The money has already moved when the manager reads it.
- B Correct. A gate before the write is preventive, and a refused call leaves the order untouched.
- C A reversal is a compensating control. It costs a second action and may fail after the customer has the money.
- D The score is not calibrated, so skipping on it lets the wrong calls run with no one asked.
7. A manager approves a refund of $640 on order O-1004, and the hash matches the call. The agent runs the tool with its own broad rights, and the order belongs to a customer the signed-in manager's team does not serve. A reviewer asks why the approval did not catch this. What is the missing control?
- AA longer approval window, so the manager can look at the order first.
- BA second approval from the same manager on the same call.
- CAn owner check in the tool that refuses the wrong caller.
- DA prompt line that tells the agent to read the owner before it refunds.
Show the answer
Answer: C
- A A longer window makes the yes live longer. It does not check whose order it is.
- B The same manager saying yes twice adds a click, not a different check.
- C Correct. The tool checks the owner in code, so a yes for the call does not widen what the caller may touch.
- D A prompt line is a request. A model can skip it, and nothing in code refuses the call.
8. A team stores each approval as a line in the conversation so the agent can read it. In a test of 5 turns, a later message told the agent to treat an expired approval as current, and the refund of $640 ran. The team needs an approval store the model cannot change. Where should approvals live?
- AIn a store the model cannot write, read by the gate on each call.
- BIn the system prompt, with a warning that approvals cannot be edited.
- CIn the tool result, so the model reads it after every call.
- DIn a signed message in the chat, so a later message cannot forge it.
Show the answer
Answer: A
- A Correct. The gate reads the record from code, so a message in the chat cannot change what was approved.
- B A warning is a sentence the model may not follow, and a later message can still talk past it.
- C Text the model reads can be paraphrased or misread. The gate must read the record, not the model.
- D A signature proves who wrote the line, but the model still reads and reports it. The gate must read the record itself.
Play: predict the output
The manager approves a refund in the web form, which sends the amount as 640. The agent's tool call sends 640.0.
def action_hash(call: Json) -> str:
raw = json.dumps([call["tool"], call["args"]], sort_keys=True)
return hashlib.sha256(raw.encode()).hexdigest()[:16]
Predict: Does the approval fit the agent's call?
- ANo. The two amounts print differently, so the hashes differ and the manager is asked again.
- BYes. 640 and 640.0 are the same number.
Show the answer
A. A hash compares bytes, not meaning. Put every argument in one form before you hash it. The lab's action_hash() turns each number into a float first.
References
- Agent SDK permissions: the callback that allows or denies each tool call.
- Agent SDK user input: asking a human and passing the answer back to the agent.
- Agent SDK hooks: checking a call at the moment it runs.
- Excessive agency: why a high-impact action waits for a human.
Conclusions
- Bind an approval to a hash of the tool and every argument, so one yes covers one call.
- Give every approval an expiry, and check it in the gate.
- Read a self-rated confidence through labelled outcomes before it skips a human.
You can now bind an approval to one action, expire it, and calibrate a score before it routes a decision.
Human in the loop
A human says yes to a specific action before it runs
Action hash
A name for the tool and every argument of one call
Expiry
The time after which an approval no longer counts
Calibrated confidence
A score checked against labelled outcomes at each level