CodeRifts contract governance for the OpenAI Agents SDK.
A tool-input guardrail decides, before a tool runs, whether the call is a governed contract mutation. It gates a tool even when CodeRifts never wrapped that tool's implementation — so you can govern third-party or hand-written tools you do not own.
npm install @coderifts/openai-agents @openai/agents @coderifts/sdk@openai/agents is a peer dependency (>=0.17.0); @coderifts/sdk is a dependency.
It is also a devDependency so npm install && npm test resolves the peer
without a second manual install. Consumers still supply the peer themselves.
CI (.github/workflows/ci.yml) runs that same clean install on ubuntu / Node 20.
import { Agent, run, tool } from '@openai/agents';
import { CodeRifts } from '@coderifts/sdk';
import { withCodeRiftsGuardrail } from '@coderifts/openai-agents';
const client = new CodeRifts({ apiKey: process.env.CODERIFTS_API_KEY });
const { guardrail, needsApproval } = withCodeRiftsGuardrail({
client,
operation: 'merge',
readPrior: (p) => fs.readFileSync(p, 'utf8'), // the `before` side
});
const writeFile = tool({
name: 'write_file',
description: 'Write a file.',
parameters: z.object({ path: z.string(), content: z.string() }),
inputGuardrails: [guardrail],
needsApproval, // REQUEST_APPROVAL suspends for a human
execute: async ({ path, content }) => { /* ... */ },
});attachGuardrailToEach(tools, guardrail) attaches it across a list you already have.
execution_action |
Behaviour |
|---|---|
CONTINUE, CONTINUE_WITH_MONITORING |
allow() — the tool runs |
REQUEST_APPROVAL |
guardrail allows, needsApproval returns true → the SDK emits a RunToolApprovalItem and suspends for a human |
STOP / BLOCK |
rejectContent() with the WHY + remediation |
| anything unrecognised | rejectContent(), message names it as not permission (fail closed) |
| unreadable arguments / missing path on a mutator | rejectContent() fail-closed |
| non-contract path, read-only tool | allow() with no API call |
rejectContent, not throwException: a governance deny must teach. throwException aborts the
whole run with a tripwire and the model learns nothing.
One preflight per tool call. The guardrail and the needsApproval resolver share a memo keyed
by the SDK's callId, so enabling approvals does not double your API spend.
With the CodeRifts guardrail attached to a tool, that tool is governed even if CodeRifts never
wrapped its implementation — so an agent can adopt governance for third-party or hand-written
tools it does not own, and a blocked contract change returns the reason and remediation to the model
rather than failing opaquely. The claim changes from per-table to per-tool-with-guardrail —
not to per-agent: tool input guardrails are attached per tool, there is no agent-wide form, and
we demonstrated that the same ungoverned tool still mutates an OpenAPI spec when the guardrail is not
attached to it. Handoff sub-agents, hosted tools, and direct client calls outside run() remain
ungated. Enforcement independent of the developer's diligence still lives downstream, at the
contract-gate on the pull request and the deploy-gate in CI.
- Tools without the guardrail attached. There is no agent-wide form —
'toolInputGuardrails' in agentisfalse, and the Agent'sinputGuardrailsis a different type that validates the agent's input, not tool calls.attachGuardrailToEachcovers exactly the list you pass it. - Handoff sub-agents. A sub-agent's tools are separate tool objects and do not inherit it.
- Hosted / built-in tools (
codeInterpreterTool,applyPatchTool). Built by SDK factories; you would have to interpose your own wrapper to addinputGuardrails. - Direct client calls outside
run(). They never reach the runner, so no guardrail fires.
The artifact is not the contract. Items 1–4 are tools the guardrail never sees. This one is a
change it sees and correctly lets through. The guardrail keys off a tool call that touches a
contract file. An implementation edit under an unchanged spec changes the served behaviour and
gives the guardrail nothing to weigh: no contract artifact moved, so there is nothing to preflight,
and an allowed call means only that the artifact did not change. A handler that starts returning
null for a field the schema still marks required, a response that quietly changes shape, an
authorization check deleted behind a byte-identical OpenAPI file — none of those are contract
changes as far as this package is concerned. Governing the artifact is not the same as governing
the behaviour it describes, and this package does not diff served behaviour.
MIT