---
title: "Runtime Controls - Docs"
label: "Runtime Controls"
description: "Composable guardrails for agent tool calls"
---

Runtime Controls wrap your functions with composable guardrails so agents can run autonomously without spiraling into infinite loops, duplicate writes, or runaway tool-call volume. No API key required — they work standalone around your own code, or combined with Buildfunctions hardware-isolated sandboxes.

> **Note**
>
> Runtime Controls is in beta. The SDK interface may change between releases.

## Install

```bash npm
npm install buildfunctions
```
```bash yarn
yarn add buildfunctions
```
```bash pnpm
pnpm add buildfunctions
```
```bash pip
pip install buildfunctions
```

## 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

Runtime Controls reduce these failure modes, but you still need to configure the right controls for your workflow. `maxToolCalls` is a call-count guardrail, not direct provider billing telemetry.

## Examples

### Wrap Any Async Call (No API Key)

```javascript JavaScript
import { RuntimeControls } from 'buildfunctions'

const controls = RuntimeControls.create({
  maxToolCalls: 50,
  timeoutMs: 30_000,
  retry: { maxAttempts: 3, initialDelayMs: 200, backoffFactor: 2 },
  loopBreaker: { warningThreshold: 5, quarantineThreshold: 8, stopThreshold: 12 },
  onEvent: (event) => console.log(`[controls] ${event.type}: ${event.message}`),
})

const guardedFetch = controls.wrap({
  toolName: 'api-call',
  runKey: 'agent-run-1',
  destination: 'https://api.example.com',
  run: async ([payload]) => {
    const res = await fetch('https://api.example.com/data', {
      method: 'POST',
      body: JSON.stringify(payload),
    })
    return res.json()
  },
})

const result = await guardedFetch({ query: 'latest results' })
```

```python Python
import httpx
from buildfunctions import RuntimeControls

controls = RuntimeControls.create({
    "maxToolCalls": 50,
    "timeoutMs": 30_000,
    "retry": {"maxAttempts": 3, "initialDelayMs": 200, "backoffFactor": 2},
    "loopBreaker": {"warningThreshold": 5, "quarantineThreshold": 8, "stopThreshold": 12},
    "onEvent": lambda event: print(f"[controls] {event['type']}: {event['message']}"),
})

async def run_api(args, runtime):
    _ = runtime
    payload = args[0]
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.example.com/data",
            json=payload,
            timeout=30,
        )
        response.raise_for_status()
        return response.json()

wrapped_fetch = controls.wrap({
    "toolName": "api-call",
    "runKey": "agent-run-1",
    "destination": "https://api.example.com",
    "run": run_api,
})

result = await wrapped_fetch({"query": "latest results"})
```

### Use `run()` Directly

```javascript JavaScript
import { RuntimeControls } from 'buildfunctions'

const controls = RuntimeControls.create({ retry: { maxAttempts: 2 } })

const result = await controls.run(
  { toolName: 'simple-tool' },
  async () => 'ok'
)
```

```python Python
from buildfunctions import RuntimeControls

controls = RuntimeControls.create({"retry": {"maxAttempts": 2}})

async def execute(runtime):
    return "ok"

result = await controls.run({"toolName": "simple-tool"}, execute)
```

`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
const controls = RuntimeControls.create({
  retry: {
    maxAttempts: 4,
    initialDelayMs: 250,
    maxDelayMs: 10_000,
    backoffFactor: 2,
    jitterRatio: 0.2,
  },
})
```

```python Python
controls = RuntimeControls.create({
    "retry": {
        "maxAttempts": 4,
        "initialDelayMs": 250,
        "maxDelayMs": 10_000,
        "backoffFactor": 2,
        "jitterRatio": 0.2,
    }
})
```

Custom retry decisions:

```javascript JavaScript
const controls = RuntimeControls.create({
  retry: { maxAttempts: 4 },
  retryClassifier: ({ error, statusCode }) => {
    if (statusCode === 409) {
      return { retryable: true, delayMs: 500, reason: 'conflict_backoff' }
    }
    return { retryable: error.code === 'NETWORK_ERROR' }
  },
})
```

