Tool Authorization Model
Version: 0.1 · July 2026 · Status: OS-level canonical Supersedes: (none — first version) Related documents: Agent Spec · Agent Runtime Policy Spec · Orchestration & Runtime Strategy · n8n Implementation Guide · LangGraph Runtime Implementation Guide · ADR-OS-001 · POC Workflow WF-OS-POC-001 · Event Taxonomy Standard
Purpose
This document answers: what is a tool set and how does tool authorization work in Synedrix OS?
The Agent Runtime Policy Spec is the authoritative schema reference — it defines the tool_authorization_policy fields, the three-layer model summary, MCP/A2A policies, and observability events. Read that document to configure tool authorization for a specific agent.
Read this document to understand why the model is structured the way it is, what the key concepts mean, how resolution works end-to-end, and where enforcement happens at runtime.
1. What is a tool
A tool is a named, typed capability that an agent may invoke during a reasoning step to read data, write state, or call an external system. From the agent’s perspective a tool is a function: it accepts structured input and returns structured output (or an error).
Tools are distinct from LLM inference. The LLM decides whether to call a tool and with what arguments; the tool executes the call and returns a result. The LLM never directly touches an external system — it can only do so through an authorized tool.
Tool ID convention
Every tool in Synedrix OS is identified by a canonical tool_id following the pattern:
tool:<provider>-<verb>Where <provider> identifies the system or adapter (supabase, questdb, redis, searxng, external-broker) and <verb> is a lowercase action word (read, write, query, search, send, delete).
Examples: tool:questdb-read, tool:supabase-write, tool:searxng-search, tool:redis-read, tool:external-broker-api.
The tool_id is the canonical reference used in all agent specs, runtime policies, event envelopes, and invocation contracts. Tool function names inside a LangGraph or n8n node use underscores (searxng_search) and map to tool_id by replacing underscores with hyphens and prepending tool:.
Tool categories
| Category | Example tool IDs | Side-effect class | Approval default |
|---|---|---|---|
| Internal read-only | tool:questdb-read, tool:redis-read, tool:qdrant-query, tool:neo4j-read | none | auto-approved |
| Internal write | tool:supabase-write, tool:redis-write, tool:neo4j-write | write | policy-gated |
| External read-only | tool:searxng-search | none | auto-approved |
| External write / irreversible | tool:external-broker-api, tool:send-email, tool:webhook-post | write / irreversible | policy-gated; HITL for irreversible |
| MCP-backed | any tool exposed via an MCP server | varies | policy-gated by default |
| A2A-backed | cross-agent delegation targets | varies | policy-gated by default |
Tools with side_effect_class: write or higher require Policy Guard (os-agt-002) approval before execution when mcp_write_approval_required: true. See Agent Runtime Policy Spec §Tool authorization schema.
2. The capability ceiling
Every agent declares the maximum superset of tools it may ever use in its Agent Spec tool_permissions list. See Agent Spec.
tool_permissions:
- tool_id: tool:questdb-read
side_effect_class: none
approval_tier: auto
- tool_id: tool:supabase-write
side_effect_class: write
approval_tier: policy
- tool_id: tool:external-broker-api
side_effect_class: irreversible
approval_tier: hitlThis list is the capability ceiling — the hard upper bound on what the agent was designed to be able to do. It is:
- Declared once, at agent definition time
- Immutable at runtime — no runtime policy or business instance configuration can add tools beyond this ceiling
- Registered in the OS agent registry and versioned with the agent spec
The ceiling rule: A tool not declared in tool_permissions can never appear in the active tool set, regardless of what any runtime policy or instance override requests.
3. The active tool set
The active tool set is the runtime-resolved subset of the capability ceiling that an agent executor actually receives for a specific invocation. It is a flat list of tool_id strings:
["tool:questdb-read", "tool:redis-read"]Key properties:
- Computed by the orchestration layer (n8n, Layer 3) before the agent is invoked — never by the agent itself
- Passed explicitly in the invocation request — for LangGraph agents it travels in the HTTP request body; for n8n AI Agent nodes it is expressed as the set of connected tool sub-nodes
- The LLM inside the agent never sees the ceiling — it only ever sees the active set; a tool absent from the active set is invisible to the model
- Can only be equal to or smaller than the capability ceiling — never larger
This separation — ceiling declared statically, active set computed dynamically per invocation — is what allows the same agent definition to behave differently across environments, tenants, and risk contexts without modifying the agent.
4. Three-layer resolution
The active tool set is produced by applying three layers of restriction in order. Each layer can only restrict downward — it cannot grant capabilities beyond what the layer above permits.
flowchart TD C[CAPABILITY CEILING. Source: Agent Spec tool_permissions. Owner: Agent definition, synedrix-os or domain pack. Example: questdb-read, supabase-write, redis-read, external-broker-api, qdrant-query]:::agent R[RUNTIME RESTRICTION. Source: Agent Runtime Policy Spec tool_authorization_policy. Owner: OS / deployment configuration. Example: active_tool_set: restricted, tool_suspensions: supabase-write, external-broker. Result: questdb-read, redis-read, qdrant-query]:::check I[INSTANCE RESTRICTION. Source: Business instance environment_overrides. Owner: Tenant / business instance configuration. Example: suspend qdrant-query in staging environment. Result: questdb-read, redis-read]:::check A([ACTIVE TOOL SET → passed to agent executor: tool:questdb-read, tool:redis-read]):::term C -- runtime policy applies tool_authorization_policy --> R R -- business instance environment_overrides apply --> I I --> A
The three active_tool_set policy modes (full schema in Agent Runtime Policy Spec):
| Mode | Meaning |
|---|---|
all | The full capability ceiling passes through; no runtime restriction applied |
declared | Only tools listed in tool_grants are active; all others are suppressed |
restricted | All ceiling tools are active except those listed in tool_suspensions |
5. Resolution flow (step by step)
At agent invocation time, orchestration (n8n, Layer 3) performs the following steps before handing the task to the agent executor:
- Read the capability ceiling from the agent registry entry (
tool_permissionslist for the agent’s current version). - Read the runtime policy for the agent (
tool_authorization_policyin the active runtime policy for the current deployment mode). - Apply the runtime restriction using the
active_tool_setmode (all/declared/restricted) and the accompanyingtool_grantsortool_suspensionslist. - Apply instance overrides from the business instance’s
environment_overridesfor the current environment. - Produce the active tool set — the flat list of
tool_idstrings that survived all three layers. - Pass the active tool set to the agent executor:
runtime_engine: langgraph→ included in the HTTP invocation request body asactive_tool_setruntime_engine: n8n-aiagent→ expressed as the sub-nodes connected to the AI Agent node
- The agent executor enforces the active tool set at tool-call time (see §6).
The full invocation contract between n8n and LangGraph is specified in Orchestration & Runtime Strategy §5.
6. Enforcement points
Tool authorization is enforced at two execution points depending on the runtime engine. Both apply the same rule: any tool call referencing a tool not in the active set is rejected before execution.
n8n AI Agent node (Layer 3)
For agents running as n8n AI Agent nodes, the active tool set is structural — it is the set of tool sub-nodes connected to the AI Agent node in the workflow graph. The n8n execution engine only exposes connected tools to the model; disconnected tools are architecturally invisible and cannot be called.
When implementing a workflow, tool sub-nodes must match the resolved active tool set for the current environment. Any ceiling tool absent from the active set must not be connected. See n8n Implementation Guide.
LangGraph executor (Layer 4)
For agents running in the LangGraph Agent Runtime (see ADR-OS-001), the active tool set is passed in the invocation request and enforced programmatically inside the graph’s conditional edge — before ToolNode executes any call.
The enforcement pattern (reference implementation from WF-OS-POC-001):
def _should_continue(state: AgentState) -> Literal["tools", "end"]:
last = state["messages"][-1]
if not isinstance(last, AIMessage) or not last.tool_calls:
return "end"
for tc in last.tool_calls:
# Map tool function name (underscores) to canonical tool_id (hyphens)
tool_key = f"tool:{tc['name'].replace('_', '-')}"
if tool_key not in state["active_tool_set"]:
raise PermissionError(
f"Tool '{tc['name']}' ('{tool_key}') not in active_tool_set. "
f"Allowed: {state['active_tool_set']}"
)
return "tools"A PermissionError causes the graph to return a status: failure Outcome Envelope and emit agent.tool.unauthorized. The tool is never executed. See LangGraph Runtime Implementation Guide for the full compliance checklist.
7. Observability events
The following events are emitted during tool authorization and conform to the Event Taxonomy Standard envelope. Full event schemas are in Agent Runtime Policy Spec §Observability events.
| Event type | Emitted when |
|---|---|
agent.tool.authorized | A tool call passed all authorization checks and was forwarded to execution |
agent.tool.unauthorized | A tool call was rejected — the tool is not in the active set |
agent.tool.suspended | A tool call was rejected — the tool appears in tool_suspensions |
agent.tool.mcp_approval_requested | An MCP tool call was submitted to Policy Guard before execution |
agent.tool.a2a_approval_requested | An A2A delegation was submitted to Policy Guard before execution |
agent.tool.mcp_server_blocked | An MCP server call was blocked by mcp_server_denylist |
agent.tool.unauthorized is the most operationally significant event. It indicates either a misconfiguration (agent spec and runtime policy out of sync) or an LLM hallucination (the model attempted a tool it was not told about). Both require investigation.
8. Worked example — POC agent (poc-agt-001)
The POC Workflow WF-OS-POC-001 provides the simplest real example of the full resolution chain.
Agent: poc-agt-001 — research and summarize using SearXNG · Runtime engine: langgraph
Capability ceiling (agent definition):
tool_permissions:
- tool_id: tool:searxng-search
side_effect_class: none
approval_tier: autoRuntime restriction (runtime policy):
tool_authorization_policy:
active_tool_set: all # no restriction — pass the full ceiling throughInstance override: none.
Active tool set produced: ["tool:searxng-search"]
Invocation request (n8n → LangGraph HTTP POST /invoke):
{
"correlation_id": "corr-1751234567-abc123",
"causation_id": "wf-exec-987654",
"agent_id": "poc-agt-001",
"active_tool_set": ["tool:searxng-search"],
"input": { "topic": "LangGraph multi-agent orchestration patterns" }
}If the LLM attempts an unauthorized tool call (e.g., hallucinates supabase_write):
The conditional edge maps the name to tool:supabase-write, finds it absent from ["tool:searxng-search"], and raises PermissionError. The graph returns:
{
"status": "failure",
"errors": ["Tool 'supabase_write' ('tool:supabase-write') not in active_tool_set. Allowed: ['tool:searxng-search']"],
"execution_context": { "correlation_id": "corr-1751234567-abc123", "agent_id": "poc-agt-001" }
}And emits: agent.tool.unauthorized with tool_id: tool:supabase-write.
9. Invariants
These rules are unconditional. They apply in all deployment modes, all domains, and all execution patterns.
Ceiling is the hard limit. A tool not declared in an agent’s
tool_permissionsceiling can never appear in the active tool set for that agent, regardless of runtime policy or instance configuration.Orchestration resolves, executor enforces. The active tool set is always computed by the orchestration layer (n8n) and passed into the agent executor. An agent executor must never compute or expand its own active tool set.
Every tool call is checked before execution. Authorization is checked at the moment of tool invocation, not at agent startup. Late binding is required because the LLM decides which tools to call at inference time.
Rejections are observable. A tool call that fails authorization must emit
agent.tool.unauthorized. Silent suppression is not permitted — it creates lineage gaps and hides misconfiguration.os-agt-008(Audit & Lineage Agent) must never appear in any agent’stool_permissionsceiling. This agent is strictly read-only and must never be callable as a tool from any other agent.