Quick start
Pick the road that matches where your agent lives. Both end with the gate stopping something and a receipt you can read.
Quick start
Two roads, and the only question is where your agent runs.
| npx | SDK | |
|---|---|---|
| Your agent runs in | Claude Code | Anywhere else |
| Also covers | Runtimes with their own hooks, Cursor among them | LangChain, the OpenAI Agents SDK, CrewAI, workflow platforms |
| You install | A PreToolUse hook | One file in your harness |
| Time to a first verdict | One command and a restart | One call and a gate URL |
| Network on the decision path | None | The round trip to the gate |
| Who operates the gate | You | Coco, or you |
Both end the same way. The gate stops a call that should not have run, and writes a row naming the contract that stopped it.
The gate does not know which runtime called it. It reads one proposed action and answers, which is why the same core serves an editor's hook and a platform's harness. A runtime with its own hooks, Cursor among them, runs the same gate as its hook, and a framework like LangChain or the OpenAI Agents SDK takes the SDK road with one call where its tools execute. Claude Code is the example on every page, because it is where most agents run.
The npx road
Your agent runs in a runtime with hooks. The gate installs as one, nothing about your agent changes, and the walkthrough shows Claude Code.
npx @trustcoco/guardrails claude-codeRestart Claude Code and it is on, in observe mode, stopping nothing. Node 18 to
install, python3 to run, standard library only. Everything stays on your
machine, and there is no account, no service to reach and no network call on the
decision path.
The SDK road
Your agent runs on a platform, in a container, in a workflow builder, or anywhere without a terminal. The gate runs as a service and your harness asks it one question before each action.
from coco_gate import CocoGate
gate = CocoGate(GATE_URL, api_key=key, session_id=run_id)
verdict = gate.check("payment.send", {"amount": 25000, "payee": payee})
if not verdict.allowed:
refuse(verdict.reason)The client is one file with no dependency beyond the language itself. Coco can run the gate for you, or you can run the container.
Running both
A team can take both roads. The developer machines take the hook, the production agents take the SDK, and both write receipts in the same shape under the same contracts.
That works because there is one gate. serve.py runs gate/coco_gate.py as a
subprocess with the payload on stdin, exactly as the hook does, so the decision
path has one implementation and an auditor has one story to follow.
What ships turned on, either way
Four contracts, deliberately quiet. Ordinary work runs untouched.
| Contract | Fires when | Answer |
|---|---|---|
baseline.credential_access | The call touches a credential file | ESCALATE to read, BLOCK to write |
baseline.destructive_command | The command destroys with no safe reading | BLOCK |
session.blast_radius | This session has written more files than your ceiling | ESCALATE |
session.credential_then_egress | Data leaves after this session read a secret | BLOCK |
The last one is the one worth understanding. Reading .env is fine and fetching a
URL is fine, and doing the second after the first is exfiltration. A check that
looks at one call at a time allows both, and the gate keeps session state so it
sees the pair.
Two further sets exist as worked examples, one for a KYC case folder and one for agent-initiated payments. Both stay off until a mandate names them, and neither ships in the npm package because neither can fire for a coding agent. They live in the repository.
Next
- Architecture for how the two roads share one core
- Modes for the week before anything stops
- Can the agent get past it for what was tested