Enforcement v7

When an AI agent runs a tool (like running a SQL query or fetching data), the system needs to decide what permissions that agent has. Instead of giving an agent static permissions when you create it, the system dynamically checks the agent's assigned purpose to figure out which database role to use.

Resolution has three outcomes

An agent's purpose is resolved to a role once per agent_converse call, not once per tool call — every tool the agent invokes during that call runs under the same resolved role:

  • No purpose set — every tool call runs as whoever called agent_converse.
  • A live purpose — every tool call runs as the role the purpose resolves to.
  • A retired or unregistered purpose — the invocation is refused. It never falls back to running as the caller. See Purposes for why.

Resolving the purpose to a role name (above) and assuming that role are different operations, on different schedules: the role is resolved once per agent_converse call, whereas the role is assumed every time a tool call is made. Each time the role switch is made, checks run to verify that the role exists and that the caller of `agent_converse is a member of the role.

What's enforced

Call pathEnforced?
Native tools (built-in operations like catalog discovery)Yes
Custom SQL tools (aidb.create_sql_tool)Yes
MCP toolsNo — an MCP call is outbound HTTP, with no local Postgres identity to switch
aidb.run_tool (direct tool invocation, bypassing an agent)No — runs as the caller's own session role

Delegation

When one agent delegates to another, the delegating agent's resolved role always wins, and the delegate's own purpose is never even read. A restricted agent can't use delegation to reach a role less restrictive than its own — the same rule that already applies to read-only mode: both propagate from the delegating call to the delegate's, unconditionally overriding whatever the delegate itself would otherwise use.

A refusal doesn't end the conversation

If the invocation of a tool fails due to governance enforcement, agent_converse still returns normally. The refusal lands in the tool's own response, not in the converse call's error column:

SELECT response_payload->>'error'
FROM aidb_internal.action_log
WHERE task_id = (
    SELECT id FROM aidb_internal.agent_task_queue
    WHERE conversation_id = 'your-conversation-id' ORDER BY created_at DESC LIMIT 1
)
AND action_type = 'tool_response'
ORDER BY created_at LIMIT 1;

The SET ROLE caveat

The resolved role is fixed for the duration of a tool call: nothing the tool's SQL does — SET ROLE, RESET ROLE, SET SESSION AUTHORIZATION, DISCARD, and similar session-state statements — can change it or escape it, even to a sibling role the purpose's role belongs to. AIDB rejects a tool body that consists only of RESET ROLE, RESET ALL, or DISCARD ALL before execution. Any other attempt fails in Postgres with:

ERROR:  cannot set parameter "role" within security-definer function

Both cases are recorded as escalation_blocked. See Decision records.

Ordinary role inheritance still works, though: if the purpose's role is a member of a sibling role, tool calls pick up whatever that sibling grants, no explicit switch needed. So design purpose roles to inherit the privileges they need directly, and avoid NOINHERIT — a NOINHERIT role that expects a tool to SET ROLE into a sibling for its real permissions won't work here.