Skip to main content

Capabilities

A capability is a named, typed operation contract. Capabilities are how actions interact with the outside world — or with reusable logic defined inside the spec itself.

There are two kinds of capabilities: domain capabilities and provider capabilities. They share the same call syntax in action steps but have fundamentally different implementations.

Domain capabilities

A domain capability is authored in the spec and executed in-process by the domain runtime. It is a pure function: given an input, it returns an output with no side effects.

domain:
capabilities:
message.is_allowed:
in:
message: text!
out:
allowed: bool!
reason: text
do:
- expr:
"!contains":
- to_lower: { var: [input.message, ""] }
- spam
as: allowed
return:
allowed: { $expr: { var: allowed } }
reason:
$expr:
if:
- var: allowed
- null
- "Blocked by moderation policy"

The do block is a sequence of expr steps that compute intermediate values. The return block produces the final output using $expr.

Domain capabilities have no access to provider capabilities. They cannot perform network calls, write to collections, or emit signals. This constraint is enforced at compilation.

Provider capabilities

A provider capability is an external operation contract fulfilled by a provider resolved at deploy time. Provider capabilities are declared in the domain.capabilities section only if they are used — their implementation is never authored in the spec.

Common provider capabilities:

CapabilityDescription
ids.generateGenerate one or more unique IDs
collections.writeWrite a record to a collection
collections.readRead a record from a collection by ID
notifications.sendDeliver a notification to a user
session.readRead from the current user's session
session.writeWrite to the current user's session

Provider capabilities are bound in the wiring.requirements section, not in domain.capabilities.

Calling a capability from an action

Both capability kinds are called the same way inside an action's do block.

domain:
actions:
guestbook.submit:
do:
- call: ids.generate
with: { prefix: "msg_", count: 1 }
as: generated_ids

- call: message.is_allowed
with:
message: { $expr: { var: input.message } }
as: moderation

- call: collections.write
with:
patch:
op: upsert
collection: hello_messages
id: { $expr: { var: generated_ids.ids.0 } }
value:
id: { $expr: { var: generated_ids.ids.0 } }
message: { $expr: { var: input.message } }
user_id: { $expr: { var: principal.subject } }
created_at: { $expr: { var: ctx.now } }

The as field binds the capability's output to a named variable available in subsequent steps. Variables are accessed with var: <name>.<field>.

What's next