```python Python
def retry_classifier(ctx):
    if ctx.get("statusCode") == 409:
        return {"retryable": True, "delayMs": 500, "reason": "conflict_backoff"}
    return {"retryable": ctx["error"]["code"] == "NETWORK_ERROR"}

controls = RuntimeControls.create({
    "retry": {"maxAttempts": 4},
    "retryClassifier": retry_classifier,
})
```

### Tool-Call Budget

Per-run cap on the number of tool calls when `maxToolCalls` is configured. Budgets are scoped by `runKey`.
This is a tool-call-count budget, not a dollar-based cost meter.

```javascript JavaScript
const controls = RuntimeControls.create({
  maxToolCalls: 50,
})

await controls.run({ toolName: 'shell', runKey: 'agent-run-1' }, async () => 'ok')

// Reset budget when starting a new run.
await controls.reset('agent-run-1')
```

```python Python
controls = RuntimeControls.create({"maxToolCalls": 50})

async def execute_step(runtime):
    _ = runtime
    return await do_work()

await controls.run({"toolName": "shell", "runKey": "agent-run-1"}, execute_step)
await controls.reset("agent-run-1")
```

### Loop Breaker

Detects repeated no-progress patterns and escalates from warning to quarantine to stop.

```javascript JavaScript
const controls = RuntimeControls.create({
  loopBreaker: {
    enabled: true,
    warningThreshold: 5,
    quarantineThreshold: 8,
    stopThreshold: 12,
    quarantineMs: 15_000,
    stopCooldownMs: 120_000,
  },
})
```

```python Python
controls = RuntimeControls.create({
    "loopBreaker": {
        "enabled": True,
        "warningThreshold": 5,
        "quarantineThreshold": 8,
        "stopThreshold": 12,
        "quarantineMs": 15_000,
        "stopCooldownMs": 120_000,
    }
})
```

### Circuit Breaker

Temporarily blocks calls to a failing dependency. Keyed by `(tenant, toolName, destination)`.

```javascript JavaScript
const controls = RuntimeControls.create({
  circuitBreaker: {
    enabled: true,
    windowMs: 30_000,
    minRequests: 20,
    failureRateThreshold: 0.6,
    cooldownMs: 60_000,
  },
})
```

```python Python
controls = RuntimeControls.create({
    "circuitBreaker": {
        "enabled": True,
        "windowMs": 30_000,
        "minRequests": 20,
        "failureRateThreshold": 0.6,
        "cooldownMs": 60_000,
    }
})
```

When failure rate exceeds threshold inside the sliding window, the circuit opens for `cooldownMs`.

### Timeout and Cancellation

Per-call timeout with abort signal propagation.

```javascript JavaScript
const controls = RuntimeControls.create({ timeoutMs: 30_000 })

const controller = new AbortController()
await controls.run(
  { toolName: 'shell', signal: controller.signal },
  async ({ signal }) => {
    return runCommand('npm test', { signal })
  }
)
```

```python Python
from buildfunctions import RuntimeControls, create_abort_controller

controls = RuntimeControls.create({"timeoutMs": 30_000})
controller = create_abort_controller()

async def execute(runtime):
    signal = runtime["signal"]
    return await run_command("npm test", signal=signal)

task = controls.run({"toolName": "shell", "signal": controller.signal}, execute)
# later if needed:
# controller.abort(Exception("cancelled"))
result = await task
```

### Policy Gates

Deterministic `allow` / `deny` / `require_approval` rules with specificity-based precedence.

```javascript JavaScript
const controls = RuntimeControls.create({
  policy: {
    mode: 'enforce',
    rules: [
      {
        id: 'deny-destructive',
        action: 'deny',
        tools: ['repo-admin'],
        actionPrefixes: ['delete'],
        reason: 'Destructive repo operations are blocked',
      },
      {
        id: 'approve-external-writes',
        action: 'require_approval',
        tools: ['ticket-write'],
        destinations: ['*.external.example.com'],
        reason: 'External writes require human approval',
      },
    ],
    approvalHandler: async () => false,
  },
})
```

