Audit Approval User Input
Audit approval with user input: @approval(type="audit") + @tool(requires_user_input=True).
"""
Audit Approval User Input
=============================
Audit approval with user input: @approval(type="audit") + @tool(requires_user_input=True).
"""
import os
from agno.agent import Agent
from agno.approval import approval
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools import tool
DB_FILE = "tmp/approvals_test.db"
@approval(type="audit")
@tool(requires_user_input=True, user_input_fields=["account"])
def transfer_funds(amount: float, account: str) -> str:
"""Transfer funds to an account.
Args:
amount (float): The amount to transfer.
account (str): The destination account (provided by user).
Returns:
str: Confirmation of the transfer.
"""
return f"Transferred ${amount} to {account}"
# ---------------------------------------------------------------------------
# Create Agent
# ---------------------------------------------------------------------------
db = SqliteDb(
db_file=DB_FILE, session_table="agent_sessions", approvals_table="approvals"
)
agent = Agent(
model=OpenAIResponses(id="gpt-5-mini"),
tools=[transfer_funds],
markdown=True,
db=db,
)
# ---------------------------------------------------------------------------
# Run Agent
# ---------------------------------------------------------------------------
if __name__ == "__main__":
# Clean up from previous runs
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
os.makedirs("tmp", exist_ok=True)
# Re-create after cleanup
db = SqliteDb(
db_file=DB_FILE, session_table="agent_sessions", approvals_table="approvals"
)
agent = Agent(
model=OpenAIResponses(id="gpt-5-mini"),
tools=[transfer_funds],
markdown=True,
db=db,
)
# Step 1: Run - agent will pause because the tool requires user input
print("--- Step 1: Running agent (expects pause) ---")
run_response = agent.run("Transfer $250 to my savings account.")
print(f"Run status: {run_response.status}")
assert run_response.is_paused, f"Expected paused, got {run_response.status}"
print("Agent paused as expected.")
# Step 2: Verify no logged approvals exist yet (audit approval creates records AFTER resolution)
print("\n--- Step 2: Verifying no logged approvals yet ---")
approvals_list, total = db.get_approvals(approval_type="audit")
print(f"Logged approvals before resolution: {total}")
assert total == 0, f"Expected 0 logged approvals before resolution, got {total}"
print("No logged approvals yet (as expected).")
# Step 3: Provide user input and continue
print("\n--- Step 3: Providing user input and continuing ---")
for requirement in run_response.active_requirements:
if requirement.needs_user_input:
print(
f" Providing user input for tool: {requirement.tool_execution.tool_name}"
)
requirement.provide_user_input({"account": "SAVINGS-9876"})
run_response = agent.continue_run(
run_id=run_response.run_id,
requirements=run_response.requirements,
)
print(f"Run status after continue: {run_response.status}")
assert not run_response.is_paused, "Expected run to complete, but it's still paused"
# Step 4: Verify logged approval record was created after resolution
print("\n--- Step 4: Verifying logged approval record ---")
approvals_list, total = db.get_approvals(approval_type="audit")
print(f"Logged approvals after resolution: {total}")
assert total >= 1, f"Expected at least 1 logged approval, got {total}"
approval_record = approvals_list[0]
print(f" Approval ID: {approval_record['id']}")
print(f" Status: {approval_record['status']}")
print(f" Type: {approval_record['approval_type']}")
assert approval_record["status"] == "approved", (
f"Expected 'approved', got {approval_record['status']}"
)
assert approval_record["approval_type"] == "audit", (
f"Expected 'audit', got {approval_record['approval_type']}"
)
print("Logged approval record verified.")
# Step 5: Verify total state
print("\n--- Step 5: Verifying final state ---")
pending_count = db.get_pending_approval_count()
print(f"Pending approvals: {pending_count}")
assert pending_count == 0, f"Expected 0 pending approvals, got {pending_count}"
all_approvals, all_total = db.get_approvals(approval_type="audit")
print(f"Total logged approvals: {all_total}")
assert all_total == 1, f"Expected 1 logged approval, got {all_total}"
print("\n--- All checks passed! ---")
print(f"\nAgent output (truncated): {str(run_response.content)[:200]}...")These examples simulate approval decisions with fixed confirmations, rejections, user input, or external results. The deployment, email, payment, and scan demonstrations use placeholder actions. The Hacker News examples make real HTTP requests after confirmation. In an application, obtain the authorized user's decision before updating a requirement or approval record.
There are two continuation paths:
- Explicit requirements: update the paused run's requirements and pass them to
continue_run()oracontinue_run(). This supplies the decision directly; a pending database approval record does not gate that path. Keep its audit record in sync. - Database resolution: resolve the approval record first, then continue by run ID without supplying requirements. Required approvals are checked before execution. See Approval Post Hook for this pattern and its resolved approval metadata.
With @approval(type="audit"), records describe HITL resolution, including rejection and externally supplied results. A record does not establish that the tool body executed or that a real person supplied the decision.
Run the Example
Set up your virtual environment
uv venv --python 3.12
source .venv/bin/activateUse a disposable example directory
This script deletes tmp/approvals_test.db if it exists, including its saved sessions and approvals. Save and run it in a separate disposable directory. If changing the database path, use a new file dedicated to this example.
Install dependencies
uv pip install -U agno openai sqlalchemyExport your OpenAI API key
export OPENAI_API_KEY="your_openai_api_key_here"Run the example
Save the code above as audit_approval_user_input.py, then run:
python audit_approval_user_input.pyFull source: cookbook/02_agents/11_approvals/audit_approval_user_input.py