Skip to main content

Add OpenBox to CopilotKit

Use this guide when you already have a CopilotKit Runtime v2 route and want OpenBox to observe and govern the CopilotKit boundary. The new SDK package is @openbox-ai/openbox-copilotkit.

The SDK is independent from backend-framework SDKs. If your CopilotKit app delegates to Mastra, LangGraph, or another AG-UI agent, keep that backend in place. OpenBox wraps the CopilotKit runtime route, then optionally coordinates with child agent streams through multi-agent handoff metadata.

What changed

Do not use the older openbox-sdk/copilotkit imports for this path. The standalone CopilotKit SDK uses withOpenBoxRuntime() and targets CopilotKit Runtime v2.

Prerequisites

  • a server-side CopilotKit Runtime v2 route
  • @copilotkit/runtime and @ag-ui/client
  • Node.js >=24.10.0
  • OpenBox Core credentials and, when signing is enabled, OpenBox agent DID identity values

Step 1: Register And Configure The OpenBox Agent

Before changing CopilotKit code, prepare the OpenBox agent that will receive this app's governance events:

  1. Register or open an OpenBox agent.
  2. Generate an agent runtime key.
  3. Copy the generated DID and private key unless Require signing is disabled.
  4. Configure OpenBox-side controls in Authorize: guardrails, policies, and behavior rules.

The CopilotKit SDK sends runtime events to OpenBox. It does not create or store those controls inside CopilotKit.

Step 2: Install The SDK

The package is published on npm: @openbox-ai/openbox-copilotkit.

npm install @openbox-ai/openbox-copilotkit

If your app does not already install CopilotKit's runtime peers, install them too:

npm install @copilotkit/runtime @ag-ui/client

Step 3: Configure Environment

.env.local
OPENBOX_URL=https://core.openbox.ai
OPENBOX_API_KEY=obx_live_or_obx_test_agent_runtime_key

# Required when the OpenBox agent has signing enabled.
OPENBOX_AGENT_DID=did:aip:550e8400-e29b-41d4-a716-446655440000
OPENBOX_AGENT_PRIVATE_KEY=base64_raw_ed25519_private_key

For parent and child multi-agent setups, prefer explicit names such as OPENBOX_COPILOTKIT_API_KEY and OPENBOX_MASTRA_API_KEY in your application code. The SDK itself reads OPENBOX_API_KEY, OPENBOX_URL, OPENBOX_AGENT_DID, and OPENBOX_AGENT_PRIVATE_KEY by default.

Step 4: Keep The Route On Node

The SDK uses Node AsyncLocalStorage, so the CopilotKit route must not run on an edge runtime.

src/app/api/copilotkit/[[...slug]]/route.ts
export const runtime = "nodejs";

For Next.js apps, keep the server-only packages external:

next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
serverExternalPackages: [
"@copilotkit/runtime",
"@openbox-ai/openbox-copilotkit",
],
};

export default nextConfig;

Restart next dev after changing next.config.ts.

Step 5: Wrap The CopilotKit Runtime

Pass the same runtime options you would normally pass to new CopilotRuntime(...) into withOpenBoxRuntime().

src/app/api/copilotkit/[[...slug]]/route.ts
import {
CopilotRuntime,
InMemoryAgentRunner,
createCopilotEndpoint,
} from "@copilotkit/runtime/v2";
import { withOpenBoxRuntime } from "@openbox-ai/openbox-copilotkit";
import { handle } from "hono/vercel";

export const runtime = "nodejs";

const options = {
agents,
runner: new InMemoryAgentRunner(),
} satisfies ConstructorParameters<typeof CopilotRuntime>[0];

const { runtime: copilotRuntime, shutdown } = await withOpenBoxRuntime(
options,
{
middlewareOptions: {
frontendToolNames: ["setThemeColor", "showSnackbar"],
enforceApprovals: false,
},
},
);

process.on("SIGTERM", async () => {
await shutdown();
});

const app = createCopilotEndpoint({
runtime: copilotRuntime,
basePath: "/api/copilotkit",
});

export const GET = handle(app);
export const POST = handle(app);

withOpenBoxRuntime() reads OPENBOX_* values from the environment unless you pass apiKey, apiUrl, agentDid, or agentPrivateKey directly.

