Tools
Custom tools live in tools/ as directories with a handler file and optional metadata.
tools/
└── create_ticket/
├── tool.json ← (optional) metadata, parameters, confirmation
├── handler.ts ← handler code
├── package.json ← (optional) npm dependencies
└── requirements.txt ← (optional) Python dependenciesPlatform Tools (built-in)
These are always available — you don't define them:
| Tool | What It Does |
|---|---|
| request | HTTP calls to connected systems with automatic auth. Declares intent: 'read' | 'write'. |
| shell_exec | Run scripts (sandboxed in hosted environments via Daytona). |
| load_knowledge | Pull KB documents into context on demand. |
| propose_knowledge | Propose knowledge base updates. |
| present | Render widgets: entity-card, timeline, data-table, score-breakdown, metric, info-card, etc. |
| dispatch | Delegate work to task agents with isolated context. |
| explore | Query connected systems and gather context. |
| store_* | Auto-generated from store definitions. |
Custom Tool Definition
Option A: tool.json + handler.ts
tool.json:{
"name": "create_ticket",
"description": "Create a Jira issue in the ops project",
"parameters": {
"type": "object",
"properties": {
"summary": { "type": "string" },
"priority": { "type": "string", "enum": ["P1", "P2", "P3", "P4"] }
},
"required": ["summary"]
},
"confirm": "review",
"timeout": 30000,
"env": ["JIRA_API_TOKEN"]
}| Field | Type | Default | Description |
|---|---|---|---|
name | string | directory name | Tool name (snake_case) |
description | string | required | Shown to the LLM |
parameters | JSON Schema | {} | Input parameters |
confirm | false | true | "review" | "never" | false | Confirmation tier |
timeout | number | 30000 | Timeout in ms |
env | string[] | [] | Allowed env var names |
responseShaping | object | — | Transform response before returning |
sandbox.language | string | "typescript" | Handler language |
export default async (params, ctx) => {
const result = await ctx.request('jira', '/rest/api/3/issue', {
method: 'POST',
data: {
fields: {
project: { key: 'OPS' },
summary: params.summary,
priority: { name: params.priority },
issuetype: { name: 'Task' },
},
},
})
return { ticketId: result.key, url: result.self }
}Option B: defineToolHandler (single file)
import { defineToolHandler } from '@amodal/core'
export default defineToolHandler({
description: 'Calculate weighted pipeline value',
parameters: {
type: 'object',
properties: {
deal_ids: { type: 'array', items: { type: 'string' } },
},
required: ['deal_ids'],
},
confirm: 'review',
timeout: 60000,
env: ['STRIPE_API_KEY'],
handler: async (params, ctx) => {
const deals = await ctx.request('crm', '/deals', {
params: { ids: params.deal_ids.join(',') },
})
return { total: deals.reduce((sum, d) => sum + d.amount, 0) }
},
})Handler Context
The ctx object available in every handler:
| Method | Description |
|---|---|
ctx.request(connection, endpoint, options?) | Make an authenticated API call |
ctx.exec(command, options?) | Run a shell command |
ctx.env(name) | Read an allowed env var |
ctx.log(message) | Log a message |
ctx.user | User info: { roles: string[] } |
ctx.signal | AbortSignal for cancellation |
Naming Convention
Tool names must be snake_case: lowercase letters, digits, and underscores, starting with a letter. Example: create_ticket, fetch_deals, calculate_risk.