
Ag2 Hitl
- 36 installs
- 8 repo stars
- Updated July 27, 2026
- ag2ai/ag2-skills
ag2-hitl is a Claude Code skill that pauses an AG2 beta Agent to collect human input or gate a tool call behind human approval.
About
ag2-hitl is a Claude Code skill that adds human-in-the-loop control to an AG2 beta Agent. It pauses the agent mid-run to collect typed human input via context.input() with a hitl_hook, and gates specific tool calls behind approval_required() middleware. A developer uses it when the agent should ask for confirmation, request missing information, or require human approval before running sensitive, irreversible or expensive tool calls such as sending emails, deleting records or payments.
- Pauses an AG2 beta Agent mid-run to collect human input via context.input() and a hitl_hook
- Gates sensitive tool calls behind approval_required() middleware with approve/deny/always
- Covers both open-question input and per-tool approval, including custom prompts and timeouts
Ag2 Hitl by the numbers
- 36 all-time installs (skills.sh)
- Ranked #8,638 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
ag2-hitl capabilities & compatibility
Free skill; running the agent needs an LLM provider API key such as OpenAI.
- Capabilities
- human in the loop · tool approval · mid run input · agent guardrails
- Works with
- openai
- Use cases
- orchestration
- Pricing
- Bring your own API key
What ag2-hitl says it does
Pause an AG2 beta `Agent` mid-run to collect human input via `context.input()`, or gate a tool call with `approval_required()` middleware.
npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-hitlAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 36 |
|---|---|
| repo stars | ★ 8 |
| Last updated | July 27, 2026 |
| Repository | ag2ai/ag2-skills ↗ |
What it does
Add human confirmation, input requests and tool-call approval gates to an AG2 beta Agent.
Who is it for?
Developers who need an AG2 agent to ask for confirmation, request missing info, or require approval before risky tool calls.
Skip if: Fully autonomous agents that never need human confirmation or input.
When should I use this skill?
The agent should ask for confirmation, request missing info, or require approval for a sensitive or irreversible tool call.
What you get
The agent pauses for typed human input or explicit approval before risky tool calls proceed.
By the numbers
- 2 HITL mechanisms (context.input and approval_required)
- default approval timeout is 30 seconds
Files
Human-in-the-loop
When to use
- The agent should ask for confirmation before doing something risky.
- The agent needs information from the user mid-conversation (a password, an API key, missing context).
- A specific tool call should require human approval before it runs (irreversible / expensive / sensitive).
- Quality assurance — show a draft, get human edits/approval before finalising.
Two distinct mechanisms — pick by intent:
| Need | Use |
|---|---|
| Tool asks an open question and waits for a typed answer | context.input() from inside the tool + hitl_hook on the agent |
| Approve / deny a specific tool call before its body runs | approval_required() tool middleware |
Pattern 1 — context.input() for open questions
A tool requests input via Context.input(message, timeout=...). The agent must have a hitl_hook that knows how to collect that input.
from autogen.beta import Agent, Context, tool
from autogen.beta.events import HumanInputRequest, HumanMessage
@tool
async def execute_query(context: Context) -> str:
answer = await context.input(
"Are you sure you want to run this query? (yes/no)",
timeout=60.0,
)
if answer.strip().lower() != "yes":
return "Query cancelled."
return "Query executed successfully."
def hitl_hook(event: HumanInputRequest) -> HumanMessage:
print(f"Agent asks: {event.content}")
return HumanMessage(content=input("Your answer: "))
agent = Agent("dba", tools=[execute_query], hitl_hook=hitl_hook)The hook receives a HumanInputRequest (the prompt is in event.content) and returns either a HumanMessage or a plain str (the framework wraps a str via HumanMessage.ensure_message). Both def and async def hooks are supported.
You can also register the hook after construction:
agent = Agent("dba", tools=[execute_query])
@agent.hitl_hook
async def async_hitl_hook(event: HumanInputRequest) -> HumanMessage:
answer = await collect_from_ui(event.content)
return HumanMessage(content=answer)The decorator overrides any hook set in the constructor — but if one was already set (e.g. via the constructor), applying @agent.hitl_hook emits a RuntimeWarning ("You already set HITL hook, provided value overrides it"). Set the hook in exactly one place to avoid the warning.
The hook participates in dependency injection — Context, Inject, Variable, Depends work the same as in tools.
If context.input() is called and no hook is registered, the framework raises HumanInputNotProvidedError.
Pattern 2 — approval_required() for specific tool calls
Gate a single tool with the built-in approval middleware. The user is prompted before the tool body runs and can approve or deny.
from autogen.beta import Agent, tool
from autogen.beta.config import OpenAIConfig
from autogen.beta.middleware import approval_required
@tool(middleware=[approval_required()])
def delete_account(user_id: str) -> str:
"""Deletes a user account by ID permanently."""
return f"Account {user_id} deleted."
agent = Agent(
"support",
config=OpenAIConfig(model="gpt-4o-mini"),
tools=[delete_account],
hitl_hook=lambda event: input(event.content),
)When the agent calls delete_account, the user sees (with the default allow_always=True):
Agent wants to call the tool:
`delete_account`, {"user_id": "abc-123"}
Please approve or deny this request.
Y/N/Always?The answer is lowercased before matching. y, yes, or 1 approve this one call; always approves this call and all subsequent calls of the same tool in the same context (it sets a per-context bypass flag). Anything else denies it; the agent receives denied_message (default "User denied the tool call request") and can adjust. The default timeout is 30 seconds. Set allow_always=False to drop the "Always" option (the prompt then shows just Y/N?).
approval_required() calls context.input() under the hood, so it also requires a `hitl_hook` — without one you'll get a runtime error.
Custom prompt
@tool(middleware=[approval_required(
message="⚠️ Run `{tool_name}` with {tool_arguments}? (y/n)",
denied_message="Operation blocked by user.",
)])
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to the given address."""
...{tool_name} and {tool_arguments} are interpolated.
Pairing both patterns
For a tool that both gathers input mid-run and requires approval:
@tool(middleware=[approval_required()])
async def schedule_report(name: str, context: Context) -> str:
"""Schedule a report — asks the user for the cadence, then runs after approval."""
cadence = await context.input("How often? (daily / weekly / monthly)")
return f"Scheduled '{name}' on {cadence} cadence."The approval middleware runs first (outermost). Once approved, the tool body executes and context.input() triggers a second human interaction.
Going deeper
- Source docs:
website/docs/beta/context/human_in_the_loop.mdx(context.input,hitl_hook),website/docs/beta/tools/approval_required.mdx(approval_requiredmiddleware). - Tool middleware in general —
website/docs/beta/tools/tool_middleware.mdx. See alsoag2-middlewarefor agent-wide HITL interception viaBaseMiddleware.on_human_input(). - HITL hooks support dependency injection identically to tools — see
../ag2-add-custom-tool/references/dependency_injection.md.
Common pitfalls
- `approval_required()` without a `hitl_hook` — the middleware calls
context.input(), so the agent needs a hook. You'll seeHumanInputNotProvidedErrorotherwise. - Forgetting to handle the denial path —
context.input()returns whatever the hook returns. If you only branch on "yes", any other answer (including silence/default) lets the operation continue. Always validate. - Sync `input()` in an async UI —
input()blocks the event loop. Use an async hook (async def) and an async input collector (web socket, message queue) for any non-CLI app. - No timeout —
context.input(prompt)can wait forever. Passtimeout=60.0(seconds) for any production path. - Decorator hook overrides constructor hook — if you set both, the decorator wins and emits a
RuntimeWarning. Pick one place. - Expecting `HumanMessage` to flow into the conversation history automatically — it does for the requesting tool's return value, but mid-run inputs collected via
ctx.input()are not separate user turns. They live in the tool's scope.
Related skills
FAQ
How do the two HITL mechanisms differ?
context.input() with a hitl_hook asks an open question and waits for a typed answer, while approval_required() middleware approves or denies a specific tool call before it runs.
Does approval_required() need a hitl_hook?
Yes. approval_required() calls context.input() under the hood, so it also requires a hitl_hook or you get a runtime error.