AI agents

A Tool Registry With Tiers: Who May Call What

Antonio Evans9 min read

A wall of numbered safety deposit boxes, each with a small label showing a tier badge and a team name.
Key points

Key points

  1. 1A tool registry that returns filtered tool lists by caller identity stops a model from selecting a tool it was never cleared to use.
  2. 2Every registry entry needs five fields: tool name, risk tier, owning team, allowed callers and an approval route for elevated tiers.
  3. 3Hiding tools above a caller's clearance is stronger than blocking them at execution, because the model cannot reason its way toward a tool it never sees.
  4. 4Logging the caller, tool and tier on every call turns the registry into an audit trail rather than a suggestion list.

Why visibility filtering beats prompt instructions

The common first attempt at tool safety is a sentence in the system prompt: do not use the refund tool unless the user asks twice. That fails for the same reason any prompt-only constraint fails. The model predicts one token at a time, and nothing in the decoding process has been told that certain outputs are forbidden. Research on constrained decoding shows that hard guarantees come from filtering the token distribution at generation time, not from asking politely in the constrained decoding overview.

The security equivalent of that lesson is to filter the tool list before the model ever sees it. Function calling works by handing the model a library of function definitions and letting it choose, with your application executing the selected function and returning the result in this function calling explainer. If the dangerous function is not in that library, the model cannot choose it. There is no argument to parse, no name to hallucinate into a real endpoint, and nothing to talk the model out of.

Practitioners describe this as dynamic tool filtering, one of the levers a team actually controls once a model has been chosen, alongside tool descriptions, atomic tool design and ongoing evaluations in the Paragon tool calling guide. The registry is where that filter gets its data.

The distinction matters for cost too. Every tool schema you send occupies context. A caller that is cleared for four read-only tools does not need to carry forty definitions, and loading unnecessary tools is one of the ways a system becomes too slow or expensive for production in the same Paragon guide. Tier filtering therefore buys security and latency at the same time.

Anatomy of a registry entry

A registry entry is a record, not a document. Keep it small enough that a reviewer can read one in thirty seconds, and complete enough that the router can decide without asking anyone.

FieldPurposeExample
nameStable identifier the router matches onbilling.issue_refund
tierRisk classification driving default accessfinancial
ownerTeam accountable for the tool and its policypayments-platform
allowed_callersExplicit identities cleared at or below the tier ceilingagent.support.tier1, agent.billing.recon
approvalWho or what must approve calls above the baselinehuman:finance-oncall
schemaArgument contract, validated before executionJSON Schema object
descriptionWhat the model reads to choose correctly"Refunds a captured charge, requires order id and reason code"

Three conventions make entries dependable. First, the name is namespaced by domain so a router can group and filter on prefix. Second, the tier is an enum with a fixed vocabulary, not free text, so the filter is a comparison rather than a judgment. Third, allowed_callers lists service identities, not people. A human's approval is a separate event; the caller is the agent or service that actually triggers the tool.

The schema deserves the same care as the rest. Tool definitions follow JSON Schema with provider extensions, and arguments should be validated against the original schema before execution, because models occasionally reference nonexistent functions or pass parameters that fail type validation in the Blaxel function calling guide. A registry that stores the schema centrally means every caller validates against one contract instead of a copy that drifted.

Owning team is not bureaucracy. It answers the question that comes up in every incident review: who decides whether this tool's tier is still right? Without a named owner, tiers calcify at whatever value they had on the day someone wrote them.

The five tiers and what each one permits

Tiers work when they map to consequences, not to how scary a tool feels. Five bands cover most enterprise estates:

TierWhat the tool doesDefault clearanceApproval
read_onlyReads data with no side effects on recordsAny authenticated internal callerNone
mutatingCreates or updates records that can be correctedCallers named by the owning teamNone for scoped updates
destructiveDeletes, revokes access, overwrites historyNamed callers plus a time windowHuman confirmation per call
financialMoves money, issues credits, changes pricesNamed callers only, never sharedHuman approval above a threshold
privilegedChanges identity, permissions, infrastructure or policyBreak-glass identitiesTwo-person approval, logged

The decision rule is simple and testable: if the worst realistic outcome of a mistaken call is a wrong answer, the tool is read-only. If it is a wrong record that someone can edit back, it is mutating. If it cannot be undone without a restore or a customer conversation, it is destructive or financial depending on whether money moved. If it grants more power to something else, it is privileged.

