Skip to content

An open-source project by FlowX.AI

Check what crosses into and out of your model.

It runs on CPU with the network interface down. An embeddable Python library: two functions, a policy file a reviewer can read without knowing Python, and an evidence record that holds hashes rather than user text.

Built for teams that run models inside their own perimeter, in more languages than English, and have to prove what was checked.

See the numbers
33detectors in the v1 set
24 msp95 for a PII scan: 87 tokens, about a short paragraph, on one thread
26languages every classifier is scored in
30 of 33need nothing beyond a CPU
  1. 01 · Tiers

    33 detectors, four tiers.

    The cheap checks run on everything. The expensive ones run only when something cheaper has found a reason, which is what keeps a scan inside its latency budget.

    T0 3
    Always runs. Cannot be disabled. disclosure, invisible_text, secrets
    T1 20
    Runs on the standard path. banned_terms, code_present, confusables, and 17 more
    T2 7
    Runs on the standard path. Can be disabled by policy. bias, injection, moderation, and 4 more
    T3 3
    Runs only when a lower tier flags, or when the policy asks for it. groundedness, topic_scope, url_reachability
  2. 02 · Stamp

    Every crossing leaves a record.

    What ran, which model revision made each call, the resolved policy hash, and a hash of the input. Never the text itself. The record is canonical JSON, so its hash reproduces on another machine, and it can be signed with a key you hold.

  3. 03 · Local

    Nothing leaves the machine.

    Weights are fetched once and cached. After that a scan works with the network interface down, so the text you check never reaches a third party and there is no per-call bill.

    139 msper document on device, $0 per 1k
    854–2,472 msfive hosted models, $0.032–$0.769 per 1k

    piiguard against five hosted models on 30 documents. The benchmark

04 · Try it

Two functions. Try them.

There is no client to construct, no gateway to run, and nothing wraps your model call. You call two functions and decide what to do with what they return. This one is live: pick an example or type your own.

guarded_turn.py · liveborder-api
1from flowx_border import scan_input, scan_output, load_policy
2
3policy = load_policy("border-code.yaml")
4
5crossing = scan_input(user_text, policy)
6if crossing.verdict == "block":
7 return refuse(crossing.evidence.record_id)
8
9answer = your_model.complete(crossing.text)
10out = scan_output(answer, policy)
11archive(out.evidence)

Live: this calls flowx_border on a small CPU instance kept for this site, and the text above is sent to it. In your deployment the library runs inside your own process, offline.

05 · Architecture

Check hard at the border, not inside.

A message passing between two services that already trust each other has been checked once, and checking it again costs latency without changing the answer. The cost of inspecting everything everywhere is the reason teams end up turning inspection off.

The design is borrowed from the Schengen model: internal checks could be abolished only because the external checks became strong, uniform, and governed by one shared rulebook.
The boundary modelText arrives from outside, passes an inbound check on the perimeter of a trusted area, is handled by two services that exchange it between them with no further inspection, and passes an outbound check on the way out.untrusted inputchecked outputtrusted areacheckcheckplannerexecutorno re-inspection
The exchange between the two services carries no check, and that is the design. The cost of inspecting everything everywhere is what makes teams turn inspection off.
Border
The library. Where checks happen.
Code
The policy file, border-code.yaml. The shared rulebook, named after the Schengen Borders Code.
Crossing
A single scan, inbound or outbound.
Stamp
The signed evidence record attached to a crossing.
Area
The trust domain. Services that accept each other's stamps.

06 · Catalogue

The 33 detectors.

Tiers decide what runs when, so the expensive checks only run once something cheaper has found a reason.

Reading the table. Runs today means installed and callable now. Trained means the model exists but the detector is not yet callable; a policy that asks it to block or redact raises before any scan happens. Needs dependency means an optional extra must be installed. Needs network means the check makes outbound requests and is off unless a policy turns it on. In the F1 column, not a classifier marks rule-based detectors, which are deterministic and measured on latency only. No corpus yet means no evaluation set we trust exists; the cost column says whether its figure is measured or a budget.

33 of the 33 run today. The rest are catalogued and do not run: a policy that asks an unavailable detector to block or redact raises before any scan happens rather than letting text through as if it had been checked. Measured figures are p95 at 87 tokens on 1 thread; where there is no measurement the column shows the budget and says so.
DetectorTierWhat it doesNeedsBacked byMean F1CostStatus
secretsinput, outputruns todayT0Credentials in text on its way to the model: named key formats, plus a deliberately conservative entropy rule.CPUrulenot a classifier0.028 msmeasuredRuns today
piiinput, outputruns todayT1Personal data in input or output, as named entity spans with checksum validation where the identifier has one.CPUnerper entity24.354 msmeasuredRuns today
injectioninputruns todayT2Attempts to talk the model out of its instructions.CPUclassifier0.98940.669 msmeasuredRuns today
moderationinput, outputruns todayT2Thirteen hazard categories in one pass, from violent crime to election misinformation. Replaces the capability Llama Guard and ShieldGemma provide, with weights this project can ship.CPUclassifier0.98041.637 msmeasuredRuns today
toxicityinput, outputruns todayT2Abusive or hateful language, in input or output.CPUclassifier0.99219.062 msmeasuredRuns today
regulated_adviceoutputruns todayT2Output that reads as regulated financial, legal or medical advice.CPUclassifier0.98640.609 msmeasuredRuns today
topic_scopeinputruns todayT3Whether a request is inside the subject matter the product covers.CPUclassifierno corpus yet146.453 msmeasuredRuns today
groundednessoutputruns todayT3Whether the claims in an answer are supported by the sources it was given.CPUclassifierno corpus yet12.851 msmeasuredRuns today

