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
TypeScript
$ npm install @cirvix/agent-controlimport { 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
$ pip install cirvixfrom 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" raiseSecrets
The agent never holds the value
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.
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
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.
