OpenAI Agents SDK
Wrap OpenAI Agents SDK tools so the kernel admits each call under a signed capability before the tool body runs.
On this page
An agent built with the OpenAI Agents SDK chooses its tools; a tool you build with chioTool runs only after the Chio kernel admits that call. The SDK keeps the agent loop, handoffs, and tracing. Chio holds the capability for each tool and signs a receipt for every call, refusals included.
@chio-protocol/openai-agents wraps the SDK's tool() helper the way @chio-protocol/ai-sdk wraps the Vercel AI SDK. The wrapper sends each call to a Chio sidecar beside the agent process at http://127.0.0.1:9090, and the kernel in the sidecar checks the capability token, runs the guards, and signs the receipt before execute is called.
1. Install the adapter beside your agent
Install the adapter with the Agents SDK and zod, its two peer dependencies. Run the Chio sidecar next to the process that runs the agent, and give that process the signed capability token for the tools it may call.
npm install @openai/agents zod @chio-protocol/openai-agents2. Wrap each tool the agent can call
chioTool takes the same name, description, parameters, and execute as tool(), plus a scope that binds the call to a Chio tool server, a tool name, and the capability token.
import { z } from "zod";
import { chioTool } from "@chio-protocol/openai-agents";
export const searchTool = chioTool({
name: "search",
description: "Search the web",
parameters: z.object({
query: z.string().describe("search query"),
}),
execute: async ({ query }) => {
return await fetchSearchResults(query);
},
scope: {
toolServer: "web-tools",
toolName: "search",
capabilityToken: process.env.CHIO_CAPABILITY_TOKEN!,
},
});The wrapper gates the call at the execute entry point, before any side effect. It returns a tool object with the shape tool() returns, so it drops into an agent's tools list and keeps full type inference from the zod schema.
3. Run the agent and read each refusal
Give the wrapped tools to an Agent and run it as usual. The model can request any tool in its list; the kernel decides whether each call runs.
import { Agent, run } from "@openai/agents";
import { searchTool } from "./tools";
const agent = new Agent({
name: "Researcher",
instructions: "Answer with sources. Use search for anything recent.",
tools: [searchTool],
});
const result = await run(agent, "Research quantum computing advances");
console.log(result.finalOutput);When the kernel denies a call, execute throws ChioToolError carrying the verdict, the guard that refused, the reason, and the receipt id. The Agents SDK passes the thrown error to the tool's errorFunction, which by default reports it to the model as a failed tool call, so the agent can answer without the tool. Set errorFunction to null where a refusal should stop the run instead.
import { Agent, run } from "@openai/agents";
import { ChioToolError, chioTool } from "@chio-protocol/openai-agents";
// errorFunction: null rethrows the refusal instead of reporting it to the model.
const strictSearch = chioTool({ ...searchOptions, errorFunction: null });
const agent = new Agent({ name: "Researcher", tools: [strictSearch] });
try {
await run(agent, "Summarize the quarterly numbers");
} catch (error) {
if (error instanceof ChioToolError) {
// error.verdict, error.guard, error.reason, error.receiptId
console.warn(`chio ${error.verdict} via ${error.guard}: ${error.reason} (receipt=${error.receiptId})`);
}
throw error;
}Every outcome except an admitted call stops the tool
The wrapper runs execute only for an allow verdict backed by a signed capability token and verified authorizing evidence. A deny, a pending_approval verdict, a missing token, and an unreachable sidecar each throw ChioToolError, and onSidecarError: "allow" does not change that.
| Outcome | error.verdict | Tool body |
|---|---|---|
| Admitted call | none, the call returns | Runs |
| Kernel denies | deny | Never runs |
| Approval required | pending_approval | Never runs |
Only scope.capabilityId given | incomplete | Never runs |
| Sidecar unreachable | sidecar_unreachable | Never runs |
An approval the SDK collects through needsApproval happens before execute. The kernel still evaluates the call after a person approves it in the SDK, so a run that approves its own interruptions never gains authority the capability lacks. Keep approver lists and thresholds in the kernel's approval policy.
Read more in Require human approval.
Adapter reference
Try scope, resolveCapabilityToken, ChioToolError, or ChioClient.
chioTool options
name,description,parameters. Forwarded totool().parametersis azodobject schema.execute. The tool implementation, called only when Chio admits the call. A returned stream or async generator passes through unbuffered.needsApproval,errorFunction. Forwarded totool()unchanged.scope. AChioToolScope:toolServer,toolName, andcapabilityToken, the full signed token.client. An optional sharedChioClient.clientOptions. Inline client options:sidecarUrl,timeoutMs,fetch,debug.resolveCapabilityToken. Resolvesscope.capabilityIdto the full raw token when the id is only an indirection key.debug. An optional debug hook; the wrapper never writes to stdout.
Capability id and token
scope.capabilityId is a request hint. The sidecar evaluates deny by default and requires the signed token through scope.capabilityToken or resolveCapabilityToken; an id with no token throws ChioToolError with verdict incomplete before the sidecar is called.
ChioToolError
Thrown when the sidecar denies a call or the transport fails. Fields: verdict, guard (an empty string on transport errors), reason, and receiptId when a signed receipt exists.
ChioClient
A minimal HTTP client for POST /chio/evaluate. Share one client across tools to reuse its configuration. It accepts the sidecar's EvaluateResponse of verdict, receipt, and evidence.
import { ChioClient, chioTool } from "@chio-protocol/openai-agents";
const chio = new ChioClient({ sidecarUrl: "http://127.0.0.1:9090", timeoutMs: 5000 });
const readTool = chioTool({ name: "read_file", /* ... */ client: chio, scope: { toolServer: "fs", toolName: "read_file", capabilityToken } });
const writeTool = chioTool({ name: "write_file", /* ... */ client: chio, scope: { toolServer: "fs", toolName: "write_file", capabilityToken } });