Reliability
Unveilr sits in front of production agent actions. This page answers, without a meeting, the three things an architecture review asks: how slow are you, what happens when you're down, and how do we get back?
Written to be checkable. Every number below is reproduced by a benchmark in our test suite; every failure behaviour is covered by a test in our suite.
1. Where we sit
agent ──► MCP gateway ──► policy decision (in-process) ──► upstream MCP server
│
└── registry snapshot, refreshed off the hot path
agent ──► POST /v1/govern/check ──► policy decision ──► allow / deny / scoped_token
(LangChain, CrewAI, function-calling, raw HTTP — no MCP required)
Two properties that make the latency story tractable:
- The decision is a pure function. No network, no LLM, no external lookup — a deterministic evaluation over the policy set.
- The gateway's allow/deny path never calls the API. The registry is a snapshot refreshed out of band, so a control-plane hiccup does not sit in the request path.
2. Latency
Measured on the decision engine, warm process, worst case — every policy in the set matches, so all of them are evaluated:
| Enabled policies | p50 | p95 | p99 | Budget (CI-enforced) |
|---|---|---|---|---|
| 10 | 0.06 ms | 0.07 ms | 0.08 ms | 5 ms |
| 100 | 0.37 ms | 0.40 ms | 0.42 ms | 15 ms |
| 1000 | 3.49 ms | 3.65 ms | 3.81 ms | 120 ms |
End-to-end POST /v1/govern/check in-process — credential resolution, DB lookups,
policy decision and hash-chained evidence sealing, 25 policies:
| p50 | p95 | p99 | |
|---|---|---|---|
| full request | 2.66 ms | 4.39 ms | 5.87 ms |
Scaling is linear, not quadratic — 10× the policies costs ~9.4× the time, and CI fails if that shape changes. A control plane that is fine at 100 policies and unusable at 1000 cannot be sold to an estate that grows.
Budgets are set roughly an order of magnitude above observed, so the guard catches a regression in the shape of the cost (an accidental O(n²), a regex recompiled per call) without flaking on a busy runner.
Not yet measured, and we say so: sustained throughput under concurrency, and latency from a real client across the network. The numbers above exclude network time and are single-threaded.
3. Failure behaviour
The control plane fails closed everywhere it decides. Each row is enforced by a test, not just intended:
| What fails | Result | Why |
|---|---|---|
| Agent credential cannot be verified (control plane unreachable) | deny, and the failure is not cached | An unverifiable identity is not a trusted one; caching a transport error would extend an outage into a security gap. |
| Credential unknown, revoked, or expired | deny | The per-agent kill switch must be immediate. |
| Credential not bound to an approved agent identity | deny | The deployment gate. |
| Server unknown / not registered | deny (404) | You cannot govern what was never declared. |
| Server not approved | deny (403) | Approval is the gate, not a label. |
| Tool not in the registry | deny (404) | Prevents a rug-pulled tool from riding an approved server. |
| No policies configured | deny | Secure default; an empty rule set is not permission. |
Broker unavailable on a scoped_token decision | Call cannot proceed; the error is returned in place of a credential | Better no authority than unbounded authority. |
| Control plane unreachable for a registry refresh | Last good snapshot continues to serve | The hot path never depended on the API; a refresh outage degrades freshness, not availability. |
Why there is no fail-open switch
The obvious request is a toggle that lets traffic through when we are unavailable. We deliberately do not offer one: a control whose guarantee evaporates exactly when the system is under stress is not a control, and "fail open" is indistinguishable from "not deployed" at the moment it matters.
The supported way to not block is Monitor Mode, which is the default. A tenant observes until enforcement is switched on explicitly, so the choice is made deliberately and up front rather than implicitly during an incident:
PUT /v1/govern/enforcement {"default": "observe"}
In observe the decision is computed and sealed identically — only the consequence is withheld — so the monitor period is real evidence for the enforce period. See Monitor Mode.
4. Change management
| How it works | |
|---|---|
| Deploys | Rolling update with health checks; no in-place mutation of a running task. Observed end-to-end ~4 minutes. |
| Rollback | Redeploy the previous image tag — every release is an immutable, tagged artifact. |
| Schema changes | Applied as a one-off migration task before services roll, so a task never runs against a schema it does not expect. |
| Infrastructure state | Versioned remote state with locking, so two concurrent changes cannot interleave. |
| Evidence integrity | Hash-chained per tenant; verify any time with GET /v1/evidence/verify. |
Current limits
Stated plainly, because an operational page that hides them is worth less than one that names them:
- Sustained throughput under concurrency is not yet published. The latency figures above are single-threaded and exclude network time.
- One hosted environment. There is no separate multi-region topology today; self-hosting is documented in Self-hosting.
- Rollback is documented but not yet published with measured timings.