Runtime Controls is in beta. The SDK interface may change between releases.
Install
Why Runtime Controls
When agents run unattended, things go wrong:- Infinite loops - the agent retries the same failing call forever
- Runaway tool-call volume - unchecked tool calls can spike API usage quickly
- Duplicate side effects - the same write runs twice
- Unsafe actions - an agent executes destructive operations
- Cascading failures - one broken dependency can fan out failures
maxToolCalls is a call-count guardrail, not direct provider billing telemetry.
Examples
Wrap Any Async Call (No API Key)
JavaScript
Use run() Directly
JavaScript
runtime["signal"] is available in handlers but optional. Use it when your tool call supports cancellation.
Control Layers
Every control is opt-in and composable. Enable only what you need.Retry with Exponential Backoff
Automatically retries transient failures (503, 429, timeouts) with configurable backoff.
JavaScript
JavaScript
Tool-Call Budget
Per-run cap on the number of tool calls whenmaxToolCalls is configured. Budgets are scoped by runKey.
This is a tool-call-count budget, not a dollar-based cost meter.
JavaScript
Loop Breaker
Detects repeated no-progress patterns and escalates from warning to quarantine to stop.JavaScript
Circuit Breaker
Temporarily blocks calls to a failing dependency. Keyed by(tenant, toolName, destination).
JavaScript
cooldownMs.
Timeout and Cancellation
Per-call timeout with abort signal propagation.JavaScript
Policy Gates
Deterministicallow / deny / require_approval rules with specificity-based precedence.
JavaScript
- Higher tool specificity (exact name > prefix > wildcard)
- Higher destination specificity (exact host > wildcard subdomain >
*) - Longer action prefix match
- Stricter action (
deny>require_approval>allow) - Earlier rule index on exact tie (current behavior)
Idempotency
Prevents duplicate side effects by replaying prior results for calls with the sameidempotencyKey.
JavaScript
Concurrency Locks
Prevents simultaneous conflicting writes to the same resource.JavaScript
Verifier Hooks
Custom correctness gates that run before execution, after success, and after errors.JavaScript
Per-Tool and Per-Destination Overrides
Apply stricter or looser controls to specific tools or destinations.JavaScript
Agent Logic Safety
applyAgentLogicSafety adds pre-execution safety checks without changing RuntimeControls internals.
Injection Guard
Scans tool name, action, destination, and args for injection patterns before execution.JavaScript
patterns is omitted):
ignore (all|any|previous) instructionssystem promptdeveloper message<scriptrm -rf
Exit Conditions
Enforces maximum steps per run and can block calls after a terminal action.JavaScript
Intent Allowlist
Restricts which tool + action combinations are permitted. Anything not explicitly allowed is denied.JavaScript
Combining All Three
JavaScript
Observability
Event Callback
JavaScript
Event Sinks
JavaScript
Event Types
| Event | Meaning |
|---|---|
retry | A retry attempt was scheduled |
loop_warning | Repeated no-progress pattern detected |
loop_quarantine | Pattern quarantined (temporarily blocked) |
loop_stop | Pattern hard-stopped |
circuit_open | Dependency circuit breaker opened |
budget_stop | Run tool-call budget exceeded |
policy_denied | Policy denied a call |
policy_approval_required | Approval required by policy |
policy_approved | Approval granted by handler |
policy_dry_run | Simulated policy decision (dry-run mode) |
verifier_rejected | Verifier rejected call, result, or error |
idempotency_replay | Prior result replayed |
concurrency_wait | Waiting for resource lock |
concurrency_rejected | Lock rejected or wait timed out |
State Adapters
By default, state is in-memory. To persist guardrail state across processes, provide adapters.JavaScript
tenantKey, so teams can scope keys when sharing adapter backends. Unit tests cover adapter contracts; validate your backend and lock semantics with integration tests before production use.
API Methods
RuntimeControls.create(config?)
Creates a new controls instance. All config is optional.
controls.run(context, fn)
Runs fn(runtime) through enabled control layers.
Context fields:
toolName(required)runKey,destination,actionargsidempotencyKey,resourceKeytimeoutMs(per-call override)signal(caller cancellation)
JavaScript
controls.wrap(params)
Creates a reusable guarded function for repeated tool calls.
JavaScript
JavaScript
controls.reset(runKey=None)
Resets budget counters for the selected run key.
JavaScript
Full Configuration Reference
| Config | Default | Description |
|---|---|---|
tenantKey | "default" | Namespace for all state keys |
timeoutMs | 60000 | Per-call timeout (0 disables) |
maxToolCalls | unset | Per-runKey call budget |
retry.maxAttempts | 4 | Total attempts including first |
retry.initialDelayMs | 250 | Backoff base delay |
retry.maxDelayMs | 10000 | Backoff cap |
retry.backoffFactor | 2 | Exponential multiplier |
retry.jitterRatio | 0.2 | Delay jitter (0..1) |
retryClassifier | unset | Custom retry decision callback |
loopBreaker.enabled | true | Enables repeated-pattern detection |
loopBreaker.warningThreshold | 5 | Emits loop_warning |
loopBreaker.quarantineThreshold | 8 | Emits loop_quarantine, blocks for quarantineMs |
loopBreaker.stopThreshold | 12 | Emits loop_stop, blocks for stopCooldownMs |
loopBreaker.quarantineMs | 15000 | Quarantine duration |
loopBreaker.stopCooldownMs | 120000 | Stop duration |
loopBreaker.maxFingerprints | 200 | Max fingerprints retained |
circuitBreaker.enabled | true | Enables dependency circuit protection |
circuitBreaker.windowMs | 30000 | Sliding failure window |
circuitBreaker.minRequests | 20 | Requests before open decision |
circuitBreaker.failureRateThreshold | 0.6 | Open when failure rate exceeds this |
circuitBreaker.cooldownMs | 60000 | Open duration |
policy.enabled | true | Enables rule enforcement |
policy.mode | "enforce" | "dryRun" emits simulation events only |
policy.rules | [] | Array of allow/deny/require_approval rules |
policy.approvalHandler | unset | Required for require_approval rules |
verifiers.beforeCall | unset | Pre-execution verifier |
verifiers.afterSuccess | unset | Post-success verifier |
verifiers.afterError | unset | Post-error verifier |
idempotency.enabled | true | Enables replay by idempotencyKey |
idempotency.ttlMs | unset | Replay record expiration |
idempotency.includeErrors | false | Replay final errors when enabled |
idempotency.namespaceByRunKey | true | Scope replay by run key |
concurrency.enabled | false | Enables locking by resourceKey |
concurrency.leaseMs | 30000 | Lock lease duration |
concurrency.waitMode | "reject" | "reject" immediately or "wait" |
concurrency.waitTimeoutMs | 5000 | Wait timeout |
concurrency.pollIntervalMs | 50 | Poll interval in wait mode |
overrides.tools | unset | Per-tool config overrides |
overrides.destinations | unset | Per-destination config overrides |
state.* | in-memory | State adapters (budget, circuit, loop, lock, idempotency) |
onEvent | unset | Synchronous event callback |
eventSinks | [] | Async event fan-out sinks |
onEventSinkFailure | unset | Sink failure callback |
FAQ
Do I need a Buildfunctions API token?
No. Runtime Controls works standalone without any API token.What should I wrap first?
Start with side-effecting calls:- Repository writes (git push, branch operations)
- External ticket or issue creation
- Sandbox and test execution
- PR or issue comments
What should be denied by default?
Start by denying destructive actions (delete*) and requiring approval for external writes.