```python Python
controls = RuntimeControls.create({
    "policy": {
        "mode": "enforce",  # or "dryRun"
        "rules": [
            {
                "id": "deny-destructive",
                "action": "deny",
                "tools": ["repo-admin"],
                "actionPrefixes": ["delete"],
                "reason": "Destructive repo operations are blocked",
            },
            {
                "id": "approve-external-writes",
                "action": "require_approval",
                "tools": ["ticket-write"],
                "destinations": ["*.external.example.com"],
                "reason": "External writes require human approval",
            },
        ],
        "approvalHandler": lambda ctx: False,
    }
})
```

**Policy precedence** (highest wins):
1. Higher tool specificity (exact name > prefix > wildcard)
2. Higher destination specificity (exact host > wildcard subdomain > `*`)
3. Longer action prefix match
4. Stricter action (`deny` > `require_approval` > `allow`)
5. Earlier rule index on exact tie (current behavior)

### Idempotency

Prevents duplicate side effects by replaying prior results for calls with the same `idempotencyKey`.

```javascript JavaScript
const controls = RuntimeControls.create({
  idempotency: {
    enabled: true,
    ttlMs: 600_000,
    includeErrors: false,
    namespaceByRunKey: true,
  },
})

await controls.run(
  {
    toolName: 'pr-comment',
    runKey: 'pr-4821',
    idempotencyKey: 'comment:pr-4821:summary',
  },
  async () => postComment('LGTM')
)

await controls.run(
  {
    toolName: 'pr-comment',
    runKey: 'pr-4821',
    idempotencyKey: 'comment:pr-4821:summary',
  },
  async () => postComment('LGTM') // replayed, does not execute again
)
```

```python Python
controls = RuntimeControls.create({
    "idempotency": {
        "enabled": True,
        "ttlMs": 600_000,
        "includeErrors": False,
        "namespaceByRunKey": True,
    }
})

context = {
    "toolName": "pr-comment",
    "runKey": "pr-4821",
    "idempotencyKey": "comment:pr-4821:summary",
}

async def post_summary(runtime):
    _ = runtime
    return await post_comment("LGTM")

await controls.run(context, post_summary)
await controls.run(context, post_summary)  # replayed, does not execute again
```

### Concurrency Locks

Prevents simultaneous conflicting writes to the same resource.

```javascript JavaScript
const controls = RuntimeControls.create({
  concurrency: {
    enabled: true,
    waitMode: 'reject', // or 'wait'
    leaseMs: 30_000,
    waitTimeoutMs: 5_000,
  },
})

await controls.run(
  {
    toolName: 'repo-write',
    resourceKey: 'repo:buildfunctions/web-app',
  },
  async () => gitPush('main')
)
```

```python Python
controls = RuntimeControls.create({
    "concurrency": {
        "enabled": True,
        "waitMode": "reject",  # or "wait"
        "leaseMs": 30_000,
        "waitTimeoutMs": 5_000,
        "pollIntervalMs": 50,
    }
})

async def push_main(runtime):
    _ = runtime
    return await git_push("main")

await controls.run(
    {"toolName": "repo-write", "resourceKey": "repo:buildfunctions/web-app"},
    push_main,
)
```

### Verifier Hooks

Custom correctness gates that run before execution, after success, and after errors.

```javascript JavaScript
const controls = RuntimeControls.create({
  verifiers: {
    beforeCall: ({ toolName, action }) => {
      if (toolName === 'repo-admin' && action?.startsWith('delete')) {
        return { allow: false, reason: 'delete blocked' }
      }
      return { allow: true }
    },
    afterSuccess: ({ result }) => {
      if (!result || typeof result !== 'object') {
        return { allow: false, reason: 'unexpected result shape' }
      }
      return { allow: true }
    },
    afterError: ({ error }) => {
      return { allow: true }
    },
  },
})
```

```python Python
def before_call(ctx):
    if ctx["toolName"] == "repo-admin" and str(ctx.get("action", "")).startswith("delete"):
        return {"allow": False, "reason": "delete blocked"}
    return {"allow": True}

def after_success(ctx):
    result = ctx.get("result")
    if not isinstance(result, dict):
        return {"allow": False, "reason": "unexpected result shape"}
    return {"allow": True}

controls = RuntimeControls.create({
    "verifiers": {
        "beforeCall": before_call,
        "afterSuccess": after_success,
    }
})
```

