npm package: @limits/openclaw
Description: Delegates policy enforcement to the Limits platform before and after every tool call.
Author: Limits
Latest version: 0.0.11

# @limits/openclaw

[![npm version](https://img.shields.io/npm/v/@limits/openclaw.svg?style=flat-square)](https://www.npmjs.com/package/@limits/openclaw)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Node.js](https://img.shields.io/badge/Node.js-18%2B-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
[![GitHub](https://img.shields.io/badge/GitHub-limitsdev%2Flimits--openclaw-181717?style=flat-square&logo=github)](https://github.com/limitsdev/@limits/openclaw)

**Official** OpenClaw plugin for the [Limits](https://limits.dev) platform. Delegates policy enforcement via HTTP. Every decision is made by calling `POST {baseUrl}/openclaw/enforce` before and after each tool call. Optional policy-generator tools let the agent create and update policies from natural language.

---

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [How it works](#how-it-works)
- [Policy-generator tools](#policy-generator-tools)
- [Configuration](#configuration)
- [Policy tag convention](#policy-tag-convention-limits-backend)
- [Enforcement API contract](#limits-api-contract)
- [CLI reference](#cli-commands)
- [Security](#security-openclaw--limits)
- [Development](#development)
- [Migration from limits-enforcer](#migration-from-limits-enforcer)
- [Troubleshooting](#troubleshooting)
- [License](#license)

---

## Installation

```bash
npm install @limits/openclaw
```

```bash
yarn add @limits/openclaw
```

```bash
pnpm add @limits/openclaw
```

Then register the plugin with OpenClaw:

```bash
openclaw plugins install ./node_modules/@limits/openclaw
openclaw gateway restart
openclaw plugins enable "@limits/openclaw"
```

---

## Quick Start

1. **Install** the plugin (see [Installation](#installation)).
2. **Configure** your API token (required for enforcement and policy-generator tools):

```bash
openclaw limits configure
```

If you see **`error: unknown command 'limits'`**, the OpenClaw CLI may not load plugin commands in your environment. Run the configure script directly (from a directory where the package is installed):

```bash
node node_modules/@limits/openclaw/scripts/configure.js
```

Or set config manually:

```json
{
  "plugins": {
    "entries": {
      "@limits/openclaw": {
        "enabled": true,
        "config": {
          "apiToken": "sk_your_organization_api_key"
        }
      }
    }
  }
}
```

3. **Allow the plugin** in your agent's tool list so the agent can use policy-generator tools (optional). Edit `~/.openclaw/openclaw.json`:

```json
{
  "agents": {
    "list": [
      {
        "id": "main",
        "tools": { "allow": ["@limits/openclaw"] }
      }
    ]
  }
}
```

4. **Run your agent.** Before and after each tool call, the plugin calls the Limits backend. No code changes required.

---

## How it works

```mermaid
flowchart LR
  subgraph pre [Before tool call]
    A[Tool invoked] --> B["POST /openclaw/enforce phase=pre"]
    B --> C{Action?}
    C -->|ALLOW| D[Run tool]
    C -->|BLOCK| E[Block call]
    C -->|REWRITE| F[Replace args, then run]
  end
  subgraph post [After tool call]
    D --> G["POST /openclaw/enforce phase=post"]
    G --> H{Action?}
    H -->|ALLOW| I[Return result]
    H -->|BLOCK| J[Safe blocked message]
    H -->|REDACT| K[Redacted result]
    H -->|REWRITE| L[Rewritten result]
  end
```

1. **Before each tool call** (`before_tool_call`): the plugin POSTs `phase: "pre"` with tool name and args. Limits returns `ALLOW`, `BLOCK`, or `REWRITE` (with `rewriteArgs`). The plugin blocks the call, allows it, or replaces the tool arguments accordingly.
2. **After each tool call** (`after_tool_call`): the plugin POSTs `phase: "post"` with tool name, args, and result. Limits returns `ALLOW`, `BLOCK`, `REDACT`, or `REWRITE` (with `redactedResult` or `rewrittenResult`). The plugin leaves the result unchanged, replaces it with a safe blocked response, or replaces it with the redacted/rewritten value.

**Compatibility:** This plugin requires an OpenClaw build where tool hooks are wired into the execution path. If you see **`[@limits/openclaw] before_tool_call observed`** in logs once after a tool runs, hooks are working. See [OpenClaw #6535](https://github.com/openclaw/openclaw/issues/6535) if hooks never fire.

---

## Policy-generator tools

When `apiToken` is set, the plugin registers two optional tools and loads the **limits-policy-generator** skill:

| Tool | Description |
|------|-------------|
| **`limits_generate_create_policy`** | Generate a new policy from natural language and create it on the Limits backend (`POST /api/policies/generatecreate`). |
| **`limits_generate_update_policy`** | Generate updates from natural language and apply to an existing policy (`POST /api/policies/:id/generateupdate`). |

The agent can then fulfill requests like “create a policy that blocks payment tools” or “update my policy to also block stripe_*” by calling these tools.

### Add the skill to the workspace

When you run `openclaw limits configure` (or `npm run configure`), you can choose to add the limits-policy-generator skill to your OpenClaw workspace when prompted.

Optionally, you can copy it manually. The example below is for Unix; on Windows use the wizard or PowerShell equivalents (e.g. `New-Item -ItemType Directory -Force` and `Copy-Item -Recurse`).

```bash
mkdir -p ~/.openclaw/workspace/skills/limits-policy-generator
cp -r ./node_modules/@limits/openclaw/skills/limits-policy-generator/. ~/.openclaw/workspace/skills/limits-policy-generator/
```

### Expose tools to the agent

The plugin registers these tools as **optional**. Add the plugin to the agent's tool allowlist (see [Quick Start](#quick-start)) so the agent can call them. Use `"@limits/openclaw"` to allow all tools from this plugin, or allow by name: `["limits_generate_create_policy", "limits_generate_update_policy"]`.

### Sandboxed agents

If the agent runs in a **sandbox**, add `"@limits/openclaw"` (or the tool names) to `tools.sandbox.tools.allow` as well so the sandboxed agent can call the policy tools. Otherwise the agent can have the skill but cannot invoke the tools from inside the sandbox.

---

## Configuration

| Key | Required | Default | Description |
|-----|----------|---------|-------------|
| `baseUrl` | — | *Fixed in plugin* | Base URL for `POST /openclaw/enforce` and policy-generator API calls. Optional in config for compatibility. |
| `apiToken` | No | — | Organization API key for `/openclaw/enforce` and policy-generator tools. Fallback when event/context don't provide a token. Store securely or use `LIMITS_ENFORCER_API_TOKEN` env. |
| `timeoutMs` | No | `2500` | Request timeout in milliseconds. |
| `failMode` | No | `"allow"` | When Limits is unreachable or errors: **Pre:** `"allow"` → let the call proceed; `"block"` → block the call. **Post:** `"allow"` → keep original result; `"block"` → replace result with a safe blocked message. |
| `tokenSource` | No | `"event.metadata.apiToken"` | Dot-separated path to the API token. The plugin uses this first; if missing, it falls back to `apiToken`. |
| `redactLogs` | No | `true` | Whether to avoid logging sensitive data (token/args are never logged). |

### Environment overrides

| Variable | Maps to |
|----------|---------|
| `LIMITS_ENFORCER_TIMEOUT_MS` | `timeoutMs` |
| `LIMITS_ENFORCER_FAIL_MODE` | `allow` or `block` |
| `LIMITS_ENFORCER_TOKEN_SOURCE` | token path |
| `LIMITS_ENFORCER_REDACT_LOGS` | `true` / `1` for true |
| `LIMITS_ENFORCER_API_TOKEN` | `apiToken` |

### Gateway config example

```json
{
  "plugins": {
    "entries": {
      "@limits/openclaw": {
        "enabled": true,
        "config": {
          "apiToken": "sk_your_organization_api_key",
          "timeoutMs": 2500,
          "failMode": "allow",
          "tokenSource": "event.metadata.apiToken"
        }
      }
    }
  }
}
```

---

## Policy tag convention (Limits backend)

Policies are scoped to OpenClaw tool calls using **tags** in the Limits dashboard:

| Tag | Meaning |
|-----|---------|
| `openclaw:phase:pre` | Apply only in the **pre** phase (before the tool runs). |
| `openclaw:phase:post` | Apply only in the **post** phase (after the tool runs, e.g. guardrails on output). |
| *(no openclaw:phase tag)* | Apply to **both** phases. |
| `openclaw:tool:stripe.charge` | Apply only to the tool named `stripe.charge`. |
| `openclaw:tool:stripe.*` | Apply to all tools whose name **starts with** `stripe.`. |
| *(no openclaw:tool tag)* | Apply to **all** tools. |
| `openclaw:scope:all` | Explicit scope: policy applies to all tools (required when `OPENCLAW_REQUIRE_SCOPE_TAGS` is set). |
| `openclaw:scope:tools` | Explicit scope: policy applies to specific tools (required when scope tags are enforced). |

**Example:** To block Stripe tool calls with amount > 200, create a **CONDITIONS** policy like `input.args.amount > 200 → BLOCK`, and tag it with `openclaw:phase:pre` and `openclaw:tool:stripe.*`.

---

## Limits API contract

**Endpoint:** `POST {baseUrl}/openclaw/enforce`

**Request body (JSON)** — same shape for pre and post:

| Field | Pre | Post | Description |
|-------|-----|------|-------------|
| `phase` | ✓ | ✓ | `"pre"` or `"post"`. |
| `apiToken` | ✓ | ✓ | String from `tokenSource` (identifies the org on Limits). |
| `tool` | ✓ | ✓ | Object: `name`, `args`, optional `toolCallId`. For **post** only, `tool` also includes `result`. |
| `context` | ✓ | ✓ | Optional: `requestId`, `runId`, `sessionKey`, `agentId`, `channel`, `userMessageSummary`. |

**Response (JSON):**

- **Pre:** `{ "action": "ALLOW" }` | `{ "action": "BLOCK", "reason": "..." }` | `{ "action": "REWRITE", "rewriteArgs": { ... } }`
- **Post:** `{ "action": "ALLOW" }` | `{ "action": "BLOCK", "reason": "..." }` | `{ "action": "REDACT", "redactedResult": ... }` | `{ "action": "REWRITE", "rewrittenResult": ... }`

**Decision precedence:** Deny wins — any `BLOCK` overrides `ALLOW`. Document your own precedence for REWRITE/REDACT if you combine multiple policies.

---

## CLI commands

| Command | Description |
|---------|-------------|
| `openclaw plugins install <path>` | Install plugin from path. |
| `openclaw plugins install -l <path>` | Link plugin (no copy, for development). |
| `openclaw plugins enable "@limits/openclaw"` | Enable the plugin. |
| `openclaw plugins list` | List installed plugins. |
| `openclaw plugins doctor` | Check plugin health. |
| `openclaw limits configure` | Interactive wizard to set API token and sandbox allowlist. |
| `openclaw config get 'plugins.entries["@limits/openclaw"]'` | Show plugin config. |
| `openclaw config set 'plugins.entries["@limits/openclaw"].config.apiToken' "sk_..."` | Set apiToken manually. |

---

## Security (OpenClaw ↔ Limits)

- Use **HTTPS** in production.
- For higher assurance (e.g. internal networks), consider **mTLS** or a **shared HMAC** header in addition to `apiToken`.
- Apply **rate limiting** and auth on the Limits endpoint.

---

## Development

- **Tests:** `npm test` (Vitest). Unit tests mock the enforcer; the integration test runs a real HTTP server and exercises the full pre/post flow.
- **Retries:** The enforcer client retries up to 2 times on HTTP 429 or 5xx with exponential backoff (200 ms, 400 ms). Non-retryable errors trigger `failMode` immediately.

---

## Migration from limits-enforcer

If you were using the plugin under the old name **limits-enforcer**, update your config and CLI usage:

| Before | After |
|--------|-------|
| Plugin ID / allowlist: `"limits-enforcer"` | `"@limits/openclaw"` |
| Config path: `plugins.entries.limits-enforcer` | `plugins.entries["@limits/openclaw"]` |
| CLI command: `openclaw limits-enforcer configure` | `openclaw limits configure` |
| Log prefix: `[limits-enforcer]` | `[@limits/openclaw]` |

After renaming, reinstall the plugin and enable `"@limits/openclaw"`.

---

## Troubleshooting

- **Config warning "plugin id mismatch (manifest uses '@limits/openclaw', entry hints 'openclaw')"**  
  Some OpenClaw gateways install scoped packages under `extensions/openclaw` instead of `extensions/@limits/openclaw`. The gateway then infers the plugin id from the folder name (`openclaw`), which does not match the manifest id `@limits/openclaw`, so the plugin may not load and `openclaw limits configure` shows **unknown command 'limits'**.

  **Workaround — install under the scoped path so the gateway matches the config entry:**
  ```bash
  # Create the scope directory and install the package there (adjust if you use npm -g or another path)
  mkdir -p ~/.openclaw/extensions/@limits
  cp -r ./node_modules/@limits/openclaw ~/.openclaw/extensions/@limits/

  # Tell OpenClaw to load the plugin from that path
  openclaw plugins install ~/.openclaw/extensions/@limits/openclaw
  openclaw plugins enable "@limits/openclaw"
  openclaw gateway restart
  ```
  Then run `openclaw limits configure`.

- **`error: unknown command 'limits'`**  
  The plugin did not load (often due to the id mismatch above). Use the workaround above, or run the configure wizard without the OpenClaw CLI:
  ```bash
  node node_modules/@limits/openclaw/scripts/configure.js
  ```
  Or from the plugin repo: `npm run configure`.


---

## License

MIT — [Limits](https://limits.dev) — [GitHub](https://github.com/limitsdev/@limits/openclaw)