Map the tiers to concrete damage before you publish the first entry. The agent security literature keeps returning to the same point: agents read and write real systems, so a tool call is a security boundary rather than a passive read, and over-broad tool access plus prompt injection is how untrusted content steers an agent into destructive changes in the MCP policy enforcement guide. Naming the damage in the tier definition is what stops a debate about labels.

Two boundary cases trip people up. A tool that sends email is mutating, not read-only, because the message leaves the building. A tool that exports a customer list to a spreadsheet is destructive even though it reads, because the data can leave your control. Classify by what the tool can cause, not by the SQL verb inside it.

Filtering the tool list by clearance

The filter runs on every request. The caller presents an identity, the registry evaluates it against each entry, and the router builds the tool list from the results.

from dataclasses import dataclass, field

TIER_RANK = {
    "read_only": 0, "mutating": 1, "destructive": 2,
    "financial": 3, "privileged": 4,
}

@dataclass
class Caller:
    identity: str
    max_tier: str
    groups: list[str] = field(default_factory=list)

def visible_tools(registry, caller):
    ceiling = TIER_RANK[caller.max_tier]
    out = []
    for tool in registry:
        if TIER_RANK[tool["tier"]] > ceiling:
            continue
        if caller.identity not in tool["allowed_callers"]:
            continue
        out.append(tool)
    return out

Two conditions, both required. The tier ceiling says how far a caller may reach; the allowed-caller list says which specific tools it may touch. A caller with a financial ceiling still sees nothing unless the payments team named it on the refund tool. Requiring both keeps a broad clearance from becoming a broad grant.

Evaluate the filter server-side, in the same process or service that assembles the model request. If the tool list is assembled in the client, a caller can send whatever list it likes. The registry returns tool schemas and the router passes them into the function-calling payload; the model's choice is then drawn only from that set following the function calling mechanics.

Keep the filter deterministic and boring. Avoid letting the model ask for additional tools mid-conversation, because a model asking for a tool it cannot see is a signal that something has gone wrong, not a workflow to automate. Log the denial with the caller and the tool name so you can see which agents keep reaching for capabilities they do not have.

If your callers span several model providers, the extra call of fetching tool lists is not the place to add per-provider code. A single OpenAI-compatible endpoint for every model keeps the router's request shape constant while the registry logic stays in your application, which is what Quanta Inference (https://quantaintelligence.ai/inference) provides.

Enforcing the tier at execution, not just at selection

Filtering what the model sees solves selection. It does not solve execution, because the tool name that comes back is still a string, and strings can be forged by anything upstream of your dispatcher. Enforce the tier again at the moment of the call.

The check is cheap: look the tool up in the registry by name, confirm the calling identity is present in allowed_callers, confirm the tier is within the caller's ceiling, then apply the approval rule for that tier. If the lookup fails, the call fails. No registry entry, no execution.

This is the same principle as putting a policy-enforcement proxy between the client and the tool servers, where a disallowed call never reaches the server and a consequential call is held until a decision is recorded in the MCP policy enforcement guide. A registry with an execution check gives you that boundary without depending on any single transport.

Validation after generation belongs here as well. A structurally valid tool call can still be semantically wrong, and runtime authorization decides whether a well-formed object may cause an external action in the TrueFoundry discussion of structured outputs. The registry is where that authorization decision lives.

Human approval belongs at the tier boundary, not at the end of the workflow. Practitioner guidance on agent orchestration puts it plainly: approvals belong at consequential action boundaries, and retries need limits, idempotency awareness and a recovery path in the orchestration patterns guide. A destructive tool that waits for a person before it runs is a different system from one that waits for a person after it runs.

Logging and review that hold up

Every call should produce one record: timestamp, caller identity, tool name, tier, decision (allowed, denied, held, approved), approver if any, and arguments hashed rather than stored in full when they contain personal data. That record is what turns the registry from a permission table into an audit trail.

Log raw and parsed model output alongside the call so you can reconstruct why the model chose the tool, and track validation failure rates rather than only watching the task success rate in the MLflow structured output guidance. Teams that only measure whether the task completed cannot tell whether a failure came from bad tool selection, bad arguments, a missing permission or too much context in the Paragon tool calling guide. A registry gives you the permission dimension of that breakdown for free.

