Skip to content

Guardian — confidential-content detection

Guardian answers one question about a message: does this contain something that should not leave the building? What to do about it — warn, keep it on a local model, block — is the caller’s decision, never Guardian’s.

Guardian is a stateless HTTP service. A client sends it a text and gets back a verdict with the findings. It never stores messages and never decides policy. Source: Odyssai-eu/odyssai-guardian (MIT).

StageWhat it catchesHowLatencyAlways on?
1 — PII / GDPRIdentifiers, and GDPR-style identification: an attribute tied to an identitya GLiNER2 privacy modelabout 40 msyes
2 — contextualBusiness secrets, health narratives, HR and legal matters, strategy — what named-entity recognition missesa direct yes/no prompt to a small instruct model200 to 400 msopt-in per request

Stage 2 runs only when the caller asks for it and stage 1 came back clean: once the PII pass has flagged a message, there is nothing to gain from the slower stage.

A quasi-identifier on its own is not confidential. “How do I negotiate my salary” is a generic topic. It becomes personal data when it is tied to an identity: “Marie’s salary is 85k” identifies a person. Hard identifiers (IBAN, social security number, card number, passport) and secrets (API keys, passwords) are sensitive on their own.

sensitive = hard_identifier OR secret OR (identity AND attribute)
  • GET /health — {status, service, model, loaded, contextual_available}
  • POST /guard with
{
"text": "...",
"threshold": 0.5,
"contextual": true,
"llm_base": "http://<engine>:8000/v1",
"llm_model": "<a model alias that engine serves>"
}

returns

{
"sensitive": true,
"max_severity": "high",
"findings": [{ "category": "...", "severity": "...", "spans": [] }],
"latency_ms": 41.2
}

The stage-2 model is supplied by the caller in every request: llm_base is any OpenAI-compatible endpoint — typically an OdyssAI-X engine — and llm_model an alias it serves. An empty llm_base skips stage 2. Stage 2 fails open: an error there never blocks a verdict.

Docker is the recommended path, and the required one on macOS when stage 2 calls an engine on the network:

Terminal window
docker compose up -d # port 8084; the image bakes the GLiNER model
curl -s http://localhost:8084/health

The first /guard request loads the model (a few seconds); subsequent calls take about 40 ms.

On macOS, do not run stage 2 under launchd or nohup: those processes are blocked from the local network and the call to a remote model fails silently. A Docker container is not affected.

  1. Guard URL — http://<guardian-host>:8084/guard. From another container on the same compose network, http://guardian:8084/guard.
  2. Policy — the caller’s. warn tells the user; force-local routes the message to a local model instead of the cloud; block refuses. Guardian only reports what it found.
  3. Stage 2 — optional, configured in the caller: pass llm_base and llm_model in each request.

Guardian runs today as a standalone service. Wiring it into Nemo’s guardian agent and into the CoeOS box is open work.

In the CoeOS suite for regulated professions

Section titled “In the CoeOS suite for regulated professions”

The open-source Guardian ships with the GDPR stage and a generic contextual stage. In the paid CoeOS suite, Guardian is customised to the profession’s own confidential categories — the vocabulary of a law firm, a medical practice, an HR department — on top of GDPR. That customisation is the suite.