Skip to main content

Capabilities

A capability is a named function that lives in the policy, with its own inputs, outputs, and access rules. Your app can run it by name, and an AI agent can call it as a tool. Because it's part of the policy, it travels with it: the same logic on every server and in Cloud, versioned with your pricing.

Capabilities are how a policy grows past pricing:

  • Answer questions about customers: a usage report, an upgrade quote, what's left this month.
  • Bring data in: load provider rates, exchange rates, or customer data from an API into the policy.
  • Connect services: wrap your app's functions or an allowed HTTP endpoint as one named operation.
  • Give AI agents tools, filtered by each customer's plan (AI tools).
import { Limitr } from '@formata/limitr';

const limitr = await Limitr.new(`
policy: {
credits: {
request: { label: 'API request' }
ai_token: { label: 'AI token', price: { amount: 0.00002 } }
}
plans: {
free: {
default: true
entitlements: {
api: { limit: { credit: 'request', value: 1_000 } }
}
}
pro: {
entitlements: {
api: { limit: { credit: 'request', value: 100_000 } }
chat: { limit: { credit: 'ai_token', mode: 'soft', value: 2_000_000 } }
}
}
}
capabilities: {
usage_report: {
description: 'What a customer has used of each entitlement on their plan'
parameters: [
{ name: 'customer', description: 'The customer ID', schema_type: 'string' }
]

#[run]
fn report() {
const policy = root.policy;
const id = self.input.customer;
const plan = policy.plan_for(id);
if (plan == null) return;

const lines = [];
for (const (name, _) in plan.entitlements.fields()) {
const used = policy.value(id, name);
const limit = policy.limit(id, name);
lines.push_back(name + ': ' + used + ' of ' + limit);
}
self.output.result = lines;
}
}
}
}`);

await limitr.ensureCustomer('ada', 'pro');
await limitr.allow('ada', 'api', 120);
await limitr.allow('ada', 'chat', 45_000);

// Your app, a support tool, or a scheduled job can run it by name.
console.log(await limitr.runCapability('usage_report', { customer: 'ada' }));

// And an AI agent can call it as a tool: it's in the tool definitions for the Claude API.
const tools = await limitr.claudeTools('ada');
console.log('tools:', tools.map((tool) => tool.name));

It prints:

[ 'api: 120 of 100000', 'chat: 45000 of 2000000' ]
tools: [ 'usage_report' ]

Defining a capability​

Write capabilities under capabilities in the policy, or add them at runtime with setCapabilities(stof). Either way, each one is named after its field.

FieldNotes
descriptionWhat it does. An AI model reads it when it's used as a tool.
parametersA list of { name, description, schema_type, required }. schema_type is a JSON Schema type (string, number, boolean, object, array); required defaults to true.
resultThe output field returned as the result (default 'result').
plansPlans whose customers can run it. Null or empty: every plan.
customersCustomer IDs that can run it. Null or empty: everyone.
#[run] functionsThe code. Arguments are in self.input; write to self.output. Several #[run] steps run in the order they're written.

Running a capability​

CallDoes
runCapability(name, args?, customerId?)Runs it and returns its result. With a customer ID, only if that customer can run it (null otherwise).
claudeTools(customerId?)Tool definitions for the Claude API, filtered by what the customer can run.
claudeToolUse(toolUse, customerId?)Runs a tool_use block from the model and returns the tool_result.
capability(name) / setCapabilities(stof)Read one, or add or replace them at runtime.

A result can be text, a number, a list, or an object. runCapability returns an object as a plain record.

Writing the code​

  • root.policy is the policy, so a capability can use any engine function: value, limit, plan_for, allow, and the rest.
  • Waiting on something? Use a plain fn and await inside it. A capability's run is finished when its functions return, so an async fn would return before its work is done.
  • It runs in the sandbox. No network unless your app allows hosts, and no files. Reach your app through host functions.
  • Keep what it creates in self.output, or drop it, so memory stays flat.

Loading data from an API​

A capability can fetch data and write it into the policy: here, a provider's latest rates into each credit's overhead_cost. Your app allows the host and runs it on a schedule, or whenever the provider changes its prices:

capabilities: {
sync_rates: {
description: 'Load the latest provider rates into the policy'

#[run]
fn sync() {
const response = await Http::fetch('https://rates.example.com/v1/rates');
const rates = Http::parse(response, new {});

// { "sonnet_input": 0.000003, "sonnet_output": 0.000015, ... }
for (const (name, rate) in rates.fields()) {
const credit = root.policy.credits.get(name);
if (credit != null) credit.overhead_cost = rate;
}
drop(rates);
self.output.result = 'synced';
}
}
}
limitr.doc.allowHttp(['rates.example.com']);
await limitr.runCapability('sync_rates');

The same shape loads exchange rates, settings, or customer data from your own services.