agentskit.js
Concepts

Tool

A function the model can call, with JSON-schema-typed arguments and a JSON-serializable result.

A Tool is a function the model can request by name. It is the primary mechanism by which an agent acts on the world: reading files, calling APIs, searching the web, executing code, editing documents, sending Slack messages.

If a Skill is what the model becomes, a Tool is what the model calls. That distinction is load-bearing — confusing the two is the recurring failure mode in agent libraries.

The interface

import type { ToolDefinition } from '@agentskit/core'
import type { JSONSchema7 } from 'json-schema'

export interface ToolDefinition {
  name: string
  description?: string
  schema?: JSONSchema7
  requiresConfirmation?: boolean
  execute?: (
    args: Record<string, unknown>,
    context: ToolExecutionContext,
  ) => MaybePromise<unknown> | AsyncIterable<unknown>
  init?: () => MaybePromise<void>
  dispose?: () => MaybePromise<void>
  tags?: string[]
  category?: string
}

Defining a tool

import type { ToolDefinition } from '@agentskit/core'

export const getWeather: ToolDefinition = {
  name: 'get_weather',
  description: 'Get the current weather for a city.',
  schema: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'City name, e.g. "Madrid"' },
    },
    required: ['city'],
  },
  async execute(args) {
    const res = await fetch(`https://wttr.in/${args.city}?format=j1`)
    return await res.json()
  },
}

The model sees name, description, and schema. The runtime validates args against schema before calling execute. Your function never receives malformed input.

Using built-in tools

import { webSearch, filesystem, shell } from '@agentskit/tools'

createRuntime({
  adapter,
  tools: [
    webSearch(),
    ...filesystem({ basePath: './workspace' }),
    shell({ allowedCommands: ['ls', 'cat'] }),
  ],
})

Confirmation gates

Set requiresConfirmation: true and the runtime will pause before executing, asking your onConfirm handler:

const dangerousTool: ToolDefinition = {
  name: 'delete_file',
  schema: { /* ... */ },
  requiresConfirmation: true,
  async execute(args) { /* ... */ },
}

createRuntime({
  adapter,
  tools: [dangerousTool],
  onConfirm: async (call) => {
    return await showConfirmationDialog(call)
  },
})

There is no timeout-based auto-approval. If the runtime requires confirmation but no onConfirm is configured, execution is refused. This matters for security-critical tools and is non-negotiable.

Streaming tool execution

Long-running tools can yield progress as an AsyncIterable:

const slowTool: ToolDefinition = {
  name: 'process_dataset',
  schema: { /* ... */ },
  async *execute(args) {
    yield 'Loading dataset...'
    const data = await loadDataset(args.path)
    yield `Processing ${data.length} rows...`
    const result = await process(data)
    return result   // the last yielded value is the recorded result
  },
}

Consumers see partial values as informational; only the last one is recorded as the official result.

Declaration-only tools (MCP-friendly)

A tool without execute is a declaration — the model can call it, and the caller (typically a client or an MCP bridge) handles the actual execution. This is what makes the AgentsKit Tool contract a thin mapping over MCP's tool spec.

const remoteTool: ToolDefinition = {
  name: 'lookup_customer',
  description: 'Find a customer by email.',
  schema: { type: 'object', properties: { email: { type: 'string' } } },
  // no execute — runs elsewhere
}

Common pitfalls

PitfallWhat to do instead
Returning a Date or Buffer from executeSerialize: date.toISOString(), buffer.toString('base64')
Throwing from execute on a recoverable errorReturn { error: '...' } so the model can react and retry
Implementing your own confirmation timeoutDon't. Use requiresConfirmation + onConfirm properly
Doing I/O at definition time (top-level await)Move it into init() or execute() — see invariant T10
Naming tools with spaces or special charactersNames must match ^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$

Going deeper

The full list of invariants (twelve of them, T1–T12) is in ADR 0002 — Tool contract.

✎ Edit this page on GitHub·Found a problem? Open an issue →·How to contribute →

On this page