Permissions

Control what tools can do with a flexible permission system. Protect sensitive operations while allowing the model to work freely on safe tasks.

Why Permissions?

AI models are powerful but unpredictable. Without guardrails, a model might:

  • Delete important files
  • Execute destructive commands
  • Access sensitive data
  • Make unintended API calls

The permission system lets you allow helpful actions while blocking dangerous ones—with user approval as a middle ground.

Permission flow
Tool Call Check allow Execute deny Block ask Ask User

Permission Policies

Policies define the rules for each tool or pattern of tools. Each policy specifies one of three actions:

Policy Behavior
allow Execute immediately, no questions
deny Block execution, return error to model
ask Prompt user for approval each time

Policy Matching

Policies can match:

  • Exact tool namesexecute_command
  • Wildcardsfile_* matches all file tools
  • All tools* as default

More specific patterns take precedence over wildcards.

Policy examples
from shared.plugins.permission import (
    create_plugin as create_permission,
    PermissionPolicy
)

# Create permission plugin
perm = create_permission()

# Define policies
policies = [
    # Safe tools: always allow
    PermissionPolicy(
        pattern="read_file",
        action="allow"
    ),
    PermissionPolicy(
        pattern="list_directory",
        action="allow"
    ),

    # Dangerous: always deny
    PermissionPolicy(
        pattern="execute_command",
        action="deny"
    ),

    # File writes: ask user
    PermissionPolicy(
        pattern="write_file",
        action="ask"
    ),
    PermissionPolicy(
        pattern="delete_file",
        action="ask"
    ),

    # Default: ask for unknown tools
    PermissionPolicy(
        pattern="*",
        action="ask"
    )
]

perm.initialize({"policies": policies})

Configuration

Permission policies can be configured via code or a JSON file.

Config File

For easier management, define policies in a JSON file:

Configuration Options

Option Description
policies List of permission policies
default_action Action for unmatched tools (ask)
ask_timeout Seconds to wait for user response
remember_session Remember user choices this session
permissions.json
{
  "default_action": "ask",
  "ask_timeout": 60,
  "remember_session": true,
  "policies": [
    {
      "pattern": "read_*",
      "action": "allow",
      "description": "Reading is safe"
    },
    {
      "pattern": "list_*",
      "action": "allow"
    },
    {
      "pattern": "search_*",
      "action": "allow"
    },
    {
      "pattern": "write_*",
      "action": "ask",
      "description": "Confirm before writing"
    },
    {
      "pattern": "delete_*",
      "action": "ask",
      "description": "Confirm before deleting"
    },
    {
      "pattern": "execute_command",
      "action": "deny",
      "description": "Shell commands blocked"
    }
  ]
}
Load from file
from shared.plugins.permission import create_plugin

perm = create_plugin()
perm.initialize({
    "config_path": "permissions.json"
})

# Use with client
client.configure_tools(
    registry,
    permission_plugin=perm
)

Using with Client

Pass the permission plugin to configure_tools() to enable permission checking for all tool calls.

How It Works

  1. Model decides to call a tool
  2. Permission plugin checks the policy
  3. Based on policy:
    • allow: Tool executes
    • deny: Error returned to model
    • ask: User prompted
  4. Result returned to model

User Prompts

When a tool requires approval, the user sees the tool name and arguments, and can approve or deny.

Complete setup
from jaato import JaatoClient, PluginRegistry
from shared.plugins.permission import (
    create_plugin as create_permission,
    PermissionPolicy
)

# Create client
client = JaatoClient()
client.connect()  # Reads JAATO_PROVIDER and MODEL_NAME from env

# Create registry and expose tools
registry = PluginRegistry(model_name=client.model_name)
registry.discover()
registry.expose_tool("cli")
registry.expose_tool("file_edit")

# Create permission plugin
perm = create_permission()
perm.initialize({
    "policies": [
        PermissionPolicy("read_file", "allow"),
        PermissionPolicy("write_file", "ask"),
        PermissionPolicy("execute_command", "ask"),
        PermissionPolicy("*", "ask"),
    ],
    "remember_session": True
})

# Configure with permissions
client.configure_tools(
    registry,
    permission_plugin=perm
)

# Now tool calls go through permission check
response = client.send_message(
    "Delete the temp files",
    on_output=handler
)
# User will be prompted before delete executes

Auto-Approved Tools

Plugins can declare certain tools as "auto-approved"—these bypass permission checks entirely. Use for tools that are inherently safe.

When to Use

  • Read-only operations
  • Local calculations
  • Status queries
  • Tools with no side effects
