Guardrails
Policy checks on every agent tool call, configured first, formally verified before they run, and enforced through the SDK or the gateway.
Guardrails put a boundary around what your agent can do. Every tool call the agent attempts (a transfer, an email, a database write) is checked against your policies before it executes. A call that violates a policy is blocked, and the blocking reason is returned to the agent in machine-readable form, so it adapts instead of retrying blindly.
Three properties define the layer:
- Deterministic. The check is a computation over the call and the session's history. No model in the loop: the same call gets the same verdict, every time, and the verdict can be reproduced later, which is what makes it usable as evidence.
- Fast. A check takes microseconds. Tool calls take seconds. The overhead is not measurable in practice.
- Verified. The policy set is proven coherent before it is allowed to run. This is the part nobody else does.
Policies come first
You start by declaring what the agent is allowed to do, as typed policies. The catalogue is deliberately small and covers the shapes that matter in practice:
| Constraint | What it caps | Example | type: |
|---|---|---|---|
| Numeric bound | A numeric argument | Transfers capped at 5,000 | Bounded |
| Value restriction | Which values an argument may take | Only approved instruments; recipients on an allowlist | |
| Ordering | Which call has to come first | A balance check must precede any transfer | Precedes |
| Session budget | A total across the whole session | Total spend, not just per-call amounts | |
| Repetition cap | How often a call may repeat | At most three payments per recipient; this tool never twice | MaxCalls |
| Tool allowlist | Which tools exist at all | Anything not declared is blocked | |
| Conditional activation | When another policy applies | A tighter cap until a human approves; stricter rules once the agent has read untrusted content | When |
You define policies in the Harbor platform: pick the type, scope it to a tool, set the bounds. The configuration is versioned, and it reads as YAML:
# illustrative
tools: [check_balance, transfer, read_email]
tiers:
untrusted: [read_email]
internal: [check_balance, transfer]
mode: observe # flip to enforce, per policy, when ready
policies:
- type: Bounded
name: transfer_cap
tool: transfer
arg: amount
lower: 0
upper: 5000
- type: Precedes
name: balance_first
first: check_balance
second: transfer
- type: When # freeze transfers once untrusted content is read
name: taint_freeze
when: { trust_below: internal }
policy: { type: MaxCalls, tool: transfer, max_calls: 0 }Policies can also be drafted from a plain-language description and turned into typed policies you review. Either way, nothing activates without passing verification.
Verified before it runs
This is the part nobody else does
Every rule engine evaluates rules. Almost none of them check the rules themselves.
Rule sets accumulate contradictions: one policy requires an approval step that another policy makes impossible, or a new rule silently forecloses a path the workflow depends on. You find out in production, when the agent deadlocks or does something the rulebook was supposed to prevent.
Harbor verifies the entire policy set for coherence before it can be deployed. A contradictory set is rejected at configuration time, with a concrete explanation of which policies conflict and why. The verification is exhaustive over the policy language, a proof rather than a test suite, so a set that passes cannot contradict itself at runtime. We do not document the procedure itself here.
Changes are held to the same standard. When you edit a deployed policy set, the new set is checked against the old one: if the change would newly permit something that was blocked before, you are shown a concrete example call that demonstrates it, before the change goes live.
| Configuration time | Runtime | |
|---|---|---|
| Policy set proven coherent | Exhaustively, over the policy language | Already guaranteed |
| Contradictory set | Rejected, naming the policies that conflict | Cannot occur |
| Edit to a deployed set | Diffed against the old set, with a counter-example call | — |
| Individual tool call | — | Evaluated in microseconds |
Observe first, then enforce
Every policy runs in one of two modes, and you flip individual policies between them with no re-integration and no code change.
| Observe | Enforce | |
|---|---|---|
| Policy evaluated on every call | Yes | Yes |
| Verdict recorded | Yes | Yes |
| Violating call blocked | No | Yes |
| Risk to production traffic | None | The blocked call |
Observe mode shows you exactly what enforcement would have stopped. When the picture looks right, you promote the policies you trust.
Where it runs
The same engine and the same policies work at two integration points — the SDK inside your agent's process, and the gateway on the wire. They differ only in what they can see; the overview has the full comparison.
Through the SDK, wiring looks like this:
# illustrative
import harbor
from harbor.guardrails import guard_tools
harbor.init()
tools = guard_tools(tools, agent="payments-agent")
# every tool call is now checked against the policies
# configured for payments-agent in the Harbor platformThe SDK ships adapters for the common agent frameworks, so wiring stays a few lines whichever one you use. Through the gateway there is nothing to wire: guardrails are enabled per agent in the platform and enforced on the wire.
Start in observe mode on whichever is easier to adopt; move to the SDK where enforcement matters most.