Extending¶
wardcat is built around registries so you can add behaviour without changing the library (Open/Closed).
Custom actions¶
Actions map a detected span to its replacement (or None to keep the text).
Register your own — an external vault, encrypt, format-preserving masking — and
use it like any built-in:
from wardcat import Wardcat, register_action
# ctx carries the salt; span has .entity_type, .text, .start, .end
register_action("vault", lambda span, ctx: f"<{span.entity_type}:{vault.put(span.text)}>")
guard = Wardcat(salt="s").add_entity("EMAIL", "vault")
Detection and anonymization are separate stages: DetectionEngine finds spans, a
standalone Anonymizer applies the actions — so you can reuse either independently.
Registering an action under a built-in name (hash, tokenize, …) overrides that
built-in for the whole process.
ctx.tokens is a per-scan TokenAllocator: reuse it (as the built-in tokenize
action does) if your action needs stable, unique placeholders that
restore() can reverse.
LLM backends are fixed¶
Unlike actions, LLM backends are not user-extensible. wardcat ships four —
ollama, openai_compatible, vllm, transformers — selected via the
Backend enum. A third-party backend would sit outside wardcat's safety checks
(the plaintext-HTTP-to-remote guard, PII handling), which is exactly where
sensitive data would leak, so point a built-in at your endpoint instead:
openai_compatible covers most OpenAI-style gateways (LM Studio, LocalAI,
LiteLLM, and hosted OpenAI-compatible APIs).
Custom detectors¶
Every layer implements BaseDetector — the engine talks to detectors only
through it and never imports a concrete one. To add a whole new detector (say, a
different token-classifier model), implement detect() returning DetectedSpans;
give model-based spans a confidence below 1.0 so a deterministic regex span
still wins overlaps.
See the detectors & backends reference.