Use Sparingly
Auto-approved tools skip ALL permission checks. Only use for genuinely safe operations that can't cause harm.
Declaring auto-approved tools
class MyPlugin:
    def get_tool_schemas(self):
        return [
            ToolSchema(name="get_status", ...),
            ToolSchema(name="calculate", ...),
            ToolSchema(name="write_file", ...),
        ]

    def get_auto_approved_tools(self):
        """
        These tools skip permission checks.
        """
        return [
            "get_status",   # Read-only
            "calculate",    # No side effects
            # NOT write_file - has side effects
        ]
Check auto-approved list
# See all auto-approved tools
registry.discover()
registry.expose_tool("my_plugin")

plugin = registry.get_plugin("my_plugin")
auto = plugin.get_auto_approved_tools()
print(f"Auto-approved: {auto}")

# These will execute without permission check
# even if policy says "ask" or "deny"

Ask Channels

When a tool needs user approval, the permission plugin uses an "ask channel" to communicate with the user.

Built-in Channels

Channel Description
console Prompt in terminal (default)
queue Queue requests for async handling
auto_deny Deny all without asking
auto_allow Allow all without asking

Queue Channel

The queue channel is useful for GUI apps or async workflows. Permission requests are queued and can be processed separately from the main execution flow.

Console channel (default)
# Console channel prompts in terminal
perm.initialize({
    "policies": policies,
    "ask_channel": "console"
})

# User sees:
# Allow tool "write_file" with args {"path": "config.json"}?
# [y/n]:
Queue channel
# Queue channel for async/GUI apps
perm.initialize({
    "policies": policies,
    "ask_channel": "queue"
})

# Permission requests are queued
# Your app processes them asynchronously

# Get pending requests
pending = perm.get_pending_requests()
for req in pending:
    print(f"Tool: {req.tool_name}")
    print(f"Args: {req.args}")

    # Process in your UI, then respond
    perm.respond_to_request(
        req.request_id,
        approved=True  # or False
    )
Auto channels for testing
# Auto-allow all (useful for testing)
perm.initialize({
    "policies": policies,
    "ask_channel": "auto_allow"
})

# Auto-deny all (safe default)
perm.initialize({
    "policies": policies,
    "ask_channel": "auto_deny"
})

Session Memory

With remember_session=True, user choices are remembered for the current session. This avoids asking repeatedly for the same tool.

How It Works

  • User approves write_file
  • Future write_file calls auto-approve
  • Memory clears when session ends

Granularity

Memory can be per-tool or per-tool-with-args:

  • per_tool: Approve once for all calls
  • per_call: Approve each unique call
Session memory options
perm.initialize({
    "policies": policies,

    # Remember approvals this session
    "remember_session": True,

    # Granularity: "per_tool" or "per_call"
    "remember_granularity": "per_tool",
})

# With per_tool:
# - User approves write_file once
# - All write_file calls auto-approve

# With per_call:
# - User approves write_file("a.txt")
# - write_file("a.txt") auto-approves
# - write_file("b.txt") still asks
Clear memory
# Clear all remembered approvals
perm.clear_session_memory()

# Check current memory
memory = perm.get_session_memory()
for tool, approved in memory.items():
    status = "approved" if approved else "denied"
    print(f"{tool}: {status}")

Best Practices

Default to Ask

Use ask as the default action. Only allow tools you've verified are safe.

Be Specific

Use specific patterns rather than broad wildcards. read_file is better than *_file.

Document Policies

Add descriptions to policies so users understand why they're being asked.

Review Regularly

As you add tools, review your permission policies to ensure new tools are covered appropriately.

Recommended setup
{
  "default_action": "ask",
  "remember_session": true,
  "remember_granularity": "per_tool",
  "ask_timeout": 30,

  "policies": [
    // Explicitly safe - allow
    {"pattern": "read_file", "action": "allow"},
    {"pattern": "list_directory", "action": "allow"},
    {"pattern": "search_files", "action": "allow"},
    {"pattern": "get_*", "action": "allow"},

    // Destructive - ask with description
    {
      "pattern": "write_file",
      "action": "ask",
      "description": "This will modify a file"
    },
    {
      "pattern": "delete_*",
      "action": "ask",
      "description": "This will delete data"
    },

    // Very dangerous - deny
    {
      "pattern": "execute_command",
      "action": "deny",
      "description": "Shell commands are blocked"
    },
    {
      "pattern": "*_production_*",
      "action": "deny",
      "description": "Production access blocked"
    }
  ]
}

Next Steps

Quick reference
# Policy actions
"allow"  # Execute immediately
"deny"   # Block with error
"ask"    # Prompt user

# Pattern matching
"tool_name"     # Exact match
"read_*"        # Wildcard suffix
"*_file"        # Wildcard prefix
"*"             # Match all (default)

# Ask channels
"console"       # Terminal prompt
"queue"         # Async queue for GUI apps
"auto_deny"     # Deny all asks
"auto_allow"    # Allow all asks

# Memory
"per_tool"      # Remember by tool name
"per_call"      # Remember by tool + args