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).
Two stages
Section titled “Two stages”| Stage | What it catches | How | Latency | Always on? |
|---|---|---|---|---|
| 1 — PII / GDPR | Identifiers, and GDPR-style identification: an attribute tied to an identity | a GLiNER2 privacy model | about 40 ms | yes |
| 2 — contextual | Business secrets, health narratives, HR and legal matters, strategy — what named-entity recognition misses | a direct yes/no prompt to a small instruct model | 200 to 400 ms | opt-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.
The identification model
Section titled “The identification model”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 /guardwith
{ "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.
Install
Section titled “Install”Docker is the recommended path, and the required one on macOS when stage 2 calls an engine on the network:
docker compose up -d # port 8084; the image bakes the GLiNER modelcurl -s http://localhost:8084/healthThe 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.
Wire a client
Section titled “Wire a client”- Guard URL —
http://<guardian-host>:8084/guard. From another container on the same compose network,http://guardian:8084/guard. - Policy — the caller’s.
warntells the user;force-localroutes the message to a local model instead of the cloud;blockrefuses. Guardian only reports what it found. - Stage 2 — optional, configured in the caller: pass
llm_baseandllm_modelin 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.
Read next
Section titled “Read next”- What CoeOS is — where Guardian sits in the stack.
- Nemo — the client — the guardian agent on the client side.
- CoeOS box — the router — the local-versus-cloud decision Guardian informs.