guardrails.tool_approvals
Capability-native async tool approval flow.
When an Agentspec marks tools as requiring human approval, this module provides:
ToolApprovalConfig— configuration for the approval flow.ToolApprovalManager— manages approval records locally and waits for human decisions via asyncio.Event (in-process signaling over WebSocket).ToolsGuardrailCapability— a pydantic-aiAbstractCapabilitythat intercepts tool calls needing approval viabefore_tool_execute.
ToolApprovalConfig Objects
@dataclass
class ToolApprovalConfig()
Configuration for the tool-approval flow.
from_spec
@classmethod
def from_spec(cls, spec_config: dict) -> ToolApprovalConfig
Build from Agentspec configuration.
Expected structure::
tool_approval:
tools:
- "deploy.*"
- "send_email"
- "write_file"
timeout: 300
ToolApprovalTimeoutError Objects
class ToolApprovalTimeoutError(GuardrailBlockedError)
Raised when the approval request times out.
ToolApprovalRejectedError Objects
class ToolApprovalRejectedError(GuardrailBlockedError)
Raised when the tool call is rejected by the human.
ToolApprovalExecutionReservationError Objects
class ToolApprovalExecutionReservationError(GuardrailBlockedError)
Raised when an approval cannot be reserved before tool execution.
ToolApprovalManager Objects
class ToolApprovalManager()
Manages tool approval requests using in-process asyncio.Event signaling.
When a tool call needs approval, an approval record is created in the
local in-memory store (visible to WebSocket clients via snapshots).
The manager then blocks on an asyncio.Event until a WebSocket
tool_approval_decision message signals a decision. No HTTP polling
is required — the event is signaled entirely in-process.
close
async def close() -> None
Compatibility no-op for adapter cleanup paths.
Some adapter flows call await approval_manager.close() after
approval rounds. This manager currently owns no external resources,
so shutdown is intentionally a no-op.
requires_approval
def requires_approval(tool_name: str) -> bool
Check whether a tool requires human approval.
request_and_wait
async def request_and_wait(tool_name: str,
tool_args: dict[str, Any],
tool_call_id: str | None = None) -> dict[str, Any]
Create a local approval record and block until a decision arrives.
The decision is delivered by signal_approval_event which is called
from the WebSocket stream loop when a tool_approval_decision message
is received from the frontend.
ToolsGuardrailCapability Objects
@dataclass
class ToolsGuardrailCapability(AbstractCapability[Any])
Capability that gates tool execution behind async human approval.
When a tool requires approval, an in-memory approval record is created and
broadcast to WebSocket clients via snapshot. Execution is suspended on an
asyncio.Event until the frontend sends a tool_approval_decision message,
which signals the event and resumes (or rejects) the tool call.
handle_deferred_tool_calls
async def handle_deferred_tool_calls(
ctx: RunContext[Any], *,
requests: DeferredToolRequests) -> DeferredToolResults | None
Inline-handle deferred approvals that already have a local decision.
This lets pydantic-ai continue the run in a single call when the corresponding approval was already approved/rejected (e.g. from a recent turn or a mirrored WS decision), while unresolved approvals still bubble out as DeferredToolRequests for the stop-the-world flow.