Tools¶
Tools are the actions the agent can take. Each tool is a Python class that inherits from BaseTool and declares:
- name — used in tool schemas and audit log
- description — what the AI sees
- input_schema — JSON Schema for parameters (Anthropic-native format)
- requires_confirmation — whether the agent must ask before executing
- allowed_in_scheduled_tasks — whether the tool can be used by cron agents
Available tools¶
| Tool | Operations | Confirmation | Scheduled |
|---|---|---|---|
| bash | execute shell commands | yes | no |
| brain | capture, search, browse, stats | no | yes |
| browser | fetch_page, screenshot, click, fill, select, press | read: no / interactive: domain-based | yes |
| caldav | list_events, get_event, create_event, update_event, delete_event | delete only | yes |
| contacts | list_contacts, search_contacts, create_contact, update_contact | no | yes |
| list_emails, read_email, send_email, list_whitelist | send only | yes | |
| filesystem | read_file, list_directory, write_file, delete_file | write/delete only | yes |
| image_gen | generate | no | yes |
| pushover | send_notification | no | yes |
| telegram | send_message | no | yes |
| web | fetch_page, search | no | yes |
| webhook | trigger_webhook, send_to_target | no | yes |
| whitelist | list_domains, add_domain, remove_domain | no | yes |
bash¶
Shell command execution. Admin only — never injected for non-admin users.
Returns: stdout, stderr, exit code, execution time.
Why admin-only: Shell access is the most powerful capability in the system. Restricting it to admin users limits blast radius if the agent is manipulated.
brain¶
Interface to the 2nd Brain (PostgreSQL + pgvector). Requires the brain database to be configured.
| Operation | What it does |
|---|---|
capture |
Save a memory or document with embedding |
search |
Semantic similarity search |
browse |
Retrieve recent entries |
stats |
Count of entries, storage size |
Per-user setting: brain_auto_approve — when enabled, the agent uses the brain tool proactively without being asked.
browser¶
Headless Chromium browser via Playwright. Useful for JavaScript-heavy pages where the web tool returns incomplete content.
Read operations (no confirmation):
- fetch_page — navigate and extract page text
- screenshot — capture PNG as base64
Interactive operations (confirmation required unless domain is trusted):
- click — click an element by CSS selector
- fill — type into an input field
- select — choose a <select> option
- press — press a keyboard key
Session state: Page state is kept across calls within the same session ID. Use fetch_page first to navigate, then interactive ops without a url to act on the current page.
Domain approval: Add domains to Settings → Whitelists → Browser Trusted Domains (or the user's Settings → Browser tab) to skip confirmation for interactive ops on those domains.
Requires: playwright install chromium in the container/environment.
caldav¶
CalDAV calendar integration. Connects to the URL and credentials configured per-user in Settings → CalDAV/CardDAV.
| Operation | What it does |
|---|---|
list_events |
List events in a date range |
get_event |
Get full event details by UID |
create_event |
Create a new calendar event |
update_event |
Update an existing event |
delete_event |
Delete an event (requires confirmation) |
Note: CalDAV must be configured per-user. There is no system-wide fallback.
contacts¶
CardDAV contacts integration. Uses the CardDAV URL configured per-user.
| Operation | What it does |
|---|---|
list_contacts |
List all contacts |
search_contacts |
Search by name or email |
create_contact |
Create a new contact (vCard) |
update_contact |
Update an existing contact |
Implementation: Uses httpx directly with REPORT/PUT/DELETE HTTP methods (the caldav Python library does not support AddressBooks).
email¶
IMAP email reading and SMTP sending. Credentials configured per-user in Settings → Email Accounts.
| Operation | What it does |
|---|---|
list_emails |
List inbox (or any folder), with optional unread-only filter |
read_email |
Read a message by UID — body is sanitised before returning |
send_email |
Send to one or more addresses (all must be in whitelist) |
list_whitelist |
Return all approved recipient addresses |
Security: send_email always requires confirmation in interactive sessions. Recipients must be in the email_whitelist table. Email bodies are sanitised with sanitize_external_content() before being returned to the agent.
Max body: 10,000 characters (6,000 when truncation is enabled in Security settings).
filesystem¶
Sandboxed file I/O. Access is restricted to directories in Settings → Whitelists → Filesystem.
| Operation | What it does |
|---|---|
read_file |
Read a file's contents |
list_directory |
List files and subdirectories |
write_file |
Write text or binary content (requires confirmation) |
delete_file |
Delete a file (requires confirmation) |
Non-admin users: Get BoundFilesystemTool scoped to {base}/{username}/ instead of the global whitelist.
Path traversal prevention: Paths are resolved with os.path.realpath() before any containment check.
image_gen¶
Image generation via the configured provider. Returns base64-encoded PNG data URLs that the browser renders inline in the chat.
{
"operation": "generate",
"prompt": "A cozy home office with plants and warm lighting",
"size": "1024x1024"
}
pushover¶
Push notifications to iOS/Android via Pushover.
{
"operation": "send_notification",
"message": "Daily summary: 3 new emails, calendar clear tomorrow",
"title": "Jarvis",
"priority": 0
}
Priorities: -2 (lowest), -1, 0 (normal), 1, 2 (emergency with retry).
Configuration: Admin sets pushover_app_token in Credentials. Each user sets their own pushover_user_key in Settings → Pushover.
telegram¶
Send messages to Telegram chats. Outbound only — the Telegram listener handles incoming messages separately.
{
"operation": "send_message",
"chat_id": "123456789",
"message": "Weather alert: rain expected in 30 minutes"
}
Security: Chat IDs must be in telegram_whitelist for the calling user. Configured in Settings → Telegram.
web¶
Web search (DuckDuckGo) and page fetching. Does not require Playwright — pure HTTP.
| Operation | What it does |
|---|---|
fetch_page |
Fetch a URL and extract text (strips scripts, styles, nav) |
search |
DuckDuckGo search — returns titles, URLs, snippets |
Tier 1 (whitelist): Always allowed. Seeded with: duckduckgo.com, wikipedia.org, weather.met.no, api.met.no, yr.no, timeanddate.com. Manage via Settings → Whitelists → Web.
Tier 2 (any domain): Only when the user's message suggests web research, or in a scheduled task that declared web access.
Content limit: 50 KB raw (20,000 chars when truncation is enabled).
webhook¶
Send HTTP requests to configured webhook targets or trigger inbound webhook endpoints.
| Operation | What it does |
|---|---|
trigger_webhook |
Trigger a named inbound webhook endpoint |
send_to_target |
POST JSON to a named outbound webhook target |
Webhook targets are configured in Settings → Webhooks. Outbound webhooks can optionally include a secret header for authentication.
whitelist¶
Manage the Tier 1 web whitelist from within a conversation. Useful when accessing via Telegram (no browser access to the Settings UI).
| Operation | What it does |
|---|---|
list_domains |
List all Tier 1 domains |
add_domain |
Add a domain to the whitelist |
remove_domain |
Remove a domain |
Note: Modifies the global web_whitelist table. Changes are visible to all users immediately.
MCP proxy tools¶
MCP (Model Context Protocol) server tools are discovered dynamically. Each MCP tool is namespaced as mcp__{servername}__{toolname}. These are not listed here because they depend on your configured MCP servers.
Manage MCP servers in Settings → MCP Servers.
Adding a custom tool¶
- Create
server/tools/my_tool.pyinheritingBaseTool - Implement
execute()— never raise; returnToolResult - Register in
server/tools/__init__.py::build_registry()
class MyTool(BaseTool):
name = "my_tool"
description = "Does something useful."
input_schema = {
"type": "object",
"properties": {
"action": {"type": "string", "enum": ["do_thing"]},
},
"required": ["action"],
}
requires_confirmation = False
async def execute(self, action: str, **kwargs) -> ToolResult:
try:
result = await some_async_operation()
return ToolResult(success=True, data={"result": result})
except Exception as e:
return ToolResult(success=False, error=str(e))