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

Built-in tools cover connection calls, stores, knowledge, skills, and chat controls. Custom tools in amodal/tools/<name>/ run your own code. A custom tool takes one of two layouts:

amodal/tools/
├── summarize_ticket/
│   └── tool.ts            ← metadata and handler in one module
└── review_request/
    ├── tool.json          ← metadata
    ├── handler.ts         ← default export (params, ctx) => result
    ├── package.json       ← (optional) npm dependencies
    └── requirements.txt   ← (optional) Python dependencies

When tool.ts exists, the directory uses that layout and ignores any tool.json. Composition (ctx.callTool, ctx.callSubagent), durable execution, triggers, the env allowlist, and UI labels require the tool.json layout.

Tool code ships with the agent repository. Commit, deploy, and read the build logs for TypeScript, dependency, and validation errors. See Deployment API.

Built-in Tools

ToolRegistered whenWhat it does
request≥1 REST connection without openapi or skipRequestTool: trueHTTP calls with connection authentication and policy checks. See Confirmation Model.
<connection>__<operationId>connection with openapi.sourceTyped operation calls. See Native OpenAPI connections.
<connection>__discoverconnection with openapi.sourceSearch and activate operation tools for the next reply.
read_spilled_result≥1 native operation toolPage a large stored connection result.
store__<store>__getstore definedRead one document by key. Hyphens in the store name become underscores. See Stores.
store__<store>__setstore definedCreate or update one document.
store__<store>__querystore definedFilter documents.
store__<store>__liststore definedList documents.
store__<store>__removestore sets deletable: trueDelete one document.
call_subagent≥1 invokable subagentRun authored subagents, up to 12 per call and five at a time. See Agents.
presentunscoped chat, or declared in a named agent's toolsRender a widget such as an info card, data table, or metric.
stop_executionalwaysEnd the current turn.
ask_choicealwaysAsk a single- or multi-select question. Calling it ends the turn; the choice arrives as the next user message.
load_skill≥1 on-demand skill in scopeReturn a skill's instructions.
load_knowledge≥1 Markdown knowledge documentReturn a knowledge document's body.
knowledge_search≥1 corpus or library, and tool in scopeSearch a corpus or library.
knowledge_read≥1 corpus or library, and tool in scopeRead a section or line range.
web_search, fetch_urlwebTools configured and tool in scopeSee Web Tools.
memorymemory enabled and editable, and tool in scopePersistent memory with add, remove, list, and search. See Memory.
*_repo_file, *_repo_filesdeclared in the agent's toolsread_repo_file, write_repo_file, edit_repo_file, delete_repo_file, list_repo_files, glob_repo_files, grep_repo_files, read_many_repo_files. Writes, edits, and deletes require confirmation. Disable all with "fileTools": false in amodal.json.
propose_source_changehosted, declared in the agent's tools (or agents/main/agent.json for default chat)Propose edits to the agent's own prompt, skills, knowledge, and tool code. They wait in Studio under Proposals; approval commits and deploys them. See Proposals. Pair it with list_repo_files and read_repo_file. Registering it also registers read_amodal_docs, which reads the Amodal docs index or one docs page. An agent cannot add a new tool or change amodal.json, agent.json, tool.json, connections, or secrets.
<server>__<tool>≥1 MCP server connectedTools discovered from each MCP server.

A tool is "in scope" in unscoped chat, or when a named agent lists it in tools. In default chat, file tools come from the tools declared by agents/main/agent.json.

A skill added through propose_source_change reaches a named agent that lists its skills in agent.json only after someone adds it to that list.

Web Tools

{
  "webTools": {
    "provider": "google",
    "apiKey": "env:GOOGLE_API_KEY"
  }
}

"webTools": true is shorthand for the Google provider with env:GOOGLE_GENERATIVE_AI_API_KEY. google is the only provider. Both tools use Gemini grounding regardless of the agent's model. Set model to a Gemini model that supports Search and urlContext grounding, or omit it for the runtime default.

  • web_search({query, max_results}) returns a synthesized answer with cited source URLs. max_results defaults to 5, maximum 10.
  • fetch_url({url, prompt}) fetches and summarizes a public page through urlContext, and falls back to a local fetch with Readability extraction. The local fetch has a 10-second timeout and a 1 MB body cap. Each hostname is limited to 10 requests per 60 seconds.
  • Under amodal dev, private-network URLs (localhost, RFC 1918, .local) use the local fetch. A hosted runtime refuses private-network and other non-public addresses.

Single-file Tools

amodal/tools/<name>/tool.ts holds the metadata and the handler:

export default {
  id: 'summarize_ticket',
  exposure: { kind: 'open' },
  base: {
    name: 'summarize_ticket',
    description: 'Load a support ticket and return a concise operator summary.',
    parametersJsonSchema: {
      type: 'object',
      properties: { ticket_id: { type: 'string' } },
      required: ['ticket_id'],
    },
  },
  async handle(ctx) {
    const { ticket_id } = ctx.input;
    const ticket = await ctx.request('support-api', `/tickets/${encodeURIComponent(ticket_id)}`);
    const needsVerification = ticket.category === 'account_access' && ticket.identity_verified !== true;
    return {
      ticket_id: ticket.id,
      status: ticket.status,
      needs_verification: needsVerification,
    };
  },
};

The default export must have a handle(ctx) function. Parameters arrive in ctx.input.

FieldDefaultDescription
id (or base.name)directory name with - → _Tool name. Must be snake_case.
base.descriptiontool nameShown to the model. Say when to use the tool.
base.parametersJsonSchema{"type":"object","properties":{}}JSON Schema for the input.
exposure.kindopenopen runs without approval and is read-only for ctx.request. requires-confirmation pauses every model-selected call for approval. operator-gated behaves like open.
enabledtruefalse skips registration.
llm_callabletruefalse hides the tool from the model. Composites and triggers can still call it.

A tool.ts tool has a fixed 30-second timeout and an empty env allowlist, so ctx.env() returns undefined.

Tool Metadata

tool.json holds the metadata, and handler.ts default-exports (params, ctx) => result:

{
  "description": "Classify support ticket priority from category, impact, and age.",
  "parameters": {
    "type": "object",
    "properties": {
      "category": { "type": "string" },
      "affected_users": { "type": "number" }
    },
    "required": ["category", "affected_users"]
  },
  "timeout": 10000,
  "env": ["PRIORITY_RULES_URL"],
  "runningLabel": "Classifying {{category}} ticket",
  "completedLabel": "Classified ticket"
}
export default async function (params, ctx) {
  const broad = params.affected_users >= 10;
  return { priority: params.category === 'account_access' && broad ? 'high' : broad ? 'medium' : 'normal' };
}

The tool name is the directory name, which must be snake_case.

FieldDefaultDescription
descriptionrequiredShown to the model.
namedirectory nameMust match the directory name when set.
parameters{}JSON Schema for the input.
confirmfalsetrue or "review" lets ctx.request send write-intent calls. Neither prompts the user. "never" skips registration with a warning.
timeout30000Handler timeout in milliseconds.
env[]Environment variables ctx.env() may read.
runningLabel, completedLabelChat card text while running and after success. {{param}} substitutes a call parameter.
displayNameNoun phrase naming the tool in aggregating UIs.
reminder, reminderAfterTurns3Text restated to the model after the tool goes unused for that many turns.
uses, execution, triggersComposition, durable execution, and non-model triggers. See Agent Workflows.

The loader accepts enabled, internal, responseShaping, and sandbox in tool.json but does not apply them. A tool.json tool cannot require approval: use tool.ts with exposure.kind: "requires-confirmation" for that, or gate the action inside a durable tool.

Handler Context

MemberDescription
ctx.request(connection, endpoint, options?)Authenticated call through a connection. options takes method, params, data, and intent.
ctx.exec(command, options?)Run a shell command. options takes cwd and timeout. Resolves to {stdout, stderr, exitCode}.
ctx.store(storeName, payload)Write a document to a store. Resolves to { key }.
ctx.env(name)Read an allowlisted environment variable. undefined when not allowlisted.
ctx.log(message)Write a log line.
ctx.signalAbortSignal that fires on cancellation or timeout.
ctx.scopeIdPer-user scope key for the turn. Empty string at agent level.
ctx.scopeContextThe turn's scope context, as sent by the embedding app. It can carry client-supplied values, so do not use it for authorization. undefined when absent.
ctx.attachmentsFiles attached to the turn's message. Top-level tools only.
ctx.callTool, ctx.callSubagentComposition. See Agent Workflows.

Durable tools also get ctx.requestInput, ctx.waitForApproval, ctx.sleepUntil, ctx.step, ctx.now, and ctx.random. See Durable Tools.

ctx.request rules

ctx.request resolves the connection's base URL and attaches its credentials. It does not evaluate policy.json permissions, field restrictions, or confirmation, and it does not retry. Enforce authorization in the tool or the downstream API.

The call's intent defaults to write for POST, PUT, PATCH, and DELETE, and to read otherwise. A read-only tool (exposure.kind: "open" in tool.ts, or confirm: false in tool.json) throws on write-intent calls. Pass intent: 'read' for a read-only POST, such as a search endpoint:

const report = await ctx.request('analytics', 'properties/123:runReport', {
  method: 'POST',
  intent: 'read',
  data: { metrics: [{ name: 'eventCount' }] },
});

Naming

Tool names must be snake_case: lowercase letters, digits, and underscores, starting with a letter.