ChainPassChainPass/Docs
HomeDashboardGitHub

LoopGuard Documentation

LoopGuard is a runtime guardrail SDK for autonomous AI agents. It wraps your LLM tool calls and enforces limits locally — no network overhead on the happy path.

Installation

npm install @loopguard/sdk

Also available via pnpm or yarn:

pnpm add @loopguard/sdk
yarn add @loopguard/sdk

Getting started

Four steps to go from zero to a guarded agent connected to the dashboard.

Step 1Get an API key

Go to chainpass.app/dashboard — use the "Generate a new key" tab to create one in your browser, or run this curl command. The key is shown once, so save it.

curl -X POST https://chainpass.app/api/lg/keys \
  -H 'Content-Type: application/json' \
  -d '{"userId": "your@email.com"}'

# { "apiKey": "lg_abc123..." }
Step 2Install the SDK in your agent project

In a terminal inside your own project folder (not the LoopGuard repo), run one of:

npm install @loopguard/sdk
# or: pnpm add @loopguard/sdk  |  yarn add @loopguard/sdk
Step 3Wrap one tool in your existing agent

You don't need a new file — add these lines to your existing agent. Replace fetchData with the actual async function your agent calls:

import { createGuard, LoopGuardError } from "@loopguard/sdk";

const guard = createGuard({
  agentId: "my-agent",                    // shown in the dashboard
  apiKey:  process.env.LOOPGUARD_API_KEY,
  loopDepthLimit: 50,                     // stop after 50 tool calls
  tokenBudget:    100_000,                // stop after 100k tokens
});

// Replace fetchData with your actual tool function
const safeFetch = guard.wrapTool(fetchData, { name: "fetch_data" });

// Use safeFetch exactly like fetchData — same signature
const result = await safeFetch(myArgs);
Step 4Run your agent

Set the env var and run as normal. When a limit trips, your agent appears in the dashboard:

LOOPGUARD_API_KEY=lg_abc123... node your-agent.js

Note: Agents appear in the dashboard only when a limit is tripped — not on every tool call. Before integrating the SDK, use the Send test event button on the Agents page to verify your key is working — no code required.

Quick start

Minimal working example with dashboard connection:

import { createGuard, LoopGuardError } from "@loopguard/sdk";

const guard = createGuard({
  agentId:        "doc-processor",
  apiKey:         process.env.LOOPGUARD_API_KEY,  // connects to dashboard
  loopDepthLimit: 50,       // stop after 50 tool calls
  tokenBudget:    100_000,  // stop after 100k tokens
  perToolCaps:    { web_search: 20 },
});

const search = guard.wrapTool(webSearch, { name: "web_search" });

try {
  await runAgentLoop({ search });
} catch (err) {
  if (err instanceof LoopGuardError) {
    console.log("Stopped:", err.tripReason, err.currentValue, "/", err.limit);
  }
}

Configuration

All options passed to createGuard(config):

agentIdstringrequired

Namespace for this agent. Used for kill flags and policy lookups.

sessionIdstring?

Identifies a single run. Auto-generated (UUID) if omitted.

tokenBudgetnumber?

Max cumulative tokens (prompt + completion) per session.

loopDepthLimitnumber?

Max total tool calls per session across all tools.

perToolCapsRecord<string,number>?

Per-tool call limits. e.g. { web_search: 20 }

alertWebhookstring?

Slack or Discord webhook URL. Fires on every trip.

onTrip(event) => void?

Sync callback fired before the error is thrown.

apiKeystring?

Enables control plane: remote kill switch + event ingestion.

controlPlaneUrlstring?

Defaults to https://chainpass.app. Override for self-hosted.

wrapTool

Wraps any async function. Before each call, increments hop count and checks all limits. After the call, extracts token usage from the return value if present.

const safeFetch = guard.wrapTool(fetchData, { name: "fetch_data" });

// safeFetch has the same signature as fetchData
const result = await safeFetch(url);

If a limit is tripped, wrapTool throws a LoopGuardError before calling the underlying function.

wrapClient

Wraps an OpenAI or Anthropic client. Intercepts .chat.completions.create() and .messages.create() automatically.

