> ## Documentation Index
> Fetch the complete documentation index at: https://docs.svantic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining capabilities

# Defining capabilities

A **capability** is a function on your agent that another agent, a user in the dashboard, or the agent's own LLM can invoke. Capabilities are the public contract — everything else (LLMs, triggers, MCP, scheduled work) eventually reduces to "call a capability."

This guide covers writing capabilities you'd ship to production.

## The minimum

```typescript theme={null}
agent.define_capability({
  name: 'lookup_invoice',
  description: 'Fetch an invoice by its id and return the line items.',
  parameters: {
    type: 'object',
    properties: {
      invoice_id: { type: 'string', description: 'Invoice id (e.g. inv_123)' },
    },
    required: ['invoice_id'],
  },
  handler: async ({ invoice_id }) => {
    const invoice = await db.invoices.find(invoice_id);
    return { invoice };
  },
});
```

Three things matter:

1. **`name`** — snake\_case, unique per agent. This is what callers and LLMs see.
2. **`description`** — what the capability does, written for an LLM. Good descriptions double as good docs.
3. **`parameters`** — JSON Schema validated before your handler runs.

## Writing good schemas

* Every field gets a `description`. The LLM leans on these more than the types.
* Mark everything the handler can't synthesize as `required`. Unmarked optional fields are the common source of "the model never fills this in" bugs.
* Prefer `enum` over free-form strings when the value is one of a closed set (`status: { type: 'string', enum: ['draft', 'sent', 'paid'] }`).
* Keep nesting shallow. LLMs are measurably worse at deeply nested objects than at flat ones.

The full schema shape is documented in [`CapabilitySchema`](../reference/types#capabilityschema).

## The handler

```typescript theme={null}
handler: async (args, context) => {
  // args is typed as Record<string, unknown> — cast or validate as needed.
  // context: CapabilitySessionContext (see below).
  return { /* plain JSON-serializable result */ };
}
```

* Return any JSON-serializable value. It ends up in the `DataPart.data.result` that callers receive.
* Throwing from the handler surfaces as an A2A error to the caller. Throw `Error` subclasses with clear messages.
* Long-running work is fine — there's no hard timeout in the SDK; the platform's dispatch timeout (default 60 s) applies end-to-end.

## The context

The second argument is a [`CapabilitySessionContext`](../reference/types#capabilitysessioncontext) with:

* `session_id` / `tenant_id` — always present; use for scoping and auditing.
* `propagation_headers` / `parent_trace_id` / `parent_span_id` / `baggage` — W3C trace context, present when the caller had an active trace. Forward `propagation_headers` verbatim to downstream HTTP services; use `parent_trace_id` / `parent_span_id` when creating custom spans. See [Trace propagation](../reference/trace-propagation).

```typescript theme={null}
handler: async ({ invoice_id }, { tenant_id, propagation_headers }) => {
  const res = await fetch(`https://billing.internal/${tenant_id}/invoices/${invoice_id}`, {
    headers: propagation_headers ?? {},
  });
  return { invoice: await res.json() };
}
```

## Errors

Throw. The SDK wraps the exception as an A2A error part the caller can inspect. For expected failures, use a clear message:

```typescript theme={null}
if (!invoice) {
  throw new Error(`invoice_not_found: ${invoice_id}`);
}
```

For validation-like failures, prefer tightening the JSON Schema — the platform rejects invalid inputs before your handler runs.

## Tags

Use `tags` to categorize capabilities on the agent card:

```typescript theme={null}
agent.define_capability({
  name: 'issue_refund',
  tags: ['billing', 'destructive'],
  /* … */
});
```

Tags are displayed in the dashboard and can be used by orchestrators to filter what the LLM sees.

## Adding capabilities after start

You can call `define_capability` at any time, but new capabilities aren't advertised to Svantic until you re-register. When you're using `agent.start()`, call `agent.stop()` and re-`start()`. When you're using a standalone `MeshConnector`, call `mesh.re_register()`.

## See also

* [`Agent.define_capability`](../reference/agent#define_capabilityconfig)
* [`CapabilityConfig`](../reference/types#capabilityconfig)
* [Calling other agents](./calling-other-agents) — what the other side of the wire looks like.
