# Permissions & budgets

Enforce tool policies and bound resource use.

## Permission levels

Tools use `ALLOW` (execute), `ASK` (request approval), or `DENY` (reject).
Omitting the permission inherits the manager's default, which is `ALLOW`.
This applies to constructor tools, later registrations, and direct
`ToolRegistry.execute()` calls without a custom manager. Built-in helpers also
inherit the manager default, so file writes, shell commands, and URL fetching
use ALLOW under a default agent. Select ASK explicitly when approval is
required. Explicit MCP server policies remain in effect. Per-tool overrides take
precedence over explicit registration permissions and the manager default.
`DENY` also overrides a previous session grant.

```python
from harnessx import PermissionLevel

# At registration
@agent.tools.register(permission=PermissionLevel.ALLOW)
def add(a: int, b: int) -> int: ...

# Or override later
agent.permissions.set_permission("run_bash", PermissionLevel.ASK)
agent.permissions.grant_session("read_file")   # pre-approve for this session
```

## Approval callbacks

Direct execution handles `ASK` through a sync or async callback. Without a
callback it denies the call; the SDK never implicitly reads stdin.

```python
from harnessx import Agent, PermissionLevel, PermissionManager, ToolCall, ToolDefinition

async def approve(call: ToolCall, definition: ToolDefinition) -> bool:
    return await approval_ui.request(call.name, call.input)

agent = Agent(permissions=PermissionManager(
    default_level=PermissionLevel.ASK,
    approval_callback=approve,
))
```

Setting `default_level=PermissionLevel.ASK` requests approval for tools without
an explicit permission. Supplying only `approval_callback` leaves the default
at `ALLOW`; the callback handles tools explicitly configured as `ASK`.

Terminal apps can use `CliPermissionManager`, an opt-in manager that defaults to
`ASK` and prompts on the console:

```python
from harnessx import Agent, CliPermissionManager

agent = Agent(tools=["bash"], permissions=CliPermissionManager())
```

## Durable approvals

Durable runtimes persist approval requests instead of calling back. The run
returns `needs_input`, and `await runtime.approve(pending, resume=True)` or
`await runtime.decline(pending, resume=True)` records the decision and finishes
the run. A declined tool sees the result `"Permission denied"`. See
[Durable agent runs](../durable-agent-runs/#approvals-and-uncertain-tools).

## Budgets

`AgentConfig(limits=Limits(...))` bounds iterations, context, and cost. A
reached cost limit returns a failed `RunResult` with error details. A reached
iteration limit asks the model for a final answer and completes with
`result.limited` true, unless `final_answer_on_limit=False` makes it fail.
`agent.guardrails.usage_summary` reports accumulated usage, including what
delegated specialists spent. [Agents & the
loop](../agents/) lists every `Limits` field and its default. Transient model
failures are governed separately; see [Retries & fallback
models](../retries-and-fallbacks/).
