Human Approval
Confirmation, user input, external execution, and approval flows in three demo agents.
Three demo agents exercise the HITL surface end-to-end, each showing a different combination of patterns over the same primitives.
| Agent | What it shows |
|---|---|
| Helpdesk | All three pause patterns + PII + injection guardrails + audit hook |
| Approvals | The @approval decorator with audit trail |
| Feedback | UserFeedbackTools and UserControlFlowTools for structured questions |
Helpdesk: the full HITL toolkit
1from kern.agent import Agent2from kern.guardrails import OpenAIModerationGuardrail, PIIDetectionGuardrail, PromptInjectionGuardrail3from kern.tools.user_feedback import UserFeedbackTools45helpdesk = Agent(6 id="helpdesk",7 model=MODEL,8 db=agent_db,9 tools=[restart_service, create_support_ticket, run_diagnostic, UserFeedbackTools()],10 pre_hooks=[11 OpenAIModerationGuardrail(),12 PIIDetectionGuardrail(),13 PromptInjectionGuardrail(),14 ],15 post_hooks=[output_guardrail, audit_log],16)Where each pattern shows up:
| Tool | HITL pattern | Why |
|---|---|---|
restart_service | requires_confirmation=True | Restarting prod is irreversible. Human approves first. |
create_support_ticket | requires_user_input=True | Need details from the user before creating. |
run_diagnostic | external_execution=True | Diagnostic runs in another system. The agent gets the result back. |
UserFeedbackTools() | Structured question to user mid-run | "Which service do you want to restart?" |
Pre-hooks catch bad input before the model sees it (PII, injection, moderation), and post-hooks catch bad output and write the audit log. The agent never sees raw PII, and every run lands in the audit log.
Approvals: compliance gates
1from kern.tools import tool2from kern.approval.decorator import approval34@approval(type="required")5@tool(requires_confirmation=True)6def process_refund(customer_id: str, amount: float, reason: str) -> str:7 return charge_refund(customer_id, amount)89@approval(type="audit")10@tool11def export_customer_data(customer_id: str) -> str:12 return get_customer_data(customer_id)@approval(type="required") blocks the run until a human approves. Audit log captures both the request and the decision.
@approval(type="audit") runs the tool but logs to the audit trail asynchronously. Used when policy says "this needs to be tracked, not gated."
Feedback: structured questions mid-run
1from kern.tools.user_feedback import UserFeedbackTools, UserControlFlowTools23feedback = Agent(4 id="feedback",5 tools=[UserFeedbackTools(), UserControlFlowTools()],6)UserFeedbackTools lets the agent pause and ask a structured question:
Agent: I need to know your team size. Please pick one: [1-5, 6-20, 21-100, 100+]
The user picks. The run resumes with the answer in scope. No prompt engineering, no hand-rolled flow control.
UserControlFlowTools adds branching. The agent can offer choices and route based on the user's pick:
Agent: Want to (a) keep the current settings, (b) reset to defaults, or (c) customize?
Pre-hooks for input safety
PII detection and prompt injection guardrails are pre-hooks that run before the model sees the user message:
1from kern.guardrails import PIIDetectionGuardrail, PromptInjectionGuardrail23agent = Agent(4 pre_hooks=[5 PIIDetectionGuardrail(6 mask_pii=True,7 enable_ssn_check=True,8 enable_credit_card_check=True,9 enable_email_check=True,10 enable_phone_check=True,11 ),12 PromptInjectionGuardrail(),13 ],14)PII gets masked in place, injection attempts get blocked outright, and the two are commonly used together.
Post-hooks for output safety
Output guardrails and audit logs run after the model produces output. The Helpdesk agent demonstrates both:
1def output_guardrail(run_output, agent):2 """Block responses that accidentally leak sensitive patterns."""3 import re4 sensitive = [r"sk-[a-zA-Z0-9]{20,}", r"postgres://[^\s]+"]5 for pattern in sensitive:6 if re.search(pattern, run_output.content or ""):7 run_output.content = "I'm unable to provide that information."8 return910def audit_log(run_output, agent):11 """Audit trail for compliance."""12 print(f"[AUDIT] Agent={agent.name} Status={run_output.event}")For audit logs that shouldn't gate the response, use @hook(run_in_background=True). See Human-in-the-Loop for the full pattern.
See it in action
1# Helpdesk2@Helpdesk restart the auth service # confirmation pause3@Helpdesk file a ticket for slow API # user input pause4@Helpdesk run a network diagnostic # external execution pause56# Approvals7@Approvals refund customer ACME-123 for $500 # blocked until approved8@Approvals export customer data for ACME-123 # runs, audit-logged910# Feedback11@Feedback help me pick a deployment region # structured questionsSources: agents/helpdesk/, agents/approvals/, agents/feedback/