Skip to main content

Tools & Permissions

Built-in tools​

All tools are registered automatically. Control access with allowed_tools / disallowed_tools.

ToolCategoryRead-OnlyDescription
BashbashNoRun shell commands, view/create/edit files
ReadfilesystemYesRead file contents or list a directory
WritefilesystemNoWrite file contents
EditfilesystemNoEdit files with search/replace
GlobfilesystemYesFind files by pattern
GrepfilesystemYesSearch file contents
WebSearchwebYesSearch the web (Tavily/Brave/SerpAPI/DuckDuckGo)
WebFetchwebYesFetch and extract web page text; SSRF-protected
TodoWritetodoNoManage a structured task list
AskUserQuestionuser_inputNoAsk the user a question
ToolSearchmetaYesSearch available tools by keyword
AgentorchestrationNoDelegate work to a registered sub-agent
MemoriesmemoryNoRead and write persistent agent memories (conditional — see below)

Agent is only registered when agents are configured on the options object.

Memories is only registered when memories_enabled=True is set on AgentixAgentOptions. It is not available by default.

Restricting tools​

from agentix import AgentixAgentOptions

# Allow only specific tools
options = AgentixAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "WebSearch"],
)

# Block specific tools
options = AgentixAgentOptions(
disallowed_tools=["Bash", "Write"],
)

When allowed_tools is empty (the default), all tools are available. When non-empty, only the listed tools are available. disallowed_tools always overrides allowed_tools.

Permission pipeline​

When the LLM requests a tool call, it passes through five sequential checks. Any stage can short-circuit to Execute (green) or Blocked (red).

Permission Pipeline diagram

Permission modes​

ModeBehavior
"default"Checks allowed_tools / disallowed_tools + can_use_tool callback
"acceptEdits"Auto-approves all file edits
"bypassPermissions"Allows everything except disallowed_tools
"plan"No tools execute; planning mode only
"dontAsk"Denies any tool not in allowed_tools
options = AgentixAgentOptions(permission_mode="bypassPermissions")

# Or change at runtime
client.set_permission_mode("plan")

Permission callback​

The can_use_tool callback is the single control point for all tool interactions — permission gating, input sanitisation, and user-input delivery.

from agentix import (
AgentixAgentOptions,
PermissionResultAllow,
PermissionResultDeny,
ToolPermissionContext,
)

async def my_permission_handler(
tool_name: str,
tool_input: dict,
context: ToolPermissionContext,
):
# Hard deny — aborts the run immediately
if tool_name == "Bash" and "rm -rf" in tool_input.get("command", ""):
return PermissionResultDeny(message="Destructive command blocked.", interrupt=True)

# Soft deny — agent sees the message and may try a different approach
if tool_name == "Bash":
return PermissionResultDeny(message="Shell access is disabled.")

# Allow with no modifications (most common)
return PermissionResultAllow()

options = AgentixAgentOptions(
name="safe-agent",
can_use_tool=my_permission_handler,
)

Permission result fields​

ClassFieldDefaultDescription
PermissionResultAllowupdated_inputNoneReplacement input dict; None = use original
PermissionResultAllowupdated_permissionsNoneAdvisory rule changes to the session's permission state
PermissionResultDenymessage""Reason shown to the agent
PermissionResultDenyinterruptFalseTrue = hard abort; False = soft deny

When PermissionResultAllow.updated_input is provided, the modified input is what gets executed and persisted to the session log, keeping audit trails accurate.

Handling AskUserQuestion​

When the agent calls AskUserQuestion, handle it inside can_use_tool by returning PermissionResultAllow with the user's answer in updated_input:

async def my_permission_handler(tool_name, tool_input, context):
if tool_name == "AskUserQuestion":
answer = await your_ui.ask(tool_input["question"])
return PermissionResultAllow(updated_input={**tool_input, "answer": answer})
return PermissionResultAllow()

When no can_use_tool is set, AskUserQuestion falls back to a blocking input() call — suitable for CLI scripts but will hang in headless deployments.

Web search backends​

Set one of these environment variables to auto-select the web search backend:

export TAVILY_API_KEY=tvly-...     # recommended
export BRAVE_API_KEY=BSA...
export SERPAPI_API_KEY=...
# (no key) → DuckDuckGo fallback

SSRF protection​

WebFetch blocks requests to private/internal IPs (RFC-1918, link-local, loopback) by default. To allow specific internal ranges:

from agentix import AgentixAgentOptions, SandboxSettings, SandboxNetworkConfig

options = AgentixAgentOptions(
sandbox=SandboxSettings(
enabled=True,
network=SandboxNetworkConfig(
allow_network_ranges=["10.100.0.0/16"],
),
),
)

Secure subprocess environment​

The Bash tool spawns subprocesses with a minimal environment (safe PATH only) by default, so ambient credentials are not leaked to subprocesses. To forward specific variables:

options = AgentixAgentOptions(
env_pass_through=["DATABASE_URL", "REDIS_URL"], # opt-in
env={"MY_APP_ENV": "production"}, # explicit extras
)