import OpenAI from "openai";

const raw = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const client = guard.wrapClient(raw);

// Use client exactly like the original
const response = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
});

Kill switch

Call guard.kill() to immediately stop all future tool calls for this agent. The killed flag is checked before every wrapped call.

// Kill the current session
guard.kill();

// Kill all sessions for this agent
guard.kill("all");

With an apiKey set, the remote kill switch is also checked before each call via GET /api/lg/check (200ms timeout, fails open). Flip it from the dashboard without redeploying.

Alerts

Pass a Slack or Discord webhook URL to get notified instantly when a limit trips:

const guard = createGuard({
  agentId: "my-agent",
  loopDepthLimit: 50,
  alertWebhook: process.env.SLACK_WEBHOOK_URL,
});

Webhook failures are swallowed silently — alerts never block agent execution. The payload is Slack-compatible JSON with an emoji-prefixed text field.

Control plane

Add your API key to enable cloud features: remote kill switch, event ingestion, and replay logs (Pro).

const guard = createGuard({
  agentId: "my-agent",
  apiKey:  process.env.LOOPGUARD_API_KEY,
  loopDepthLimit: 50,
});

Generate an API key:

curl -X POST https://chainpass.app/api/lg/keys \
  -H "Content-Type: application/json" \
  -d '{"userId": "your@email.com"}'

With an API key set, the SDK calls GET /api/lg/check before each tool execution and POST /api/lg/event on every trip. Both calls have a hard 200ms timeout and fail open — your agent is never blocked by a network issue.

Vercel AI SDK

Two helpers for users of the ai package. Install it in your project first:

npm install ai @ai-sdk/openai

guardTools(tools, guard)

Wraps every tool's execute function with the guard. Drop-in replacement for the tools record passed to generateText or streamText. Enforces loopDepthLimit, perToolCaps, and the kill flag.

import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import { createGuard, guardTools, LoopGuardError } from "@loopguard/sdk";

const guard = createGuard({
  agentId:        "my-agent",
  apiKey:         process.env.LOOPGUARD_API_KEY,
  loopDepthLimit: 50,
  tokenBudget:    100_000,
});

const tools = guardTools({
  searchWeb: tool({
    description: "Search the web",
    parameters: z.object({ query: z.string() }),
    execute: async ({ query }) => fetchSearchResults(query),
  }),
  fetchPage: tool({
    description: "Fetch a web page",
    parameters: z.object({ url: z.string() }),
    execute: async ({ url }) => fetchPage(url),
  }),
}, guard);

try {
  const { text } = await generateText({
    model: openai("gpt-4o"),
    tools,
    prompt: "Research the latest AI news and summarize it.",
  });
} catch (err) {
  if (err instanceof LoopGuardError) {
    console.log("Stopped:", err.tripReason, err.currentValue, "/", err.limit);
  }
}

loopguardMiddleware(guard)

A LanguageModelV1Middleware that intercepts every generateText / streamTextcall. Checks the kill flag before each call and feeds token usage from the response into the guard's tokenBudget. Use alongside guardTools for full coverage.

import { wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { loopguardMiddleware } from "@loopguard/sdk";

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: loopguardMiddleware(guard),
});

// Use model exactly like the original — token usage is tracked automatically
const { text } = await generateText({ model, tools, prompt });

API reference

Control plane endpoints. All require Authorization: Bearer lg_....

POST/api/lg/keysno auth

Generate an API key. Body: { userId }. Returns key once — store it.

POST/api/lg/policy

Set agent policy. Body: { agentId, tokenBudget?, loopDepthLimit?, perToolCaps? }.

GET/api/lg/policy?agentId=...

Fetch current policy for an agent.

GET/api/lg/check?agentId=...&sessionId=...

Check remote kill flag. Always returns 200. Used by SDK.

POST/api/lg/event

Ingest a TripEvent. Body: TripEvent JSON. Returns 202.

POST/api/lg/kill

Set kill flag for an agent. Body: { agentId }. Expires in 24h.

GET/api/lg/agents

List all agents with policy, kill status, and last event.

GET/api/lg/replay/:sessionId

Replay full event log for a session. Pro plan required.