OGuardAI
Architecture

Detector Runtime Contract

Detection modes, guarantees, fallback behavior, quality expectations, and how to verify the runtime configuration

Detection Modes

OGuardAI operates in one of three detection modes, configured in oguardai.yaml:

Mode: builtin (Deterministic)

detector:
  mode: builtin

Guarantees:

  • Builtin detection in low single-digit ms (indicative, hardware-dependent)
  • Builtin entity types: the named formats plus the always-on builtin_custom detectors, detected via the regex registry
  • Zero external dependencies
  • Deterministic: same input always produces same output
  • Horizontally scalable, no shared state

Named builtin entity types: email, phone, ssn, iban, vat_id, credit_card, ip, url, order, customer_id, passport, health_id, date_of_birth, address

Always-on builtin_custom detectors: money (ISO 4217 currency amounts), de_tax_id (German tax ID), de_sozialversicherung (German social security), quote_id (quote/offer IDs), case_ref (case references). These emit typed custom:<name> entities and run in every mode, including builtin. Source: crates/core/src/types.rs BUILTIN_CUSTOM_NAMES, registered in detector-builtins.

NOT available (requires NER): person, company, location

Mode: both (Full Coverage)

detector:
  mode: both
  advanced_url: http://localhost:9090

Guarantees:

  • The builtin entity types (named + builtin_custom) plus the NER-only types person, company, and location
  • Person names, company names, locations detected via GLiNER/spaCy
  • Graceful fallback: if NER sidecar is unreachable, both mode falls back to builtin-only
  • Fallback timeout: configurable (detector.timeout_secs)

Additional entity types: person, company, location

Latency (indicative, not guaranteed):

  • Builtin entities: ~1-2ms
  • NER entities: ~80ms p50, up to several hundred ms p99, depending on hardware, text length, and model

Fallback behavior when NER is unavailable:

  1. Server logs: ner_service_unavailable_falling_back_to_builtin
  2. Detection continues with builtin regex only
  3. person/company/location will NOT be detected
  4. Health endpoint shows: builtin_and_ner (full entity detection) but actual capability degrades
  5. No crash, no hang: bounded by the configured NER timeout (detector.timeout_secs)

Mode: advanced (NER Only)

detector:
  mode: advanced
  advanced_url: http://localhost:9090

NER is mandatory in this mode. Builtin regex still contributes structured detection, but NER cannot be bypassed: requesting builtin_regex alone is rejected, and if the Python sidecar is unavailable the request FAILS CLOSED with GUARDAI_DETECTION_FAILED rather than downgrading to builtin-only. Graceful fallback to builtin is a both-mode behavior only.

The same fail-closed rule applies whenever a loaded policy marks an entity type required_for (via detection.ner_labels, or the opt-in healthcare-hipaa template): the runtime gates on whether NER actually ran, not on the configured mode, so a required_for type that NER did not produce rejects the request instead of silently passing the input through.

Detection Mode Decision Tree

How to Verify Runtime Mode

# Check current mode (requires admin scope)
curl -H "X-API-Key: $GUARDAI_API_KEY" http://localhost:3000/v1/diagnostics | jq .detector_mode

# Check available entity types
curl -H "X-API-Key: $GUARDAI_API_KEY" http://localhost:3000/v1/capabilities | jq '.entity_types[].name'

# Check per-request mode (in transform response)
curl -X POST http://localhost:3000/v1/transform \
  -H "X-API-Key: $GUARDAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":"test","input_type":"text"}' | jq .detector_mode

Quality Expectations by Mode

Entity TypeBuiltin+ GLiNERNotes
EmailPrecision >99%SameRegex + Unicode support
PhonePrecision >95%SameInternational formats
SSNPrecision >99%SameStrict format matching
IBANPrecision >99%SameCountry code + structure
Credit CardPrecision >99%SameLuhn validation
Person NameN/APrecision ~85-95%Language-dependent
Company NameN/APrecision ~80-90%Context-dependent
LocationN/APrecision ~80-90%May overlap with Address

Overlap Precedence

When a regex span and an NER span cover the same text, the merge applies a deterministic precedence so the entity type is stable regardless of NER over-tagging:

  • Structured regex types always win over NER. email, phone, ssn, iban, ip, url, order, passport, health_id, customer_id, and critically credit_card and date_of_birth are precise, deterministic, high-value PII. If NER tags an overlapping span (for example a date read as an event or a card number read as an ID), the structured regex type is kept, not downgraded to the NER label.
  • NER wins for name-shaped types (person, company, address) over non-structured regex overlaps.
  • Otherwise the longer span wins, then the higher-confidence span, with ties resolved deterministically.

NER Precision Floor (False-Positive Suppression)

Zero-shot NER (GLiNER) over-tags common function and greeting words as person/company/location entities. OGuardAI applies a built-in precision floor after NER, additive over the raw model output:

  • A universal acronym stoplist (~50 entries: PII, API, CEO, IBAN, SWIFT, HR, SSN, ...) drops NER spans that are just a known acronym.
  • A per-language noise floor (shipped as data for German and Arabic today) suppresses NER spans whose tokens are all noise words, stopwords, or proclitics, including glued phrases the model tags as one entity.
  • A minimum-confidence floor for NER-only types (built-in 0.65) drops low-confidence names.

This floor is a precision floor, not a ceiling: a deployment may add acronyms, un-suppress a specific built-in acronym so an acronym-shaped real organization name is detected, or raise the confidence floor (stricter only). It can never lower the floor or weaken detection below the built-in baseline, and invalid overrides are rejected at startup.

Recommendation

  • For maximum speed + determinism: Use builtin mode
  • For maximum coverage: Use both mode with healthy NER sidecar
  • For production safety: Configure health monitoring for NER sidecar availability