AuthorityLayer V1 implements exactly three enforcement primitives. Each is independently opt-in — omit a config key to disable that primitive entirely. Unused guards are never instantiated.
Config key: budget.dailyUSD
Tracks cumulative USD spend across the lifetime of the process. Halts when the total exceeds the configured cap.
const authority = new AuthorityLayer({
budget: { dailyUSD: 50 },
});Spend must be reported explicitly by the host. AuthorityLayer does not know how your model provider prices tokens — you calculate the USD cost and report it:
const response = await authority.tool("openai.chat", () =>
openai.chat.completions.create({ model: "gpt-4o", messages })
);
// Calculate cost from token counts (provider-specific)
const costUSD = response.usage.total_tokens * PRICE_PER_TOKEN;
authority.recordSpend(costUSD);recordSpend() is cumulative. It accumulates across all calls within the current process lifetime — not per wrap() run.
Why explicit spend reporting? Different providers expose token counts and pricing differently. AuthorityLayer doesn't assume your pricing model — you calculate the USD cost and report it. This makes the integration provider-agnostic.
"budget_exceeded"
Config key: loopGuard.maxToolCallsPerRun
Limits the total number of tool calls within a single wrap() invocation. The counter resets at the start of each wrap() call, so limits are per-run.
const authority = new AuthorityLayer({
loopGuard: { maxToolCallsPerRun: 25 },
});Every call to authority.tool() increments the counter by one, before the tool function executes. If the counter exceeds the limit, the tool function is never called.
"loop_limit_exceeded"
Config key: toolThrottle.maxCallsPerMinute
Rate-limits tool calls using a sliding 60-second window — not a fixed reset bucket. This always reflects the true call density in the last minute, and prevents bursts at bucket boundaries.
const authority = new AuthorityLayer({
toolThrottle: { maxCallsPerMinute: 60 },
});The throttle maintains a list of call timestamps. On each authority.tool() call:
- Timestamps older than 60 seconds are evicted.
- The current timestamp is added.
- If the resulting count exceeds
maxCallsPerMinute, execution halts.
No fixed reset buckets. No sudden bursts allowed at bucket boundaries.
"tool_throttle_exceeded"
When any guard breaches, AuthorityLayer throws an EnforcementHalt error. The structured halt object is available at err.enforcement:
import { AuthorityLayer, EnforcementHalt } from "authority-layer";
try {
await authority.wrap(async () => {
// ... agent loop
});
} catch (err) {
if (err instanceof EnforcementHalt) {
// Always access via err.enforcement — never parse err.message
const { reason, limit, spent, event_id } = err.enforcement;
console.error(`Halted: ${reason} (${spent} > ${limit}) [${event_id}]`);
// e.g. "Halted: budget_exceeded (52.14 > 50) [evt_3f9a2c1b...]"
}
}See docs/api.md for the full HaltResult type definition.
All three primitives flow through two points of contact:
| Call | Enforces |
|---|---|
authority.wrap(fn) |
Resets loop counter; wraps run lifecycle |
authority.tool(name, fn) |
Loop guard + tool throttle before fn executes |
authority.recordSpend(usd) |
Budget cap check after you calculate cost |
Any primitive can be disabled by omitting its config key:
// Only loop guard — no budget or throttle
const authority = new AuthorityLayer({
loopGuard: { maxToolCallsPerRun: 10 },
});