Tools & Permissions
Built-in tools
All tools are registered automatically. Control access with allowed_tools / disallowed_tools.
| Tool | Category | Read-Only | Description |
|---|---|---|---|
Bash | bash | No | Run shell commands, view/create/edit files |
Read | filesystem | Yes | Read file contents or list a directory |
Write | filesystem | No | Write file contents |
Edit | filesystem | No | Edit files with search/replace |
Glob | filesystem | Yes | Find files by pattern |
Grep | filesystem | Yes | Search file contents |
WebSearch | web | Yes | Search the web (Tavily/Brave/SerpAPI/DuckDuckGo) |
WebFetch | web | Yes | Fetch and extract web page text; SSRF-protected |
TodoWrite | todo | No | Manage a structured task list |
AskUserQuestion | user_input | No | Ask the user a question |
ToolSearch | meta | Yes | Search available tools by keyword |
Task | orchestration | No | Delegate work to a registered sub-agent |
Taskis only registered whenagentsare configured on the options object.
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_toolsis empty (the default), all tools are available. When non-empty, only the listed tools are available.disallowed_toolsalways overridesallowed_tools.
Permission modes
| Mode | Behavior |
|---|---|
"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
| Class | Field | Default | Description |
|---|---|---|---|
PermissionResultAllow | updated_input | None | Replacement input dict; None = use original |
PermissionResultAllow | updated_permissions | None | Advisory rule changes to the session's permission state |
PermissionResultDeny | message | "" | Reason shown to the agent |
PermissionResultDeny | interrupt | False | True = hard abort; False = soft deny |
When
PermissionResultAllow.updated_inputis 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_toolis set,AskUserQuestionfalls back to a blockinginput()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
)