Gateway Tool Policy

Scope the toolset advertised to the model per inbound route โ€” trust-tiered, fail-closed, prompt-injection-aware

Untrusted inbound routes never even see dangerous tools โ€” the model is offered a scoped subset for that turn, then the agent’s original toolset is restored.

python
from praisonaiagents import Agent

agent = Agent(name="assistant", tools=["read_file", "search_web"])
# Gateway bindings with trust: untrusted scope which tools the model sees per route

The user asks the agent to run tools; gateway tool policy allows or blocks tools before execution.

Diagram

Per-route Tool Policy

untrusted

standard / trusted

๐Ÿ“ฉ Inbound Message

๐ŸŽฏ Route Resolver

๐Ÿ›ก๏ธ Trust Tier

๐Ÿšซ Deny-list:
shell ยท file mut ยท delegate

โœ… Full Toolset

๐Ÿค– Agent Turn
scoped tools

โ™ป๏ธ Restore Tools

Quick Start

  1. Lock down stranger DMs with one YAML line

    Add trust: untrusted to any binding. Dangerous tools (shell, file mutation, delegation, scheduling) are automatically withheld from the model for that route.

    yaml
    gateway:
      routes:
        - { chat_type: dm, agent: assistant, trust: untrusted }
  2. Give your operator full power
    python
    from praisonaiagents import Agent
    from praisonaiagents.gateway import RouteBinding
    
    assistant = Agent(name="assistant", instructions="Help users.")
    
    bindings = [
        RouteBinding(agent="assistant", chat_type="dm", trust="untrusted"),
        RouteBinding(agent="assistant", peer="operator-123", trust="trusted"),
    ]

    Stranger DMs get the safe subset. Your operator peer gets everything.

  3. Add explicit allow/deny overrides
    python
    from praisonaiagents.gateway import RouteBinding
    
    RouteBinding(
        agent="assistant",
        channel_id="ops",
        deny_tools=["shell", "delete_file"],
    )

    Layer deny_tools on top of any trust tier for project-specific tools not covered by the default substrings.


How It Works

Diagram
โ™ป๏ธ Restore๐Ÿค– Agent Turn_apply_tool_policy()๐Ÿ›ก๏ธ tool_policy()๐ŸŽฏ Route Resolver๐Ÿ“ฉ Inbound Messageโ™ป๏ธ Restore๐Ÿค– Agent Turn_apply_tool_policy()๐Ÿ›ก๏ธ tool_policy()๐ŸŽฏ Route Resolver๐Ÿ“ฉ Inbound MessageMatch bindingbinding.tool_policy()ToolPolicy (or None)agent.tools โ† scoped subset (per-agent lock)Turn complete (or raises)agent.tools โ† original (always restored)

The apply/restore step happens inside the per-agent lock and is guaranteed to run even when the agent turn raises an exception โ€” so a failed run can never leave an agent permanently hobbled.

StepWhat happens
Route resolverMatches the binding with highest priority + specificity
tool_policy()Builds a ToolPolicy from trust, allow_tools, deny_tools
_apply_tool_policy()Swaps agent.tools to the allowed subset for this turn
Agent turnModel only sees the scoped tool list
Restoreagent.tools reset to original regardless of outcome

Configuration Options

RouteBinding security fields

For routing fields (peer, role, channel_id, account, chat_type, priority) see Gateway Route Bindings.

FieldTypeDefaultDescription
trustOptional[str]None"untrusted" \"standard" \"trusted". Unknown values normalise to "untrusted" (fail-closed).
allow_toolsOptional[List[str]]NoneWhitelist by exact tool name. None = allow all except deny rules. Accepts a YAML scalar or list.
deny_toolsOptional[List[str]]NoneExact tool names removed before the run. Layers on top of the trust tier’s deny-list. Accepts a YAML scalar or list.

ToolPolicy dataclass

FieldTypeDefaultDescription
allow_toolsOptional[Set[str]]NoneWhitelist by exact name. None = allow all except deny rules.
deny_toolsSet[str]set()Exact names removed.
deny_substringsList[str][]Case-insensitive substrings; a tool whose name contains any of these is removed. Used by the untrusted tier.

Module constants

ConstantTypeDescription
UNTRUSTED_DENY_SUBSTRINGSList[str]The 12 substrings the untrusted tier seeds: shell, exec, command, subprocess, write_file, edit_file, delete_file, rm_file, delegate, handoff, cronjob, schedule.
TRUST_TIERSList[str]["untrusted", "standard", "trusted"] (least โ†’ most privileged).

Import from praisonaiagents.gateway:

python
from praisonaiagents.gateway import (
    RouteBinding, ToolPolicy, TRUST_TIERS, UNTRUSTED_DENY_SUBSTRINGS,
)

Common Patterns

Lock down stranger DMs, keep operator at full power

python
from praisonaiagents import Agent
from praisonaiagents.gateway import RouteBinding

assistant = Agent(name="assistant", instructions="Help users.")

bindings = [
    RouteBinding(agent="assistant", chat_type="dm", trust="untrusted"),
    RouteBinding(agent="assistant", peer="operator-123", trust="trusted"),
]

Or in YAML:

yaml
gateway:
  routes:
    - { chat_type: dm, agent: assistant, trust: untrusted }
    - { peer: "operator-123", agent: assistant, trust: trusted }

Channel-scoped policy โ€” deny specific tools on #ops

python
from praisonaiagents.gateway import RouteBinding

RouteBinding(
    agent="assistant",
    channel_id="ops",
    deny_tools=["shell", "delete_file"],
)

Whitelist mode โ€” only search on a public-facing route

python
from praisonaiagents.gateway import RouteBinding

RouteBinding(
    agent="assistant",
    chat_type="group",
    allow_tools=["search"],
)

Only the search tool is offered. Everything else is withheld.


Best Practices

Default new gateway routes to trust: untrusted

Opt in to power, not out of safety. Add trust: untrusted to every new route you create; promote to standard or trusted only when you have a clear need.

yaml
gateway:
  routes:
    - { chat_type: dm, agent: assistant, trust: untrusted }   # safe by default
Layer deny_tools on top of untrusted for project-specific tools

UNTRUSTED_DENY_SUBSTRINGS covers the 12 common dangerous families. Custom tools with names like purge_cache or flush_queue won’t match any substring โ€” add them explicitly.

python
RouteBinding(
    agent="assistant",
    chat_type="dm",
    trust="untrusted",
    deny_tools=["purge_cache", "flush_queue"],
)
Never put your operator's peer on an untrusted binding

Pair trust: trusted (or omit trust) with your operator’s stable peer: id. If you put an operator peer on untrusted, they will hit the conservative deny-list on every message.

yaml
bindings:
  - { peer: "operator-123", agent: assistant }          # trusted by default (no trust field)
  - { chat_type: dm, agent: assistant, trust: untrusted }   # everyone else
Audit UNTRUSTED_DENY_SUBSTRINGS for your tool families

Import the constant and check your tool names against it before deploying:

python
from praisonaiagents.gateway import UNTRUSTED_DENY_SUBSTRINGS

my_tools = ["search_web", "read_file", "send_email", "run_python"]
for tool in my_tools:
    blocked = any(sub in tool.lower() for sub in UNTRUSTED_DENY_SUBSTRINGS)
    print(f"{tool}: {'BLOCKED on untrusted' if blocked else 'allowed'}")

Anything that bypasses the 12 substrings will leak through trust: untrusted unless you add an explicit deny_tools entry.


Related