Review on a fixed rhythm, and review the right things:

  • New entries: anyone can propose, the owning team approves, and the tier is justified in one sentence.
  • Tier changes: an escalation to destructive or financial is a policy change and needs the same approval as a schema change.
  • Unused grants: drop callers that have not called a tool in a quarter. Standing access with no usage is pure surface.
  • Denied calls: a cluster of denials for one agent usually means its ceiling is wrong or its description is misleading it toward the wrong tool.

The regulatory tailwind is real but does not need to drive the design. Frameworks that ask you to inventory AI systems, describe their capabilities and show controls are much easier to satisfy with a registry that already records who may call what in the EU AI Act agent security overview. Build the registry for the incident review, and the compliance paperwork becomes an export.

One operational note on scale. Registries run with a read-heavy, write-light pattern, so cache the full registry in the router and invalidate on change. The filter itself is an in-memory comparison over a few hundred entries and costs microseconds. The expensive part is the approval workflow, which is why most teams start with two tiers and a shared spreadsheet, then split into five once the first financial tool appears.

Getting from nothing to a working registry in a week

Start with the tools already in production, not with an ideal taxonomy. Export every function currently offered to any agent, then sort.

  1. Name and owner. One row per tool, namespaced and assigned to the team that would be paged if it misfired.
  2. Tier by consequence. Apply the damage test from the tier section. Most estates land heavily in read_only and mutating, with a short list in the top three tiers.
  3. List current callers. Take the callers from your logs, not from memory. Anything you cannot attribute to a known agent or service becomes destructive until proven otherwise.
  4. Set ceilings. Give each agent the lowest ceiling that lets it do its job. A support triage agent rarely needs more than mutating.
  5. Ship the filter behind a flag. Log what would have been filtered for a week before you enforce, then compare. Expect the list to be longer than you guessed.
  6. Add the execution check. Route all tool calls through the dispatcher that re-checks the registry, then remove the flag.
  7. Publish the approval path. For the top two tiers, name the approver group and the response time you promise.

Step 5 is the one teams skip. Shadow mode tells you which agents depend on tools nobody meant to grant them, and those are the conversations worth having before enforcement rather than after an outage.

When the registry spans several lines of business, the ownership question gets harder than the code. Quanta Experts (https://quantaintelligence.ai/agents) is one route for teams that want a specialist reading their own context and returning finished work, with the registry as the boundary that decides what that work may touch.

FAQ

FAQ

Does hiding a tool completely stop a model from calling it?

No single control is complete, but hiding the tool removes the main path. A model cannot select a function that is absent from the tool list it receives, because selection happens over the definitions in the request following the function calling mechanics. The execution check in the registry then catches forged or replayed tool names that never came from a model at all.

What stops someone from editing the registry to grant themselves a higher tier?

Nothing in the registry code, which is why the registry is a policy system rather than a library. Tier escalations and new entries for the top tiers should require the owning team's approval, and those approvals should be recorded next to the entry. Store the registry in version control or a system with the same review discipline, and alert on any change to allowed_callers at financial or privileged tier.

How many tiers should a small team use?

Start with two, read-only and everything else, and split only when a tool appears whose damage is not recoverable by editing a record. The five-band scheme earns its keep once you have money movement, deletion or permission changes in scope, because those three need different approval rules. A team with twelve tools and no destructive actions does not need five tiers, and a team with a refund API does.

Should the registry return tool descriptions to the model, or only names?

Return full schemas and descriptions for the tools you return at all, because the description is what the model uses to match user intent to the right function in the Blaxel function calling guide. Vague or partial definitions increase routing errors. What you withhold is the existence of the tool, never the quality of its definition.

How does this interact with structured outputs?

They solve different problems and compose cleanly. Structured outputs constrain the shape of a single generation, while the registry constrains which actions are available at all in the MLflow guidance. A tool call is structured output with a consequence attached, so you want both: schema-valid arguments from the model, and a registry that decides whether the call may run.

Sources

Sources

  1. Beyond Free-Form Text: How Constrained Decoding is ... - Medium
  2. Understanding Tool Calling Mechanisms in AI Agents
  3. How to Optimize Tool Calling for AI Agents - Paragon
  4. What Is LLM Function Calling?
  5. MCP Policy Enforcement: How to Secure Model Context ...
  6. Structured Outputs vs. Function Calling: Which Should Your Agent ...
  7. LLM Structured Outputs with JSON Schema
  8. AI Agent Orchestration: Patterns, Workflows & Best Practices
  9. Enforce, Validate, Observe: 3 LLM Structured Output ...
  10. How to Secure Your Agents for EU AI Act Compliance
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles