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-inSend an email from the agent’s own address.
communicationswriteread_after_writekeyedavailablerisk: what kind of harm a mistake could cause:none,communications,financial,legal,physical, orcustom.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, orhuman. 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), ornone(not safe, and not checkable).executionMode: set by Shadway, not you.availablemeans the capability can actually run.unavailablemeans 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.
email.sendmailbox.read_threadbrowser.runbilling.refundemail.sendmailbox.read_threadbrowser.runbilling.refundemail.sendmailbox.read_threadbrowser.runbilling.refundThe 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.
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);type PurchaseOrderInput struct {
Supplier string `json:"supplier"`
Part string `json:"part"`
Quantity int `json:"quantity"`
AmountMinor int64 `json:"amount_minor"`
}
type PurchaseOrderOutput struct {
PurchaseReference string `json:"purchase_reference"`
}
risk := shadway.CapabilityRiskFinancial
effect := shadway.CapabilityEffectIrreversible
createPO, err := shadway.DefineCapability(shadway.DefineCapabilityOptions[PurchaseOrderInput, PurchaseOrderOutput]{
Name: "procurement.create_po",
Description: "Create a purchase order in the ERP system.",
Risk: &risk,
Effect: &effect,
Execute: func(ctx context.Context, input PurchaseOrderInput) (PurchaseOrderOutput, error) {
// Call your ERP system here.
return PurchaseOrderOutput{PurchaseReference: "PO-2041"}, nil
},
})
if err != nil {
log.Fatal(err)
}
if _, err := client.Capabilities.Register(ctx, createPO); err != nil {
log.Fatal(err)
}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.
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 ?? "");
}
}saved, err := client.Agents.Get(ctx, agent.ID)
if err != nil {
log.Fatal(err)
}
if saved.CapabilityStatus != nil {
for name, readiness := range *saved.CapabilityStatus {
if readiness.Status != "ready" {
detail := ""
if readiness.Detail != nil {
detail = *readiness.Detail
}
fmt.Println(name, readiness.Status, detail)
}
}
}