Permissions¶
Permission policies for agent backends. See the Permissions guide.
agentflow.backends.base
¶
Backend protocols and permission policies.
A backend drives one underlying engine — an agent process (Kiro, Codex,
Claude Code) or an LLM endpoint (Ollama, OpenAI-compatible). The graph engine
depends only on :class:Backend; user code that needs agent- or LLM-specific
features constructs the concrete subtype and gets the narrowed method.
This module imports only :mod:agentflow.events and
:mod:agentflow.errors — never the graph engine. Backends are a leaf layer.
PermissionPolicy
¶
Bases: Protocol
Decides how to answer an agent's permission request.
Source code in agentflow/backends/base.py
AllowAll
¶
Approve every request. Auto-approve — only for a trusted local sandbox.
Source code in agentflow/backends/base.py
DenyAll
¶
ToolAllowlist
¶
Approve only requests whose tool is on the allowlist; deny the rest.
A pragmatic least-privilege default: name the tools an agent may use and
everything else is refused. Matching is exact on the request's tool.
policy = ToolAllowlist({"read_file", "list_directory"})
fallback decides unlisted tools (default :class:DenyAll); pass
:class:Interactive to ask a human for anything not pre-approved.
Source code in agentflow/backends/base.py
Interactive
¶
Escalate a permission request to a human via an engine interrupt.
When resolved inside a running graph, this calls ctx.interrupt(req) on
the node's :class:~agentflow.runtime.Context, which suspends the run and
persists an interrupted checkpoint. The human inspects the request and
calls app.resume(thread, value=...); the resumed value becomes this
policy's :class:PermissionDecision. A plain :class:Allow/:class:Deny
or a bare truthy/None value is accepted and normalized.
The node's context is found via :data:agentflow.runtime.current_context,
which the runtime sets around each node call. Because a node and any
backend generator it iterates share one task, the policy — invoked while
the node consumes the backend's event stream — sees the right context.
Outside a graph run (no current context) it falls back to raising
:class:~agentflow.errors.InterruptError so misuse fails loudly.
Source code in agentflow/backends/base.py
Callback
¶
Ask a human (or any resolver) inline, without interrupting the graph.
Unlike :class:Interactive — which suspends the whole run via
ctx.interrupt and requires a separate resume() — this awaits a
caller-supplied async function within the same turn and answers the
agent's permission request immediately. That matters for a persistent
session backend (Kiro's ACP): the agent is holding one turn open waiting
for the decision, so the answer must go back in that turn rather than
abandoning it and starting a new one.
resolver is async (PermissionRequest) -> decision where the
decision is an :class:Allow/:class:Deny, or a value normalized the same
way as a resume value ("y"/"n", None, an option id, ...).
async def ask(req):
answer = await asyncio.to_thread(input, f"allow {req.tool}? [y/N] ")
return answer
backend = KiroBackend("vibe", permission=Callback(ask))