### Per-Tool and Per-Destination Overrides

Apply stricter or looser controls to specific tools or destinations.

```javascript JavaScript
const controls = RuntimeControls.create({
  timeoutMs: 60_000,
  retry: { maxAttempts: 4 },
  overrides: {
    tools: {
      shell: { timeoutMs: 10_000, retry: { maxAttempts: 2 } },
    },
    destinations: {
      '*.internal-api.example.com': { timeoutMs: 5_000 },
    },
  },
})
```

```python Python
controls = RuntimeControls.create({
    "timeoutMs": 60_000,
    "retry": {"maxAttempts": 4},
    "overrides": {
        "tools": {
            "shell": {"timeoutMs": 10_000, "retry": {"maxAttempts": 2}},
        },
        "destinations": {
            "*.internal-api.example.com": {"timeoutMs": 5_000},
        },
    },
})
```

Tool overrides win when both tool and destination set the same field.

## 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 JavaScript
import { RuntimeControls, applyAgentLogicSafety } from 'buildfunctions'

const controls = RuntimeControls.create(
  applyAgentLogicSafety(
    { retry: { maxAttempts: 2 } },
    {
      injectionGuard: {
        enabled: true,
        patterns: [
          /ignore\s+previous\s+instructions/i,
          /\brm\s+-rf\b/i,
          /<script\b/i,
        ],
      },
    }
  )
)
```

```python Python
import re
from buildfunctions import RuntimeControls, applyAgentLogicSafety

controls = RuntimeControls.create(
    applyAgentLogicSafety(
        {"retry": {"maxAttempts": 2}},
        {
            "injectionGuard": {
                "enabled": True,
                "patterns": [
                    re.compile(r"ignore\s+previous\s+instructions", re.I),
                    re.compile(r"\brm\s+-rf\b", re.I),
                    re.compile(r"<script\b", re.I),
                ],
            },
        },
    )
)
```

Default patterns (when `patterns` is omitted):
- `ignore (all|any|previous) instructions`
- `system prompt`
- `developer message`
- `<script`
- `rm -rf`

### Exit Conditions

Enforces maximum steps per run and can block calls after a terminal action.

```javascript JavaScript
const controls = RuntimeControls.create(
  applyAgentLogicSafety(
    {},
    {
      exitCondition: {
        enabled: true,
        maxStepsPerRun: 30,
        terminalActions: [{ toolNamePattern: 'agent-control', actionPrefix: 'finish' }],
        blockAfterTerminal: true,
      },
    }
  )
)
```

```python Python
from buildfunctions import RuntimeControls, applyAgentLogicSafety

controls = RuntimeControls.create(
    applyAgentLogicSafety(
        {},
        {
            "exitCondition": {
                "enabled": True,
                "maxStepsPerRun": 30,
                "terminalActions": [{"toolNamePattern": "agent-control", "actionPrefix": "finish"}],
                "blockAfterTerminal": True,
            },
        },
    )
)
```

### Intent Allowlist

Restricts which tool + action combinations are permitted. Anything not explicitly allowed is denied.

```javascript JavaScript
const controls = RuntimeControls.create(
  applyAgentLogicSafety(
    {},
    {
      intentAllowlist: {
        enabled: true,
        rules: [
          { toolNamePattern: 'cpu-sandbox', actionPrefixes: ['run_'] },
          { toolNamePattern: 'repo-write', actionPrefixes: ['push_'] },
          { toolNamePattern: 'pr-comment', actionPrefixes: ['post_'] },
        ],
      },
    }
  )
)
```

```python Python
from buildfunctions import RuntimeControls, applyAgentLogicSafety

