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.
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/runtimeand@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:
- Register or open an OpenBox agent.
- Generate an agent runtime key.
- Copy the generated DID and private key unless Require signing is disabled.
- 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
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.
export const runtime = "nodejs";
For Next.js apps, keep the server-only packages external:
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().
- CopilotKit
- OpenBox
import {
CopilotRuntime,
InMemoryAgentRunner,
createCopilotEndpoint,
} from "@copilotkit/runtime/v2";
import { handle } from "hono/vercel";
export const runtime = "nodejs";
const options = {
agents,
runner: new InMemoryAgentRunner(),
} satisfies ConstructorParameters<typeof CopilotRuntime>[0];
const copilotRuntime = new CopilotRuntime(options);
const app = createCopilotEndpoint({
runtime: copilotRuntime,
basePath: "/api/copilotkit",
});
export const GET = handle(app);
export const POST = handle(app);
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:
| Role | Example | OpenBox identity |
|---|---|---|
| Parent / orchestrator | CopilotKit runtime route | CopilotKit API key and DID |
| Child / subagent | Mastra weather agent | child API key and DID |
Configure the delegation tool on the CopilotKit parent:
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)andSignalReceived(agent_output)recordsActivityStartedandActivityCompletedaround each AG-UI tool callfrontend: truefor tool names in your frontend allowlist- a
Handoffevent 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
- Run the Demo
- CopilotKit SDK reference
- Mastra SDK or LangGraph SDK if you also need backend-framework instrumentation