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.
| Field | Notes |
|---|---|
description | What it does. An AI model reads it when it's used as a tool. |
parameters | A list of { name, description, schema_type, required }. schema_type is a JSON Schema type (string, number, boolean, object, array); required defaults to true. |
result | The output field returned as the result (default 'result'). |
plans | Plans whose customers can run it. Null or empty: every plan. |
customers | Customer IDs that can run it. Null or empty: everyone. |
#[run] functions | The code. Arguments are in self.input; write to self.output. Several #[run] steps run in the order they're written. |
Running a capability
| Call | Does |
|---|---|
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.policyis the policy, so a capability can use any engine function:value,limit,plan_for,allow, and the rest.- Waiting on something? Use a plain
fnandawaitinside it. A capability's run is finished when its functions return, so anasync fnwould 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.