Step 6: Label Frontend Tools

CopilotKit can emit tool calls for frontend tools and backend tools through the same AG-UI event stream. OpenBox does not guess. Add every React-side tool name you want labelled as frontend:

const { runtime: copilotRuntime } = await withOpenBoxRuntime(options, {
middlewareOptions: {
frontendToolNames: [
"setThemeColor",
"showSnackbar",
"go_to_moon",
],
},
});

For dynamic registries, use isFrontendTool instead:

const frontendTools = new Set(["setThemeColor", "showSnackbar"]);

const { runtime: copilotRuntime } = await withOpenBoxRuntime(options, {
middlewareOptions: {
isFrontendTool: ({ name }) => frontendTools.has(name),
},
});

Without either option, observed tools are recorded with frontend: false and tool_origin: "copilotkit-observed".

Step 7: Choose Enforcement Behavior

The default mode records events and verdicts without stopping the CopilotKit stream:

middlewareOptions: {
enforceApprovals: false,
}

Set enforceApprovals: true when block or halt verdicts should stop a tool call after its full input arguments are known:

middlewareOptions: {
enforceApprovals: true,
}

When enforcement stops a stream, the client receives a redacted AG-UI error frame:

{
"type": "RUN_ERROR",
"code": "governance_blocked",
"correlationId": "<governanceEventId or approvalId>"
}

Tool name, tenant id, agent id, and verdict reason stay in OpenBox, not in the client-facing error frame.

Step 8: Optional Multi-Agent Handoff

Use multi-agent mode when a CopilotKit tool delegates to another OpenBox-governed agent and you want one OpenBox timeline with a parent to child handoff edge.

Register distinct OpenBox agents for each role:

RoleExampleOpenBox identity
Parent / orchestratorCopilotKit runtime routeCopilotKit API key and DID
Child / subagentMastra weather agentchild API key and DID

Configure the delegation tool on the CopilotKit parent:

src/app/api/copilotkit/[[...slug]]/route.ts
import type {
OpenBoxMultiAgentContext,
} from "@openbox-ai/openbox-copilotkit";

const pendingChildContext = new Map<string, OpenBoxMultiAgentContext>();

const { runtime: copilotRuntime } = await withOpenBoxRuntime(options, {
apiKey: process.env.OPENBOX_COPILOTKIT_API_KEY,
apiUrl: process.env.OPENBOX_URL,
agentDid: process.env.OPENBOX_COPILOTKIT_AGENT_DID,
agentPrivateKey: process.env.OPENBOX_COPILOTKIT_AGENT_PRIVATE_KEY,
middlewareOptions: {
multiAgent: {
enabled: true,
parentAgentDid: process.env.OPENBOX_COPILOTKIT_AGENT_DID,
handoffTools: {
weatherTool: {
childAgentName: "mastra-weather-agent",
childWorkflowType: "weather-agent",
childTaskQueue: "mastra",
childApiKey: process.env.OPENBOX_MASTRA_API_KEY,
childAgentDid: process.env.OPENBOX_MASTRA_AGENT_DID,
childAgentPrivateKey:
process.env.OPENBOX_MASTRA_AGENT_PRIVATE_KEY,
},
},
forwardContext: (ctx) => {
pendingChildContext.set(ctx.parentActivityId, ctx);
return { correlation_id: ctx.parentActivityId };
},
},
},
});

With child credentials present, the CopilotKit SDK sends the Handoff request authenticated as the child, so OpenBox can resolve the receiving agent correctly. Your application still needs to pass the forwarded context to the child runtime so that the child stream stamps the same multi_agent_session_id and parent_workflow_id.

Step 9: Verify A Live Request

Run one request through the Copilot UI, then check OpenBox for:

  • a workflow_type: "copilotkit" session
  • SignalReceived(user_input) and SignalReceived(agent_output) records
  • ActivityStarted and ActivityCompleted around each AG-UI tool call
  • frontend: true for tool names in your frontend allowlist
  • a Handoff event when multi-agent mode is enabled and a mapped delegation tool fires
  • a grouped child session when the child runtime also carries the same multi_agent_session_id

Next Steps