live · mainnetoc · docs
specs · api · guides
docs / integrations

Fleet integrations

Five framework adapters and one shared client. Pick the one that matches your stack; install it; wire three lines of config; every LLM tool call becomes a BIP-322-signed action receipt that flows into your fleet project automatically.

The packages

PackageWrapsnpm
@orangecheck/agent-anthropicAnthropic Messages API tool_use blocks0.1.0+
@orangecheck/agent-openaiOpenAI Chat Completions function-call / Responses tool_call0.1.0+
@orangecheck/agent-vercelVercel AI SDK tool() definitions0.1.0+
@orangecheck/agent-langgraphLangGraph tool-node executes0.1.0+
@orangecheck/agent-mcpModel Context Protocol invocations0.1.0+
@orangecheck/agent-fleet-clientShared HTTP client (used by every adapter; install directly if you're rolling your own)0.1.0+
@orangecheck/webhook-verifyDrop-in HMAC verifier for receiving fleet webhooks0.1.0+

Every framework adapter exposes the same shape:

const { result, action, posted } = await invokeWithStampAndPost({
    agent: agentSigner,
    delegation: signedDelegation,
    /* framework-specific tool-call payload */
    call: (normalizedCall) => myExecute(normalizedCall),
    fleet: {
        apiToken: process.env.OC_TOKEN!,
        projectId: process.env.OC_PROJECT_ID!,
    },
});

If fleet is omitted the adapter still stamps + executes; posted just comes back null. The post is fire-and-forget after the call, so a flaky fleet never blocks a tool from running.

Anthropic (Claude tool_use)

import { invokeWithStampAndPost } from '@orangecheck/agent-anthropic';

const message = await anthropic.messages.create({
    /* … */
});
for (const block of message.content) {
    if (block.type !== 'tool_use') continue;
    const { result, action, posted } = await invokeWithStampAndPost({
        agent: agentSigner,
        delegation: signedDelegation,
        toolUse: block,
        call: (toolUse) => myToolImpl(toolUse.input),
        fleet: { apiToken: OC_TOKEN, projectId: OC_PROJECT_ID },
    });
    // feed `result` back to Claude as a tool_result content block.
    // `action` + `posted` are your audit trail.
}

Scope check: scopeExercised defaults to anthropic:tool(name=<tool>) — the tightest admissible sub-scope. Override via the scopeExercised field if your delegation grants a broader verb.

OpenAI (function calling / Responses)

import { invokeWithStampAndPost } from '@orangecheck/agent-openai';

const completion = await openai.chat.completions.create({
    /* … */
});
const fc = completion.choices[0].message.function_call;
if (fc) {
    const { result, action } = await invokeWithStampAndPost({
        agent: agentSigner,
        delegation: signedDelegation,
        call: fc,
        execute: (parsed) => myFn(parsed.args),
        fleet: { apiToken: OC_TOKEN, projectId: OC_PROJECT_ID },
    });
}

Vercel AI SDK

import { ocTool } from '@orangecheck/agent-vercel';
import { tool } from 'ai';

const createInvoice = ocTool({
    verb: 'invoice.create',
    parameters: invoiceSchema,
    execute: (args) => myInvoiceCreate(args),
});

const tools = {
    'invoice.create': tool({
        parameters: invoiceSchema,
        execute: async (args, { toolCallId }) => {
            const { result } = await createInvoice.execute(args, {
                agent: agentSigner,
                delegation: signedDelegation,
                callId: toolCallId,
                fleet: { apiToken: OC_TOKEN, projectId: OC_PROJECT_ID },
            });
            return result;
        },
    }),
};

LangGraph

import { ocToolNode } from '@orangecheck/agent-langgraph';

const createInvoice = ocToolNode({
    verb: 'invoice.create',
    execute: (args) => myInvoiceCreate(args),
});

const graph = new StateGraph(MyState).addNode(
    'createInvoice',
    async (state) => {
        const { result } = await createInvoice.execute(pickArgs(state), {
            agent: agentSigner,
            delegation: signedDelegation,
            callId: state.lastToolCallId,
            graphState: state,
            fleet: { apiToken: OC_TOKEN, projectId: OC_PROJECT_ID },
        });
        return mergeIntoState(state, result);
    }
);

The graphStateHash (deterministic over the supplied state object) is embedded in the action canonical message so a verifier replaying the graph against a snapshot gets byte-identical receipts.

MCP (Model Context Protocol)

import { invokeWithStampAndPost } from '@orangecheck/agent-mcp';

const { result, action } = await invokeWithStampAndPost({
    agent: agentSigner,
    delegation: signedDelegation,
    invocation: { server, tool, params },
    call: (inv) => mcpClient.callTool(inv),
    fleet: { apiToken: OC_TOKEN, projectId: OC_PROJECT_ID },
});

The stamp commits to (server, tool, params) before the call runs, so a hung or failing MCP server still leaves an on-record commitment that the agent attempted exactly this invocation.

Receiving the webhooks

When your fleet project has webhook endpoints subscribed, every accepted envelope fires a signed POST. Use the drop-in verifier:

import { verify } from '@orangecheck/webhook-verify';
import express from 'express';

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/orangecheck', (req, res) => {
    const ok = verify({
        secret: process.env.OC_WEBHOOK_SECRET!, // shown ONCE at create
        signature: req.header('X-OrangeCheck-Signature') ?? '',
        rawBody: req.body,
    });
    if (!ok) return res.status(401).send('bad signature');

    const event = JSON.parse(req.body.toString('utf8'));
    // handle event.event_type, event.id, event.envelope
    res.status(200).send('ok');
});

See Webhooks for the full event catalog and the delivery / retry semantics.

Direct API (no adapter)

If your stack isn't on the list above, install the shared client and post envelopes directly:

import { postActionToFleet } from '@orangecheck/agent-fleet-client';

await postActionToFleet(stampedAction, {
    apiToken: process.env.OC_TOKEN!,
    projectId: process.env.OC_PROJECT_ID!,
});

The client also exposes postDelegationToFleet, postRevocationToFleet, and postSubdelegationToFleet if you prefer to drive the registry yourself instead of going through the dashboard.

Authentication

All adapter posts authenticate via Bearer token:

Authorization: Bearer ock_<64-hex>

Tokens are minted at /settings § 03 · API tokens. The plaintext is returned once at create — store it in your CI's secrets manager immediately. The token authenticates as the project owner; revoke at any time from the same surface.