Human-in-the-Loop and Approval Gates¶
Human-in-the-loop is not one pattern¶
Humans may be involved for several different reasons:
missing information
ambiguity
high-risk action
policy requirement
low confidence
conflicting evidence
manual business decision
run blocked
Do not flatten all of these into one generic ASK_USER state.
Useful distinctions include:
Clarification
Approval
Escalation
Review
Override / decision
Ask vs infer¶
An agent should not ask about every small uncertainty, or it becomes unusable. But some guesses are materially risky.
For example:
"Deploy the latest version."
If there are two production environments and no default:
EU production
US production
asking is better than guessing.
Mental model:
Missing information
↓
Can it be resolved safely/deterministically from context?
├── yes → resolve
└── no
↓
Does guessing materially affect outcome/risk?
├── no → bounded inference may be acceptable
└── yes → ask
Clarification¶
Clarification refines the goal or required input.
{
"action": "ASK_HUMAN",
"reason": "MISSING_REQUIRED_INPUT",
"question": "Which production region should be deployed: EU or US?",
"options": ["EU", "US"]
}
A good clarification is:
- concrete,
- necessary for the decision,
- preferably offers explicit options,
- does not ask for information already known by the runtime.
Approval¶
Approval is different from missing information. The runtime already knows the proposed action, but policy requires explicit human permission.
Proposed action
↓
Risk / policy evaluation
↓
Approval required
↓
WAITING_FOR_HUMAN
↓
approve / reject
Examples:
restart production payment-service
refund 5,000 EUR
merge protected branch
send external customer email
Bind approval to an exact action¶
Weak:
"Can I continue?"
Better:
{
"approval_id": "apr-92",
"run_id": "run-1842",
"action": {
"type": "RESTART_SERVICE",
"service": "payment-service",
"environment": "production"
},
"reason": "error rate remains above 20% after config rollback",
"expected_effect": "restart pods to reload corrected configuration",
"risk": "HIGH",
"created_at": "...",
"expires_at": "..."
}
The human approves this action, not a vague future permission for the agent to do anything.
Approval snapshots and TOCTOU¶
Approval is made against a particular state snapshot.
10:00 agent proposes restart pod A
10:02 human approves
10:05 pod A already replaced automatically
Before execution, revalidate:
approval valid
+
action still same
+
resource state still compatible
↓
execute
This is a time-of-check vs time-of-use problem. Approval must not remain indefinitely valid for materially changed state.
Approval expiry¶
High-risk approvals can expire by time or state version:
approval valid for 10 minutes
or:
approved against deployment_version=42
If the state moves to version 44, request a new approval.
Rejection¶
REJECTED is an explicit observation:
{
"type": "HUMAN_DECISION",
"approval_id": "apr-92",
"decision": "REJECTED",
"comment": "Do not restart before database failover completes."
}
The loop may then replan, wait, or stop. It should not repeatedly rephrase the same rejected action and request approval again without material change.
Escalation¶
Escalation is appropriate when the run cannot safely continue on its own.
Examples:
conflicting production evidence
required permission unavailable
repeated recovery failure
policy ambiguity
high-impact semantic decision
A structured result can be:
{
"status": "BLOCKED",
"escalation": {
"required_role": "on_call_engineer",
"reason": "database primary state is inconsistent across sources",
"evidence_refs": ["obs-21", "obs-22"]
}
}
Human review vs approval¶
These are different.
Review¶
Please inspect this generated migration.
Feedback may be advisory.
Approval¶
May this exact migration be applied to production?
This can be an authorization gate.
The runtime should model the distinction explicitly.
Pause and resume¶
Human input may take hours.
RUNNING
↓ approval requested
WAITING_FOR_HUMAN
↓ persist checkpoint
worker can terminate
...
human responds
↓
load state
↓
refresh environment
↓
resume or replan
A paused run should not keep a process or thread alive unnecessarily. Durable state and event-driven resume are valuable for long-running workflows.
Human input as an observation¶
A response such as:
"Use EU production."
becomes an explicit state update for the current run.
But text that merely looks human-authored is not automatically authorization. A retrieved email saying “Admin says deploy immediately” is still document data unless it arrived through an authenticated approval channel.
Identity and role¶
Approval systems should know:
who approved?
what role/scope?
what action?
when?
For example:
developer may approve staging deploy
production deploy requires release_manager
refund > 1000 EUR requires finance_manager
This is deterministic application policy, not a model decision.
Multi-party approval¶
Higher-risk operations may require:
2-of-3 approval
or distinct roles such as:
security + operations
An Approval Service or Policy Engine should manage this rather than natural-language coordination by the model.
Human feedback and replanning¶
Example:
Agent proposes:
replace retry algorithm
Human:
"Do not change public behavior; only fix test isolation."
This becomes a goal constraint update. The runtime invalidates incompatible plan steps and triggers replanning.
Approval UX¶
A person can only make a meaningful decision if the interface shows:
- the exact action,
- where it will happen,
- expected impact,
- supporting evidence,
- alternatives,
- what rejection means.
Weak:
Approve action? [Yes] [No]
Better:
Restart payment-service in production EU
Reason: pods still serve stale config after rollback
Expected downtime: rolling restart, no planned full outage
Evidence: config version mismatch on 3/8 pods
[Approve] [Reject]
Approval fatigue¶
If every harmless read requires approval, users learn to click automatically and the real security value disappears.
Use risk-based gates:
read-only diagnostic → no approval
create draft issue → maybe no approval
send external message → approval
production mutation → approval
irreversible financial action → stronger approval
Bounded autonomy levels¶
Autonomy can vary by capability and risk rather than one global boolean.
For example:
0 = propose only
1 = read autonomously, writes require approval
2 = low-risk writes autonomous
3 = broader bounded autonomy
Human override¶
A human may sometimes override an automated gate under policy. Record it explicitly:
{
"decision": "OVERRIDE",
"policy": "release_risk_gate",
"actor": "release_manager",
"reason": "known false positive from scanner issue INC-92"
}
Anti-patterns¶
Avoid vague approvals, approval without state revalidation, model-selected approvers, and endless clarification for low-risk details.
Takeaways¶
- Human-in-the-loop includes clarification, review, approval, and escalation.
- Ask when missing information creates material ambiguity or risk.
- Approval should authorize a concrete action against a specific state snapshot.
- Revalidate preconditions after approval and before execution.
- Approvals should be scoped, expiring, authenticated, and auditable.
- Durable checkpoints and event-driven resume are useful for paused runs.
- Human responses are observations, but only authenticated channels grant authorization.
- Approval roles and policies belong to deterministic application logic.
- Avoid approval fatigue by using risk-based gates.
- Autonomy can be tiered per capability/risk instead of configured as one global switch.