Shadway

Core concepts / Capabilities

Capabilities

A capability is one thing an agent can do, like send an email or place a call. Each one takes specific inputs and returns a specific output. You give an agent capabilities to decide what it's allowed to do.

Capabilities are named per action, and the name always has a dot in it: email.send, mailbox.read_thread, browser.run. The name describes the action, not the vendor. email.send says what happens, not which email provider does it.

What a capability defines

Each capability spells out what the action does, how risky it is, and how Shadway should treat the result. This is what lets Shadway apply limits and check outcomes without knowing anything else about your integration.

email.sendBuilt-in

Send an email from the agent’s own address.

Riskcommunications
Effectwrite
Verifyread_after_write
Idempotencykeyed
Executionavailable
SchemasTyped input and output, JSON Schema
  • risk: what kind of harm a mistake could cause: none, communications, financial, legal, physical, or custom.
  • effect: whether the action reads, writes, or does something you can't undo. Reads have their own budget, and actions you can't undo are handled more carefully.
  • verify: how Shadway confirms the result: none, read_after_write, authoritative_source, or human. See Verification.
  • idempotency: whether the action is safe to repeat: native (always safe), keyed (safe when the vendor honors an idempotency key), reconcilable (not safe to repeat, but Shadway can check afterward what happened), or none (not safe, and not checkable).
  • executionMode: set by Shadway, not you. available means the capability can actually run. unavailable means it's defined but has nothing behind it yet. A mandate that needs an unavailable capability is rejected right away.

How capabilities narrow

A capability passes through three levels, and each level can only cut down the one before it. Your workspace defines the full set, an agent gets some of them, and a mandate gets some of what the agent has, for one job.

Capability grantsEach level can only narrow the one above it
Registered in the workspaceThe catalog of typed contracts
email.sendmailbox.read_threadbrowser.runbilling.refund
Granted to the agentWhat this agent may ever use
email.sendmailbox.read_threadbrowser.runbilling.refund
Allowed by this mandateauthority.capabilities for one piece of work
email.sendmailbox.read_threadbrowser.runbilling.refund

A mandate can grant only capabilities its agent holds. Omit authority.capabilities and the mandate uses the agent’s full set.

The mandate is where the limits actually bite: every name in authority.capabilities has to be a full action name that the agent has and the workspace knows. An agent's own capabilities list can also include a family name like phone, which is used to set up and configure the capability, but a mandate can only grant exact action names.

Define your own capability

Registering your own capability gives your action the same setup as a built-in one. You define the input and output types, and the SDK turns them into JSON schemas.

register-capability.ts
import { defineCapability } from "shadway";
import { z } from "zod";

const createPurchaseOrder = defineCapability({
  name: "procurement.create_po",
  description: "Create a purchase order in the ERP system.",
  input: z.object({
    supplier: z.string(),
    part: z.string(),
    quantity: z.number().int().positive(),
    amount_minor: z.number().int().positive(),
  }),
  output: z.object({
    purchase_reference: z.string(),
  }),
  risk: "financial",
  effect: "irreversible",
  execute: async (input) => {
    // Call your ERP system here.
    return { purchase_reference: "PO-2041" };
  },
});

await shadway.capabilities.register(createPurchaseOrder);

A capability's input and output are part of what it is. After you register it, you can only change the description, status, and metadata. To change the inputs or outputs, register a new capability under a new name or version.

Check whether a capability is ready

Some built-in capabilities have to set something up first. A phone capability, for example, has to get a phone number. After you create or update an agent, read it back and check capabilityStatus. Each entry is provisioning, requires_action, ready, unavailable, or failed, with a detail that explains anything that isn't ready. A capability that doesn't need to set anything up is ready as soon as the agent has it.

check-readiness.ts
const saved = await shadway.agents.get(agent.id);

for (const [name, readiness] of Object.entries(saved.capabilityStatus ?? {})) {
  if (readiness.status !== "ready") {
    console.log(name, readiness.status, readiness.detail ?? "");
  }
}

Continue building

On this page