Gate tool calls (approval hook)¶
An agent with tools can do real things: run skill scripts, execute_python, write files, call a
remote MCP service. The tool-approval hook lets you decide, per call, whether a tool may run.
It's optional and off by default (everything is approved), so existing code is unchanged until you
set a policy.
The policy¶
A policy is a callable (tool_name, arguments) -> bool run right before each tool invocation.
Return False to block the call: the tool does not run, and a refusal tool message
("Tool '<name>' was not approved.") is appended so the model sees it and can react. The type alias
ToolApproval and the default approve_all are exported from aimu.
import aimu
RISKY = {"execute_python", "write_file"}
def confirm_risky(name: str, arguments: dict) -> bool:
if name not in RISKY:
return True
answer = input(f"Allow {name}({arguments})? [y/N] ")
return answer.strip().lower() == "y"
Use it¶
Approval is part of the agent's tool-loop, so it lives on the Agent — a constructor field and a
per-run override, mirroring deps=. (Tools are only ever executed by an agent, so there is no
bare-client approval path.)
client = aimu.client("ollama:qwen3:8b")
agent = aimu.agents.Agent(client, tools=[my_tool], tool_approval=confirm_risky)
agent.run("do the thing") # agent-level default
agent.run("do it", tool_approval=confirm_risky) # per-run override
The policy sees the tool name and the model-supplied arguments, so you can gate only the risky
tools and approve the rest. It covers every dispatch path the engine runs: non-streaming, streaming,
and concurrent (when the agent sets concurrent_tool_calls=True).
Async¶
On aimu.aio the policy may be a coroutine function (it is awaited), so it can do async I/O such as
asking the user over a chat channel:
from aimu import aio
async def confirm(name, arguments):
if name not in RISKY:
return True
return await ask_user_yes_no(f"Allow {name}?")
agent = aio.Agent(aio.client("ollama:qwen3:8b"), tools=[my_tool], tool_approval=confirm)
A sync agent requires a sync policy; handing it a coroutine raises a clear error pointing at the
aimu.aio surface.
Worked example¶
The personal-assistant example gates its full-access
add_skill_script tool with a terminal y/n prompt by default (see its CONFIRM_BEFORE set), so the
user confirms before the assistant writes and runs code.
See also¶
- Add a custom tool and Use MCP tools: the tools a policy gates
- Build a personal assistant: the approval demo in context