controls = RuntimeControls.create(
    applyAgentLogicSafety(
        {},
        {
            "intentAllowlist": {
                "enabled": True,
                "rules": [
                    {"toolNamePattern": "cpu-sandbox", "actionPrefixes": ["run_"]},
                    {"toolNamePattern": "repo-write", "actionPrefixes": ["push_"]},
                    {"toolNamePattern": "pr-comment", "actionPrefixes": ["post_"]},
                ],
            },
        },
    )
)
```

### Combining All Three

```javascript JavaScript
const controls = RuntimeControls.create(
  applyAgentLogicSafety(
    {
      tenantKey: 'my-team-prod',
      maxToolCalls: 100,
      retry: { maxAttempts: 3 },
      concurrency: { enabled: true, waitMode: 'reject', leaseMs: 30_000 },
    },
    {
      injectionGuard: { enabled: true },
      exitCondition: {
        enabled: true,
        maxStepsPerRun: 50,
        terminalActions: [{ toolNamePattern: 'agent-control', actionPrefix: 'finish' }],
        blockAfterTerminal: true,
      },
      intentAllowlist: {
        enabled: true,
        rules: [
          { toolNamePattern: 'cpu-sandbox', actionPrefixes: ['run_'] },
          { toolNamePattern: 'repo-write', actionPrefixes: ['push_'] },
          { toolNamePattern: 'pr-comment', actionPrefixes: ['post_'] },
        ],
      },
    }
  )
)
```

```python Python
import re
from buildfunctions import RuntimeControls, applyAgentLogicSafety

controls = RuntimeControls.create(
    applyAgentLogicSafety(
        {
            "tenantKey": "my-team-prod",
            "maxToolCalls": 100,
            "retry": {"maxAttempts": 3},
            "concurrency": {"enabled": True, "waitMode": "reject", "leaseMs": 30_000},
        },
        {
            "injectionGuard": {
                "enabled": True,
                "patterns": [
                    re.compile(r"ignore\s+previous\s+instructions", re.I),
                    re.compile(r"\brm\s+-rf\b", re.I),
                ],
            },
            "exitCondition": {
                "enabled": True,
                "maxStepsPerRun": 50,
                "terminalActions": [{"toolNamePattern": "agent-control", "actionPrefix": "finish"}],
                "blockAfterTerminal": True,
            },
            "intentAllowlist": {
                "enabled": True,
                "rules": [
                    {"toolNamePattern": "cpu-sandbox", "actionPrefixes": ["run_"]},
                    {"toolNamePattern": "repo-write", "actionPrefixes": ["push_"]},
                    {"toolNamePattern": "pr-comment", "actionPrefixes": ["post_"]},
                ],
            },
        },
    )
)
```

## Observability

### Event Callback

```javascript JavaScript
const controls = RuntimeControls.create({
  onEvent: (event) => {
    console.log(`[${event.type}] ${event.message}`, event.details)
  },
})
```

```python Python
controls = RuntimeControls.create({
    "onEvent": lambda event: print(f"[{event['type']}] {event['message']} {event.get('details')}")
})
```

### Event Sinks

```javascript JavaScript
const controls = RuntimeControls.create({
  eventSinks: [
    async (event) => await writeToDatabase(event),
    async (event) => await sendToMonitoring(event),
  ],
  onEventSinkFailure: ({ failure, sinkIndex }) => {
    console.error(`Event sink ${sinkIndex} failed:`, failure)
  },
})
```

```python Python
async def sink_a(event):
    await write_to_database(event)

async def sink_b(event):
    await send_to_monitoring(event)

controls = RuntimeControls.create({
    "eventSinks": [sink_a, sink_b],
    "onEventSinkFailure": lambda params: print(f"sink {params['sinkIndex']} failed: {params['failure']}"),
})
```

### 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 JavaScript
const stateAdapter = {
  get: async (key) => await store.get(key),
  set: async (key, value) => await store.set(key, value),
  delete: async (key) => await store.delete(key),
  keys: async (prefix) => await store.keys(prefix),
}

const controls = RuntimeControls.create({
  state: {
    budget: stateAdapter,
    circuit: stateAdapter,
    loop: stateAdapter,
    lock: stateAdapter,
    idempotency: stateAdapter,
  },
})
```

