Skip to main content

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

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

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.

The handler

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 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.
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:
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:
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