Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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 dependencies

Platform Tools (built-in)

These are always available — you don't define them:

ToolWhat It Does
requestHTTP calls to connected systems with automatic auth. Declares intent: 'read' | 'write'.
shell_execRun scripts (sandboxed in hosted environments via Daytona).
load_knowledgePull KB documents into context on demand.
propose_knowledgePropose knowledge base updates.
presentRender widgets: entity-card, timeline, data-table, score-breakdown, metric, info-card, etc.
dispatchDelegate work to task agents with isolated context.
exploreQuery 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"]
}
FieldTypeDefaultDescription
namestringdirectory nameTool name (snake_case)
descriptionstringrequiredShown to the LLM
parametersJSON Schema{}Input parameters
confirmfalse | true | "review" | "never"falseConfirmation tier
timeoutnumber30000Timeout in ms
envstring[][]Allowed env var names
responseShapingobjectTransform response before returning
sandbox.languagestring"typescript"Handler language
handler.ts:
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:

MethodDescription
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.userUser info: { roles: string[] }
ctx.signalAbortSignal 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.