Some actions shouldn't fire automatically. Sending money. Posting publicly. Modifying customer data. The pattern: agent prepares → human approves → agent acts.
Three steps:
# 1. Prepare
action = {"type": "send_email", "to": "customer@x", "body": "..."}
# 2. Notify (email to self — production: Slack message, dashboard alert, SMS)
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": user_email,
"subject": "[APPROVAL NEEDED] Send to customer",
"body": f"Action proposed: {action}\n\nReply 'approve' to proceed.",
})
# 3. Read signal & act
if approved:
perform(action)
else:
log_skip(action)And the script just sits there waiting for me to approve?
In a real system — yes, with a polling loop or a webhook. In the sandbox we simulate the approval signal with a hardcoded value, because true wait-for-human exceeds the sandbox's runtime. The pattern is the gate, not the wait — agent decides, human signals, code branches.
Why email to self?
Because the curriculum's connector set is gmail / googletasks / sheets / docs / slides / linkedin / calendar — no Slack. Self-email gets the notification onto your phone via Gmail push, which is a valid HITL channel.
def hitl_action(action_description, approved_signal):
# 1. Notify a human (email yourself — production: Slack/SMS/dashboard alert)
notify_human(action_description)
# 2. Read approval signal
if not approved_signal:
return {"status": "declined", "acted": False}
# 3. Perform the action
result = perform(action_description)
return {"status": "approved", "acted": True, "result": result}Agents are powerful and probabilistic. The same prompt today produces a different decision tomorrow. For high-stakes actions (transactions, public posts, privileged data), you want a human checkpoint every time until trust is earned.
Whatever reaches a human reliably:
All four are equivalent — the channel is whatever puts the request in front of a human and then provides a way to read the response.
Where does the approved boolean come from?
"yes"[APPROVED]The gate has two outputs: {acted: true, result} or {acted: false, reason}. Always return both — downstream code (or the eval suite) can branch on whether the action fired.
Different failure modes:
Combining: a gated action that fires can be wrapped in retry; a gate that returns declined doesn't retry — it logs and moves on.
Some actions shouldn't fire automatically. Sending money. Posting publicly. Modifying customer data. The pattern: agent prepares → human approves → agent acts.
Three steps:
# 1. Prepare
action = {"type": "send_email", "to": "customer@x", "body": "..."}
# 2. Notify (email to self — production: Slack message, dashboard alert, SMS)
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": user_email,
"subject": "[APPROVAL NEEDED] Send to customer",
"body": f"Action proposed: {action}\n\nReply 'approve' to proceed.",
})
# 3. Read signal & act
if approved:
perform(action)
else:
log_skip(action)And the script just sits there waiting for me to approve?
In a real system — yes, with a polling loop or a webhook. In the sandbox we simulate the approval signal with a hardcoded value, because true wait-for-human exceeds the sandbox's runtime. The pattern is the gate, not the wait — agent decides, human signals, code branches.
Why email to self?
Because the curriculum's connector set is gmail / googletasks / sheets / docs / slides / linkedin / calendar — no Slack. Self-email gets the notification onto your phone via Gmail push, which is a valid HITL channel.
def hitl_action(action_description, approved_signal):
# 1. Notify a human (email yourself — production: Slack/SMS/dashboard alert)
notify_human(action_description)
# 2. Read approval signal
if not approved_signal:
return {"status": "declined", "acted": False}
# 3. Perform the action
result = perform(action_description)
return {"status": "approved", "acted": True, "result": result}Agents are powerful and probabilistic. The same prompt today produces a different decision tomorrow. For high-stakes actions (transactions, public posts, privileged data), you want a human checkpoint every time until trust is earned.
Whatever reaches a human reliably:
All four are equivalent — the channel is whatever puts the request in front of a human and then provides a way to read the response.
Where does the approved boolean come from?
"yes"[APPROVED]The gate has two outputs: {acted: true, result} or {acted: false, reason}. Always return both — downstream code (or the eval suite) can branch on whether the action fired.
Different failure modes:
Combining: a gated action that fires can be wrapped in retry; a gate that returns declined doesn't retry — it logs and moves on.
Create a free account to get started. Paid plans unlock all tracks.