Service Level Objectives
How to set availability, latency, and throughput objectives for an OGuardAI deployment, and the hard limits and security properties the runtime enforces
An SLO is a target you commit to for your own deployment, on your own hardware. OGuardAI publishes no latency or throughput figures, and this page does not invent any: see Benchmarks for the rule and for the harnesses that produce numbers from a run you execute. What this page does give you is the axes worth setting a target on, and the limits and properties the runtime enforces regardless of what you target.
Availability
Availability targets are yours to set. These are common starting points, not vendor commitments.
| Metric | Measurement |
|---|---|
| API availability | /readyz returns 200 |
| Transform endpoint | Non-5xx responses / total requests |
| Rehydrate endpoint | Non-5xx responses / total requests |
Rehydrate deserves the tighter target of the two: a failed transform can be retried from the original text, while a failed rehydrate strands output the caller already paid an LLM to produce.
Latency
Set the latency objective per detector mode, because the two modes are bounded by different things:
- Builtin-only is in-process CPU work. It scales with payload length and match count, and it has no external dependency to wait on.
- Builtin + NER (
bothoradvanced) adds a synchronous sidecar round trip and a model forward pass per detection. Its worst case is bounded bydetector.timeout_secs, so that value, not a target you write down, is what actually caps the tail. - Rehydrate performs local replacement and runs no detection, unless output NER scanning or a Redis-backed session or revocation backend puts it on the network. See Performance.
Measure your own baseline before committing to a number. The guardai_transform_duration_seconds and guardai_rehydrate_duration_seconds histograms give you the quantiles:
histogram_quantile(0.95, sum(rate(guardai_transform_duration_seconds_bucket[5m])) by (le))Throughput
Throughput has two independent capacity axes, and sizing only one of them is the common mistake:
- The API instances handle the builtin path and scale horizontally, because sealed sessions carry state with the request and instances do not coordinate on the transform path.
- The NER sidecars handle model inference. A NER-heavy workload needs proportional sidecar capacity; adding API instances does not add NER throughput.
Optional shared backends (Redis sessions, replay protection) introduce a coordination point a purely sealed deployment does not have. Shared revocation is not optional in a multi-replica deployment, so count it in.
Payload Limits
These are enforced by the runtime, not targets.
| Limit | Default | Configurable |
|---|---|---|
| Max request body (non-file routes) | 10 MB | server.max_body_size_bytes |
| Max file/image upload | 50 MB | file_upload.max_size_bytes |
| Max batch items | 100 | limits.max_batch_size (lower only; capped at 100) |
| Max detection spans per request | 10000 | limits.max_detected_spans (GUARDAI_MAX_DETECTED_SPANS, 1..=10000; over-cap request refused 413, over-cap config value fails startup) |
| PDF extraction address-space cap | 256 MiB | limits.pdf_extract_memory_mib (lower only) |
| PDF extraction wall clock | 10s | limits.pdf_extract_timeout_secs (lower only) |
| Session TTL | 3600s (1 hour) | session.ttl_seconds, no enforced ceiling; keep it short, it bounds how long a captured blob restores |
| Max concurrent streams (proxy) | 100 | GUARDAI_PROXY_STREAM_MAX_CONCURRENT (0 disables the cap) |
| Max concurrent streams (server streaming endpoints) | 100 | limits.max_concurrent_streams (GUARDAI_MAX_CONCURRENT_STREAMS), 1..=1024 |
| Server stream total duration | 3600s (1 hour) | limits.stream_max_duration_secs (GUARDAI_STREAM_MAX_DURATION_SECS), 1..=86400 |
| Server stream total egress | 64 MiB | limits.stream_max_total_bytes (GUARDAI_STREAM_MAX_TOTAL_BYTES), 1..=1073741824 |
| RAG ingest wall clock (per document) | 3600s (1 hour) | rag.max_ingest_duration_secs (GUARDAI_RAG_MAX_INGEST_DURATION_SECS), 1..=86400; over-budget ingest refused 503 GUARDAI_DEADLINE_EXCEEDED with nothing sealed |
| SSE heartbeat interval | 15s (proxy only) | Proxy SSE keep-alive; the server stream endpoints send none |
A document refused by a size ceiling returns 413 GUARDAI_PAYLOAD_TOO_LARGE, not a 400: the document may be perfectly valid. See PDF Support. The server streaming endpoints add three fail-closed bounds: a request past the concurrency ceiling is refused 503 GUARDAI_STREAM_CAPACITY_EXHAUSTED with Retry-After: 1; a response whose total serialized egress exceeds the byte ceiling is refused 413 GUARDAI_PAYLOAD_TOO_LARGE before the stream opens; and a stream whose client drain outlasts the duration budget is cut with a single terminal GUARDAI_STREAM_BUDGET_EXCEEDED event (never a complete event, so the truncation is detectable).
Error Budget
Budget on the axes you can actually observe:
| Error Type | Where to read it |
|---|---|
| 5xx responses | guardai_errors_total for the coarse signal, then the request failed log events for the error_code breakdown |
| Token repair failures | GUARDAI_TOKEN_REPAIR_FAILED in the structured log |
| Session expiry | GUARDAI_SESSION_EXPIRED in the structured log. Expected at the TTL boundary; a sustained spike means the TTL is misconfigured or clients are replaying stale blobs |
Detection accuracy is deliberately absent from this table. Precision and recall depend on the model, the language, and your own corpus, so they are not a number we can set on your behalf. Run benchmarks/ner_bakeoff/ against your own labelled data to get per-language and per-entity-type precision, recall, and F1, then budget from that.
Degraded Mode
When the NER sidecar is unavailable:
| Metric | Behavior |
|---|---|
both mode | The request still succeeds via builtin regex, after waiting up to detector.timeout_secs |
advanced mode | A sidecar outage fails the request closed with GUARDAI_DETECTION_UNAVAILABLE (503, retriable). NER is required in this mode |
Entity coverage in both | Builtin types only; person, company, and location are not detected |
Policy detection.required_for | Fails closed in either mode. A type declared required never passes raw because NER did not run |
| Data safety | Unaffected. Nothing detected is left raw in safe_text |
Session Security
| Property | Guarantee |
|---|---|
| Encryption | AES-256-GCM (AEAD) |
| Key strength | 256-bit minimum |
| Nonce uniqueness | Random 12-byte per seal (cryptographically random) |
| Tamper detection | Authentication tag verified on every unseal |
| Replay protection | Enforced for transform continuations via a per-session-id strict-monotonic counter store (replay_backend: memory default, or redis for HA). Rehydrate and RAG context are exempt by design and remain TTL-bounded bearer tokens. Memory backend has a cold-start window on restart; Redis closes it across replicas. Running redis replay across replicas is a multi-replica deployment, so it also requires revocation_backend: redis, otherwise a value revoked on one replica stays restorable through another and the server refuses to start |
| Cross-tenant isolation | Tenant ID validated during unseal |
Revocation
| Property | Guarantee |
|---|---|
| Revocation effect | Applies to future rehydrate calls. Output already delivered cannot be clawed back |
| Revocation persistence | Survives server restart (file backend) |
| Revocation consistency | Per-instance with the file backend; shared across replicas with revocation_backend: redis |
| Future restore suppression | A revoked value always resolves to [DELETED], even from a valid session blob |
The binding version of these, including what the person cascade does and does not cover, is the Revocation Contract.
Monitoring
# Prometheus alerts (see deploy/prometheus/alerting-rules.yml)
- guardai_transforms_total # request rate
- guardai_errors_total # error rate
- guardai_transform_duration_seconds # latency
- guardai_rate_limit_rejections_total # capacity
- guardai_prompt_security_triggers_total # securityThe shipped alerting rules carry starting thresholds, not tuned ones. See Monitoring.