Human-in-the-Loop

Pause agents for approval before executing sensitive tools.

Human-in-the-Loop solves three problems that appear when agents move from answering questions to taking actions:

  1. Irreversible operations. Sending an email, deleting a record, or posting to a channel cannot be undone. A human checkpoint prevents mistakes that require cleanup or apologies.

  2. Missing context. The agent knows what action to take but lacks a critical detail. A deployment needs a target environment. A booking needs a budget. Rather than guessing, the agent pauses and asks.

  3. Audit trail. Sensitive operations need accountability. Slack threads already contain the discussion that led to the action. Rendering the approval in the same thread keeps the decision and its context together.

Agent pausing for approval before creating a calendar event and sending an email

Quick start

The Slack interface renders ordinary HITL pauses as interactive cards in the thread. Users can confirm, deny, or provide input. These buttons are not an admin-role authorization system; required @approval records use the separate approval flow.

Follow the Slack setup guide, including interactivity URLs and signing credentials. Install uv pip install -U "agno[os,slack]" openai and set OPENAI_API_KEY in the server terminal. Save one tab as its named file and run it, for example python agent.py. The email tool below is a simulation.

agent.py
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS
from agno.os.interfaces.slack import Slack
from agno.tools import tool

@tool(requires_confirmation=True)
def send_email(to: str, subject: str, body: str) -> str:
    """Simulate an email after confirmation."""
    return f"Simulation: email to {to} with subject {subject}; nothing was sent."

db = SqliteDb(db_file="agent.db")

agent = Agent(
    name="Assistant",
    model=OpenAIResponses(id="gpt-5.4"),
    tools=[send_email],
    db=db,
)

agent_os = AgentOS(
    agents=[agent],
    db=db,
    interfaces=[Slack(agent=agent)],
)
app = agent_os.get_app()

if __name__ == "__main__":
    agent_os.serve(app="agent:app", reload=True)

HITL requires a database to persist paused runs.

Pause types

Pause typeSlack cardTrigger
ConfirmationApprove/Deny buttons@tool(requires_confirmation=True)
User inputText fields or dropdowns@tool(requires_user_input=True)
External executionResult field, submitted back to the run@tool(external_execution=True)
User feedbackQuestion forms with checkboxes or dropdownsUserFeedbackTools()

See the HITL overview for details on each pause type.

Next steps