```python Python
async def adapter_get(key: str):
    return await store.get(key)

async def adapter_set(key: str, value):
    await store.set(key, value)

async def adapter_delete(key: str):
    await store.delete(key)

async def adapter_keys(prefix: str):
    return await store.keys(prefix)

adapter = {
    "get": adapter_get,
    "set": adapter_set,
    "delete": adapter_delete,
    "keys": adapter_keys,
}

controls = RuntimeControls.create({
    "state": {
        "budget": adapter,
        "circuit": adapter,
        "loop": adapter,
        "lock": adapter,
        "idempotency": adapter,
    }
})
```

State is namespaced by `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`, `action`
- `args`
- `idempotencyKey`, `resourceKey`
- `timeoutMs` (per-call override)
- `signal` (caller cancellation)

```javascript JavaScript
const result = await controls.run(
  {
    toolName: 'shell',
    runKey: 'agent-run-42',
    destination: 'sandbox.example.com',
    action: 'run_tests',
    args: { command: 'npm test' },
    idempotencyKey: 'test:42:v1',
    resourceKey: 'workspace:/repo',
    signal: controller.signal,
    timeoutMs: 10_000,
  },
  async ({ signal }) => {
    return runShell('npm test', { signal })
  }
)
```

```python Python
async def execute(runtime):
    signal = runtime["signal"]
    return await run_shell("npm test", signal=signal)

result = await controls.run(
    {
        "toolName": "shell",
        "runKey": "agent-run-42",
        "destination": "sandbox.example.com",
        "action": "run_tests",
        "args": {"command": "npm test"},
        "idempotencyKey": "test:42:v1",
        "resourceKey": "workspace:/repo",
        "signal": controller.signal,
        "timeoutMs": 10_000,
    },
    execute,
)
```

### `controls.wrap(params)`

Creates a reusable guarded function for repeated tool calls.

```javascript JavaScript
const guardedShell = controls.wrap({
  toolName: 'shell',
  runKey: 'agent-run-42',
  destination: 'sandbox.example.com',
  run: async ([command], { signal }) => {
    return runShell(command, { signal })
  },
})

await guardedShell('npm test')
await guardedShell('npm run lint')
```

```python Python
async def run_shell_guarded(args, runtime):
    command = args[0]
    signal = runtime["signal"]
    return await run_shell(command, signal=signal)

guarded_shell = controls.wrap({
    "toolName": "shell",
    "runKey": "agent-run-42",
    "destination": "sandbox.example.com",
    "run": run_shell_guarded,
})

await guarded_shell("npm test")
await guarded_shell("npm run lint")
```

Dynamic context via resolver functions:

```javascript JavaScript
const guardedShell = controls.wrap({
  toolName: 'shell',
  run: async ([command], { signal }) => runShell(command, { signal }),
  resolveRunKey: (command) => `run-${command}`,
  resolveDestination: () => 'sandbox.example.com',
  resolveAction: (command) => (command.startsWith('rm') ? 'delete' : 'execute'),
  resolveIdempotencyKey: (command) => `shell:${command}`,
  resolveResourceKey: () => 'workspace:/repo',
})
```

```python Python
async def run_shell_dynamic(args, runtime):
    command = args[0]
    signal = runtime["signal"]
    return await run_shell(command, signal=signal)

guarded_shell = controls.wrap({
    "toolName": "shell",
    "run": run_shell_dynamic,
    "resolveRunKey": lambda command: f"run-{command}",
    "resolveDestination": lambda command: "sandbox.example.com",
    "resolveAction": lambda command: "delete" if str(command).startswith("rm") else "execute",
    "resolveIdempotencyKey": lambda command: f"shell:{command}",
    "resolveResourceKey": lambda command: "workspace:/repo",
})
```

### `controls.reset(runKey=None)`

Resets budget counters for the selected run key.

```javascript JavaScript
await controls.reset('agent-run-42')
```

```python Python
await controls.reset("agent-run-42")
```

## 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:
1. Repository writes (git push, branch operations)
2. External ticket or issue creation
3. Sandbox and test execution
4. PR or issue comments

### What should be denied by default?

Start by denying destructive actions (`delete*`) and requiring approval for external writes.

### Can I use this without agents?

Yes. Runtime Controls wrap functions — API calls, database queries, file operations, shell commands. Validate behavior in your own integration paths.
