Skip to content

Compliance PoC — PII redaction & data retention (#275)#279

Draft
illeatmyhat wants to merge 14 commits into
mainfrom
feat/275-pii-retention-poc
Draft

Compliance PoC — PII redaction & data retention (#275)#279
illeatmyhat wants to merge 14 commits into
mainfrom
feat/275-pii-retention-poc

Conversation

@illeatmyhat

@illeatmyhat illeatmyhat commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Proof of concept for #275 (Compliance — PII, Retention). For presenting results, not a merge candidate — hence draft. Every claim is backed by runnable demos and benchmarks; full walkthrough in DEMO.md.

What it does

Two enterprise asks from #275, in the altk_evolve package (PII redaction also mirrored into the evolve-lite plugin):

  1. PII never gets saved into memory — redaction at the single backend write choke-point (BaseEntityBackend.update_entities), covering every write path (CLI / MCP / API / Phoenix sync). Two pluggable backends: regex (CPEX) and semantic (IBM READI NER — with a pluggable extractor/model: spaCy / HF / Microsoft Presidio — defaulting to spaCy-English).
  2. Data retention policies — age- and unused-based flag-or-delete, plus session retention that cascade-deletes derived memories via provenance. Every decision records why (age / unused / cascade). CLI: evolve retention run --policy <file> [--apply] (dry-run by default).

See it in ~2 minutes

uv sync --extra pii
uv run python examples/pii_redaction_demo.py    # made-up PII → inert filler, before storage
uv run python examples/retention_demo.py        # flag / delete / provenance cascade, with "why"

A learned guideline with PII removed — the reusable knowledge is kept, only the PII goes:

IN : call account owner Dana Whitfield at 415-555-0199 or [email protected];
     verify the card on file (4111 1111 1111 1111) before a refund over $500.
OUT: call account owner [REDACTED] at [REDACTED] or [REDACTED];
     verify the card on file ([REDACTED]) before a refund over $500.

How well does it actually remove PII? (measured)

English — ai4privacy, 43,501 records — per-entity recall:

PII type regex (CPEX) semantic (READI, en_core_web_trf)
Overall recall 0.12 0.47
Precision 1.00 0.99
email / ip 1.00 / 1.00 1.00 / 0.95
phone / ssn / credit card 0.46 / 0.22 / 0.04 0.60 / 0.43 / 0.03
first / last / middle name 0.00 0.92 / 0.97 / 0.94
city / county / state 0.00 0.88 / 0.90 / 0.86

Japanese — ai4privacy openpii-1.5m, 2,000 records — regex and the English NER largely fail on Japanese-embedded PII; a language-matched model (one config line) fixes it:

PII type regex (CPEX) English NER (en_core_web_trf) Japanese NER (ja_core_news_trf)
Overall recall 0.03 0.15 0.92
Precision 0.99 0.99 0.93
email / phone / zipcode 0.09 / 0.13 / 0.00 0.09 / 0.47 / 0.46 0.94 / 0.99 / 1.00
credit card / ID number 0.02 / 0.46 0.03 / 0.46 1.00 / 1.00
given / surname 0.00 / 0.00 0.07 / 0.04 0.82 / 0.96

Takeaways: regex is high-precision on well-formed structured PII but blind to names and to non-Western formats; semantic NER adds names/locations (~4× recall on English) but is language-bound — a language-matched (or multilingual) model is essential for non-English. Structured PII (cards, phones) is the shared weak spot → a hybrid (regex + language-matched NER) is the production shape. (Benchmarking also caught a real byte-vs-char offset bug in the regex detector — now fixed.)

More

Full walkthrough, all commands, per-entity tables, retention scenarios & "why", limitations, and a reviewer guide: DEMO.md.

🤖 Generated with Claude Code

illeatmyhat and others added 11 commits June 29, 2026 10:46
Proof-of-concept for the two halves of the Compliance epic, implemented in
the altk_evolve package.

PII redaction (CPEX):
- altk_evolve/pii: pluggable PIIRedactor with a CPEX regex backend
  (cpex-pii-filter / PIIDetectorRust). 'semantic' mode is a documented seam
  (CPEX has no embedding detector). Lazy-imported and config-gated, so the
  [pii] extra is optional and disabled flows are untouched.
- Applied at the BaseEntityBackend.update_entities choke-point, so PII is
  scrubbed before it is persisted OR used as a conflict-search query — covers
  every write path (CLI, MCP, API, Phoenix sync). Wired in by EvolveClient
  from EvolveConfig.pii (NullRedactor default).

Data retention:
- altk_evolve/retention: RetentionPolicy/RetentionRule schema (YAML/JSON) and
  a backend-agnostic RetentionEngine. Supports age-based and unused-based
  flag/delete, plus session (trajectory) retention with provenance cascade
  delete of derived memories (metadata.source_task_id == trace_id).
- EvolveClient.record_access stamps metadata.last_accessed for the unused signal.
- CLI: `evolve retention run --policy <file> [--apply]` (dry run by default).

Tests: 20 unit tests (pure logic + real-CPEX end-to-end redaction at the write
choke-point + retention engine over an in-memory client). mypy + ruff clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
examples/pii_redaction_demo.py writes made-up memories full of fake PII (a
fictional persona name, email, phone, SSN, card, private IP) into a throwaway
Evolve namespace with redaction enabled, then reads them back to show every PII
item was replaced with inert filler at the write choke-point — while non-PII
text survives verbatim.

Also expose custom_patterns / whitelist_patterns on PIIConfig so callers can
catch entities the built-in regex flags miss (e.g. specific names, since the
CPEX regex backend has no NER). The demo uses custom_patterns for the persona.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds the package's PII redaction to the evolve-lite plugin world so memory is
scrubbed before it lands in .evolve/.

- plugin-source/lib/pii.py: stdlib-only redactor (the plugin lib forbids
  third-party deps since these scripts run in the host's Python). CPEX's
  cpex-pii-filter is imported lazily with a NullRedactor fallback + stderr
  warning when absent; 'semantic' mode is the same documented seam. Config via
  a `pii:` block in evolve.config.yaml.
- learn/save_entities.py: build a redactor from load_config() and scrub each
  entity before dedup/persist. No-op unless pii.enabled.
- Rendered to all variants via `just compile-plugins`; the shared lib ships to
  all four, the wired save path ships to claw-code (claude/codex/bob use native
  memory / exclude the learn skill).
- mypy: register the plugin 'pii' module in ignore_missing_imports (resolved at
  runtime via sys.path, matching the existing config/audit/entity_io entries).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…275)

- examples/retention_demo.py: seeds a namespace (stale guideline, fresh one,
  old session + derived memory), backdates ages on disk, then runs a policy to
  show unused-flag, age-delete, and provenance cascade-delete (dry-run + apply).
- DEMO.md: self-contained walkthrough for presenting the PoC — setup, both
  demo commands with captured output, real-usage config/CLI snippets, talking
  points, and honest limitations.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Swap the PII demo's mask text from [INERT] to [REDACTED] (also the PIIConfig
default) and refresh the captured output in DEMO.md to match.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
examples/pii_benchmark.py scores the CPEX redactor against a deterministic
labeled gold set (text with exact PII spans) and reports recall (the "did we
remove it" number), precision (over-redaction), F1, per-entity recall, and a
record-level leak rate. Accepts --data PATH for a real JSONL corpus
(ai4privacy / Presidio) instead of the built-in synthetic set.

Results on the built-in set: structured PII (email/phone/ssn/card/ip) recall
1.00 at precision 1.00; names/addresses leak (no NER) → 0.68 overall, lifting
to 0.91 with custom name patterns. DEMO.md gains a benchmark section.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
pii_benchmark.py gains --dataset to stream an ai4privacy-style Hugging Face set
(privacy_mask is already {value,start,end,label}) and a "supported-types recall"
metric (of the types CPEX targets, how much it removes) alongside overall
recall. New [bench] extra ships `datasets`.

Real-data finding (ai4privacy/pii-masking-200k, 1000 en records): email/IP hold
at 1.00, but phone/SSN drop to ~0.45 and credit_card to 0.03 on varied/non-Luhn
formats — vs 1.00 on the clean synthetic set. Overall recall 0.11 (CPEX targets
~5 of ~42 types), supported-types recall 0.71, precision 1.00. DEMO.md documents
this with the format-sensitivity caveat.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…emantic (#275)

Implements the semantic seam with IBM READI (readi-privacy): ReadiSemanticRedactor
wraps READIAnalyzer.detect (transformer spaCy NER + dictionary ensemble) and masks
the detected spans (READI only detects; no built-in redactor). Package import is
validated at construction so a missing [readi] extra degrades to a NullRedactor;
the ~450MB en_core_web_trf model loads lazily on first detect. New [readi] extra
(pins spacy-curated-transformers, which en_core_web_trf needs) + risk_assessment
mypy override + readi_detection_type config.

Benchmark gains --mode regex|semantic|both. On 150 ai4privacy records, semantic
lifts overall recall 0.12 -> 0.45 at precision 1.00 — names go 0.00 -> ~0.92
(firstname 0.93, lastname 0.92, middlename 1.00) plus locations/dates/urls — the
free-form PII regex can't catch. DEMO.md documents the regex-vs-semantic table.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
mode=semantic now selects among the extractors READI already ships instead of a
hand-built pipeline. New config: readi_extractor (default|spacy|hf|presidio),
readi_model, readi_language.

- default -> READI's DetectionType.PII (spaCy en_core_web_trf + identifiers), English.
- spacy/hf -> swap the NER model (e.g. ja_core_news_trf, or a multilingual
  pipeline('ner') HF model) via a single SpacyEntityExtractor/HFEntityExtractor.
- presidio -> Microsoft Presidio (MIT) via READI's PresidioEntityExtractor.

Each path passes ONE READI-provided extractor into DetectionType.CUSTOM; no
custom aggregator tuning. Verified default + presidio redact name/email/phone
end-to-end. DEMO.md documents the options.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
- pii_benchmark.py: add a WikiANN loader (tokens + BIO -> PER/ORG/LOC char spans;
  CJK-safe) and --dataset-format, --dataset-config, plus --readi-extractor /
  --readi-model / --readi-language passthrough so any extractor+model can be
  benchmarked. Enables scoring the semantic backend on languages ai4privacy
  lacks (e.g. Japanese via unimelb-nlp/wikiann config=ja).
- DEMO.md: add a "Reviewing this PoC (for agents)" section encoding the
  verification-first, reproduce-and-adversarially-spot-check approach used to
  build it.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
DEMO.md gains a full-sized results section:
- English ai4privacy 43,501 records (~65 min): regex 0.12 vs semantic 0.47
  overall recall at ~1.0 precision; names 0.00 -> ~0.9+.
- Japanese WikiANN 10,000 records: the English NER scores 0.00, a language-
  matched ja_core_news_trf scores 0.87 overall (person 0.93) — swapped in via
  the pluggable readi_extractor/readi_model config. Documents the WikiANN
  precision caveat and the hybrid takeaway.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffe03913-0f0f-428b-bbd4-f89f5019f29f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/275-pii-retention-poc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

illeatmyhat and others added 3 commits July 1, 2026 11:13
#275)

- DEMO.md: add a learned-guideline before/after (PII removed, reusable knowledge
  kept); a retention "why each memory was kept/flagged/removed" decision table;
  and a scenarios table (data minimization, right-to-be-forgotten cascade,
  memory hygiene, human-in-the-loop). Name the English model (en_core_web_trf)
  in the semantic columns.
- retention_demo.py: emit a per-memory decision + derived "why" (age / unused /
  provenance cascade), including kept memories.
- pii_benchmark.py: map openpii-1.5m labels (TELEPHONENUM->phone, SOCIALNUM->ssn)
  for the Japanese PII run.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
cpex-pii-filter is a Rust regex engine and returns BYTE offsets. On multi-byte
text (e.g. Japanese) those don't align with Python character indexing, so any
consumer using detect() spans (like the benchmark's span-overlap scorer) breaks
— which made regex look like ~0.01 recall on Japanese even though redaction
worked. Convert byte->char offsets in detect(); redact() is unchanged (mask()
runs Rust-side with byte-consistent offsets). ASCII is a no-op. Adds a
multi-byte regression test.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the WikiANN Japanese table with a real-PII 3-way on
ai4privacy/pii-masking-openpii-1.5m (ja, 2000 records): regex 0.03 vs English
NER 0.15 vs Japanese ja_core_news_trf 0.92 overall recall, all at ~0.99
precision (JA 0.93). Regex/English fail on Japanese-embedded PII; the
language-matched model dominates. Notes the byte->char offset fix caught here.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant