Skip to content

SDKs

One integration point: the boundary where your agent executes a tool. Wrap it once and every tool — including the ones you add next month — is governed.

Install

Wrap the executor, not each tool

The most common integration mistake is decorating individual tools. Do that and the tool someone adds in a hurry is the one that is ungoverned.

TypeScript

InstallShell
$ npm install @cirvix/agent-control
agent.tsTypeScript
import { guard, CirvixDenied } from "@cirvix/agent-control"; const tools = guard.wrap(myTools, {  agent: "pr-triage",  environment: process.env.CIRVIX_ENV ?? "local",  // Policies resolve from ./policies by default. In CI and  // production this is the same set, evaluated the same way.  policyDir: "./policies",}); try {  await agent.invoke(input);} catch (err) {  if (err instanceof CirvixDenied) {    // Typed, so the agent can re-plan rather than retrying.    console.log(err.policy);      // "deny-dotenv-read"    console.log(err.remediation); // 'secrets.get("STRIPE_KEY")'    console.log(err.decisionId);  // "dec_01JQ8F2K7M"  }  throw err;}

Python

InstallShell
$ pip install cirvix
agent.pyPython
from cirvix import guard, CirvixDenied tools = guard.wrap(    my_tools,    agent="support-triage",    environment=os.environ.get("CIRVIX_ENV", "local"),    policy_dir="./policies",) try:    agent.invoke(payload)except CirvixDenied as err:    # Same shape as the TypeScript SDK — the denial is data,    # not a stack trace, so the agent can act on it.    print(err.policy)       # "deny-dotenv-read"    print(err.remediation)  # 'secrets.get("STRIPE_KEY")'    print(err.decision_id)  # "dec_01JQ8F2K7M"    raise

Secrets

The agent never holds the value

Request a handle. The real credential is substituted into the outbound request at the boundary, after policy has authorized that specific destination.

A handle sent anywhere the policy did not sanction resolves to nothing. That is the entire security property, and it is worth stating plainly: substitution protects the credential from the model — it does not stop an authorized call from doing something expensive. That is what policy and approvals are for.

secrets.tsTypeScript
import { secrets } from "@cirvix/agent-control"; // Returns an opaque handle: "sec_handle_a89f".// The model sees this string and nothing else.const key = await secrets.get("STRIPE_RESTRICTED_KEY"); await fetch("https://api.stripe.com/v1/charges", {  headers: { Authorization: `Bearer ${key}` },}); // At the boundary Cirvix://   1. checks the destination against the handle's allowlist//   2. resolves the value from your vault//   3. substitutes it into the outbound request//   4. records the handle — never the value — on the trace//// The same handle sent to evil.example resolves to nothing// and the call is denied by the egress policy.

Testing

Assert on decisions, not on behaviour

Policy is code. It gets unit tests, and they run without a network, a model, or a running control plane.
policy.test.tsTypeScript
import { evaluate } from "@cirvix/agent-control/testing"; test("agents cannot read dotenv files", async () => {  const decision = await evaluate({    policyDir: "./policies",    agent: "pr-triage",    action: "fs.read",    resource: "/repo/.env.production",    context: { environment: "production" },  });   expect(decision.verdict).toBe("deny");  expect(decision.policy).toBe("deny-dotenv-read");}); test("production writes are held for a human", async () => {  const decision = await evaluate({    policyDir: "./policies",    agent: "deploy-bot",    action: "k8s.apply",    resource: "production/checkout",    context: { environment: "production" },  });   expect(decision.verdict).toBe("hold");  expect(decision.approvers).toContain("platform-oncall");});

SDKs

Read the reference.

Full API surface, typed denial shapes, the local evaluator, and framework-specific guides.

Cirvix AgentControl is in private beta. Pricing is published and plans are not yet chargeable — early access confirms your place and we will tell you before anything bills.