Metadata-Version: 2.5
Name: agentapprovalprotocol
Version: 0.1.0
Summary: Put an AI agent's tool calls behind an Agent Approval Protocol (AAP) approval provider.
Project-URL: Homepage, https://agentapprovalprotocol.io
Project-URL: Source, https://github.com/agentapprovalprotocol/agentapprovalprotocol
License-Expression: Apache-2.0
Keywords: aap,agents,ai,approval,human-in-the-loop
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: langgraph
Requires-Dist: langchain>=1.0; extra == 'langgraph'
Requires-Dist: langgraph>=1.0; extra == 'langgraph'
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai-slim>=2.55; (python_full_version >= '3.11') and extra == 'pydantic-ai'
Description-Content-Type: text/markdown

# Agent Approval Protocol for Python

Put an AI agent's tool calls behind an [AAP](https://agentapprovalprotocol.io) approval provider. Each call waits for the provider's decision, runs only if approved, and runs at most once.

```sh
pip install agentapprovalprotocol
```

## Check a tool call

```python
from agentapprovalprotocol import Call, Client, Gate

gate = Gate(Client("https://approvals.example.com/api/aap", token))

with gate.guard(Call(key=f"{run_id}:{tool_call.id}", tool=tool_call.name, arguments=tool_call.args)) as verdict:
    if verdict:
        result = run_tool(tool_call)
    else:
        result = verdict.reason  # return this to the model as the tool result
```

`key` must identify the tool call within its run, and stay the same if the call is seen again after a retry or resume. `guard` reports whether the tool succeeded when the block exits.

`AsyncGate` and `AsyncClient` provide the same API for `asyncio`.

## Pause instead of waiting

With `mode="async"`, `check` returns `Pending` when there is no decision yet, so the run can pause and the process can exit. Mount a `Receiver` where the provider can reach it. When a decision arrives it wakes the run, and the gate checks the decision again before anything runs.

Framework integrations handle pausing and resuming for you.

## LangChain, LangGraph and Deep Agents

```sh
pip install "agentapprovalprotocol[langgraph]"
```

```python
from agentapprovalprotocol.langgraph import ApprovalMiddleware

approvals = ApprovalMiddleware("https://approvals.example.com/api/aap", token)
agent = create_agent(model, tools=tools, middleware=[approvals])
```

Every tool call the agent makes goes to the provider. A denied call returns the reason to the model as the tool result.

For Deep Agents, also add the middleware to every subagent:

```python
from agentapprovalprotocol.langgraph import deep_agent_subagents

agent = create_deep_agent(model, tools=tools, middleware=[approvals], subagents=deep_agent_subagents(approvals, my_subagents))
```

To pause instead of waiting, use `ApprovalMiddleware(..., mode="async")` with a checkpointer, and mount a `Receiver` with `resumers={"langgraph": resumer(agent)}`. On LangSmith Deployment, use `deployment_resumer(get_client())` in a custom route instead. Resuming a thread any other way cannot approve a call: the middleware asks the provider again.

## Pydantic AI

```sh
pip install "agentapprovalprotocol[pydantic-ai]"
```

Needs Python 3.11 or later.

```python
from agentapprovalprotocol.pydantic_ai import ApprovalCapability

approvals = ApprovalCapability("https://approvals.example.com/api/aap", token)
agent = Agent(model, tools=tools, capabilities=[approvals])
```

Every tool call the agent makes goes to the provider, including MCP and other toolset tools. A denied call returns the reason to the model as a failed tool result. Add the same capability to every agent you run as a sub-agent.

Tools that run at the model provider, such as native web search or code execution, cannot be sent for approval. A run that includes one stops with an error. Use a local tool instead.

To pause instead of waiting, use `ApprovalCapability(..., mode="async")` and add `DeferredToolRequests` to the agent's `output_type`. When a run ends with `DeferredToolRequests`, save `result.conversation`. Mount a `Receiver` with `resumers={"pydantic-ai": resumer(agent, load=load, save=save)}`, where `load(conversation_id)` returns the saved conversation and `save(conversation)` stores the resumed one. Calls inside a sub-agent wait instead of pausing. Continuing a conversation any other way cannot approve a call: the capability asks the provider again, and an approval with edited arguments is refused.
