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 dependenciesWhen 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
| Tool | Registered when | What it does |
|---|---|---|
request | ≥1 REST connection without openapi or skipRequestTool: true | HTTP calls with connection authentication and policy checks. See Confirmation Model. |
<connection>__<operationId> | connection with openapi.source | Typed operation calls. See Native OpenAPI connections. |
<connection>__discover | connection with openapi.source | Search and activate operation tools for the next reply. |
read_spilled_result | ≥1 native operation tool | Page a large stored connection result. |
store__<store>__get | store defined | Read one document by key. Hyphens in the store name become underscores. See Stores. |
store__<store>__set | store defined | Create or update one document. |
store__<store>__query | store defined | Filter documents. |
store__<store>__list | store defined | List documents. |
store__<store>__remove | store sets deletable: true | Delete one document. |
call_subagent | ≥1 invokable subagent | Run authored subagents, up to 12 per call and five at a time. See Agents. |
present | unscoped chat, or declared in a named agent's tools | Render a widget such as an info card, data table, or metric. |
stop_execution | always | End the current turn. |
ask_choice | always | Ask 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 scope | Return a skill's instructions. |
load_knowledge | ≥1 Markdown knowledge document | Return a knowledge document's body. |
knowledge_search | ≥1 corpus or library, and tool in scope | Search a corpus or library. |
knowledge_read | ≥1 corpus or library, and tool in scope | Read a section or line range. |
web_search, fetch_url | webTools configured and tool in scope | See Web Tools. |
memory | memory enabled and editable, and tool in scope | Persistent memory with add, remove, list, and search. See Memory. |
*_repo_file, *_repo_files | declared in the agent's tools | read_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_change | hosted, 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 connected | Tools 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_resultsdefaults 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.
| Field | Default | Description |
|---|---|---|
id (or base.name) | directory name with - → _ | Tool name. Must be snake_case. |
base.description | tool name | Shown to the model. Say when to use the tool. |
base.parametersJsonSchema | {"type":"object","properties":{}} | JSON Schema for the input. |
exposure.kind | open | open runs without approval and is read-only for ctx.request. requires-confirmation pauses every model-selected call for approval. operator-gated behaves like open. |
enabled | true | false skips registration. |
llm_callable | true | false 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.
| Field | Default | Description |
|---|---|---|
description | required | Shown to the model. |
name | directory name | Must match the directory name when set. |
parameters | {} | JSON Schema for the input. |
confirm | false | true or "review" lets ctx.request send write-intent calls. Neither prompts the user. "never" skips registration with a warning. |
timeout | 30000 | Handler timeout in milliseconds. |
env | [] | Environment variables ctx.env() may read. |
runningLabel, completedLabel | Chat card text while running and after success. {{param}} substitutes a call parameter. | |
displayName | Noun phrase naming the tool in aggregating UIs. | |
reminder, reminderAfterTurns | 3 | Text restated to the model after the tool goes unused for that many turns. |
uses, execution, triggers | Composition, 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
| Member | Description |
|---|---|
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.signal | AbortSignal that fires on cancellation or timeout. |
ctx.scopeId | Per-user scope key for the turn. Empty string at agent level. |
ctx.scopeContext | The 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.attachments | Files attached to the turn's message. Top-level tools only. |
ctx.callTool, ctx.callSubagent | Composition. 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.