07 · Why it is different

Three claims, each measured.

0 network calls

It runs where your data already is

CPU is the reference target, not a fallback. Weights are fetched once and cached, and after that a scan works with the network interface down, which a test asserts by making a socket call raise.

26 languages

Detection that starts multilingual

The PII model is trained across all 26 target languages at once, each country's national identifier generated to pass its own checksum. Span-level F1 0.998, weakest language French at 0.977. Each of the 8 classifiers is scored in every language, and every row is published, including the ones that fail.

0 characters of your text

A record, not a boolean

Every crossing produces a stamp: which detectors ran, which model revision each used, the resolved policy hash, and a hash of the text. Labels and scores, never the text. Optionally signed with a key you hold. The console above shows a real one.

10025050010002500ms per docpiiguard, on device$0 /1kgemini-3.1-flash-lite$0.053 /1kgpt-5.4-nano$0.032 /1kgpt-5.4-mini$0.159 /1kclaude-haiku-4-5$0.769 /1kgemini-3.5-flash$0.219 /1klower is better, log scale
Milliseconds per document over 30 documents, from artifacts/bench-piiguard.json, OpenNER training repo. The filled marker is the only row that runs on your own hardware, which is also the only row where no document leaves your network. On this set our model and three of the five hosted models all score an F1 of 1.0. A benchmark everyone passes is not measuring quality, it is measuring that the set is synthetic and in distribution. Read the latency and cost columns, not the accuracy ones, and treat a harder evaluation as outstanding work. The hosted rows answer "why not just call an API"; for same-class comparisons against Presidio and Llama Guard, see the benchmarks page.
01019enrohupldefrazbgcsdaelesetfigahritltlvmtnlptskslsvtr
26 of 26target languages are declared by at least one published model
Models declaring each language, from the Hugging Face repository tags. The border classifiers are verified against their training configs and carry per-language evaluations in their cards; for the rest, read this as coverage intent, not accuracy.
Table view
LanguageModels
en19
ro18
hu15
pl15
de14
fr14
az13
bg13
cs13
da13
el13
es13
et13
fi13
ga13
hr13
it13
lt13
lv13
mt13
nl13
pt13
sk13
sl13
sv13
tr13

08 · Latency

What a scan costs.

Every latency figure here describes 87 tokens of prose on 1 thread, the library default, so a scan does not take cores from the application it runs inside. Cost is close to linear at 0.241 ms per token. At the reference length the model-backed detectors measure between 13 and 146 ms p95 each, depending on the model.

075150225300ms168794128input length, tokens225 ms budget94-token window2nd pass +7.12 ms1 thread
Cost is linear inside a window and steps at each boundary: within one 94-token window the slope is 0.241 ms per token, and text past it needs a second forward pass. The 225 ms line is the per-scan budget, not a limit on input length: longer text costs more passes, each budgeted on its own.

09 · Weights

The models.

The models this library ships: the PII tagger and 10 classifiers. All on Hugging Face under Apache-2.0, each carrying its per-language evaluation in its card, and small enough to run on a CPU you already own. Browse our models.

piiguard

Token classification, PII spans

Eight entity types: card, date, email, IBAN, location, national ID, person, phone. Identifiers are generated checksum-valid in training, so an IBAN that fails mod-97 is not reported as an IBAN.

Parameters
278M
Languages
all 26
Artifacts
ONNX, safetensors

Apache-2.0

pii: { model: piiguard }

injection

Sequence classification

Backs the injection detector, for the text a retrieval tool fetched as much as the text a user typed.

Parameters
278M
Languages
all 26
Artifacts
ONNX, ONNX INT8

Apache-2.0

moderation

Multi-label classification, 12 hazard categories

Twelve hazard categories in one pass, from violent crime to election misinformation. The taxonomy has thirteen; child_safety is left out of training on purpose, because it cannot be generated synthetically and needs a vetted source. The capability Llama Guard and ShieldGemma provide, with weights this project ships.

Parameters
278M
Languages
all 26
Artifacts
ONNX, ONNX INT8

Apache-2.0

regulated-advice

Sequence classification

Backs the regulated_advice detector: the line between explaining an instrument and recommending it.

Parameters
278M
Languages
all 26
Artifacts
ONNX, ONNX INT8

Apache-2.0

10 · Scope

What it is not.

Not a gateway

It does not sit in front of your model, it does not hold your traffic, and it does not wrap your model call. If it stops working, your application still runs, it just stops producing evidence.

Not compliance

Nothing here is certified, and no library can be. Obligations under the EU AI Act sit with the provider or deployer of a system, not with a dependency it installs. What this produces is an auditable record of which checks ran and what they found, which supports the evidence requirements of a governance process you run yourself. What it does support: the disclosure detector records whether an AI disclosure was present in each output, in 26 languages, which is evidence a transparency process can file.

Not a security review

It reads text and reports on text. It knows nothing about your authentication, your tool permissions, or what your agent is allowed to do with the answer it got.

It does not pretend

A detector whose model is not published raises an error naming what is missing rather than returning an empty result, because a check that silently passes is worse than one that is absent.

Get started

Install it and read the record.

Weights are fetched once and cached. After that a scan needs no network, and nothing you scan reaches FlowX.AI.

Read the docsRead the benchmarks

Python 3.11 or newer. Apache-2.0. The package is flowx-border, the import is flowx_border, the GitHub org is flowx-ai, and the models live under flowxai on Hugging Face. Four spellings, one project.

Get started

Check what crosses.

Read the docsSee the numbers