Reversible PII pseudonymization for the full AI agentic loop.
Project description
Legend
PII pseudonymization for the full agentic loop.
Agent pipelines expose PII at every step: in user prompts, tool call arguments, tool results, and final responses. Legend intercepts at all four points, replacing PII with realistic pseudonyms before it reaches external APIs or leaves your infrastructure, and restoring the original values exactly in the final response.
Design
The pseudonymization strategy is grounded in recent research showing that realistic substitutes preserve utility significantly better than suppression or redaction across multi-turn interactions. Legend extends the single-boundary pseudonymization approach of LOPSIDED (Serenari & Lee, 2025) to the full four-boundary agentic loop, addressing the leakage surface that existing tools leave uncovered.
Getting Started
Requirements
- Python 3.13+
- No external API keys. Legend runs entirely on your infrastructure.
Install
pip install legend
legend download-models
The download-models command fetches the spaCy model weights to ~/.legend/models/. Run this once before first use. If you skip it, Legend downloads the model automatically on first run.
For custom model locations (containerized or edge deployments), set LEGEND_MODEL_PATH before running:
LEGEND_MODEL_PATH=/mnt/storage/legend
legend download-models
Use
import asyncio
from legend import DetectionPipeline, ReplacementEngine, PseudonymContext
pipeline = DetectionPipeline(entities=["PERSON", "EMAIL_ADDRESS", "US_SSN"])
engine = ReplacementEngine()
async def run():
async with PseudonymContext(pipeline=pipeline, engine=engine) as ctx:
# Boundary A: user prompt entering the agent
prompt = await ctx.sanitize_prompt(
"Process a refund for John Smith (john.smith@acme.com), SSN 123-45-6789."
)
# "Process a refund for Michael Torres (fake3847@example.com), SSN 987-65-4320."
# Boundary B: tool call arguments leaving toward an external API
args = await ctx.sanitize_tool_args({"email": "john.smith@acme.com", "amount": 150})
# {"email": "fake3847@example.com", "amount": 150}
# Boundary C: tool results entering the agent context
result = await ctx.sanitize_tool_result(tool_response)
# Boundary D: real values restored in the final response
response = await ctx.revert(agent_response)
asyncio.run(run())
DetectionPipeline and ReplacementEngine are initialized once and reused across sessions. A fresh session-scoped entity map is created for each PseudonymContext block and destroyed on exit.
The Four Boundaries
Agent pipelines expose PII at four distinct points. Legend intercepts all of them.
A — User prompt in. PII in the user's message is replaced before it enters the agent's context window.
B — Tool call arguments out. PII that the agent constructs into tool call arguments is replaced before it reaches an external API, database, or service.
C — Tool result in. PII returned by a tool (fetched from a database, retrieved from an external system) is replaced before it enters the agent's context.
D — Response out. Pseudonyms are reverted to their original values in the final response before the user sees it.
Most tools protect only the prompt. Boundary B and Boundary C are where agent-specific leakage happens and where Legend is different.
Properties
Reversible. Real values are restored exactly at Boundary D. The agent's response reads naturally, with original names, addresses, and identifiers intact.
Realistic substitutes. A name becomes a plausible name. An IBAN becomes a valid-format IBAN with the correct country code and check digit. Agents reason correctly over pseudonymized data because the substitutes are semantically coherent.
Session-scoped. The entity map lives only for the duration of a PseudonymContext block. No state persists between sessions. No database required.
On-premise. Detection runs via YARA rules and a local spaCy model. Nothing leaves your infrastructure during processing.
Framework-agnostic. Works with LangGraph, LangChain, CrewAI, or any agent framework. PseudonymContext wraps your existing agent calls with no framework-specific integration required.
Custom Rules
Legend ships with detection rules covering the full Presidio entity list. For domain-specific PII formats (internal account IDs, regional document numbers, custom identifiers), generate additional YARA rules with yaramint and pass the rules directory to DetectionPipeline.
Roadmap
- Framework adapters. One-line integration for LangChain, LangGraph, and CrewAI via
wrap_langchain_tools()and equivalents. No tool-level boilerplate. - Transparent proxy. An OpenAI-compatible HTTP proxy that intercepts MCP tool calls at the protocol level. Works with any agent framework, zero code changes required.
- OpenTelemetry integration. Optional
legend[otel]extra that routes pseudonymized observability events into any OTel-compatible backend. Each session maps to a trace, each boundary to a child span.
References
Xiao, Yunze, Wenkai Li, Xiaoyuan Wu, Ningshan Ma, Yueqi Song, and Weihao Xuan. Say Something Else: Rethinking Contextual Privacy as Information Sufficiency. arXiv:2604.06409. Preprint, arXiv, April 7, 2026. https://doi.org/10.48550/arXiv.2604.06409.
Serenari, Jayden, and Stephen Lee. Semantically-Aware LLM Agent to Enhance Privacy in Conversational AI Services. arXiv:2510.27016. Preprint, arXiv, October 30, 2025. https://doi.org/10.48550/arXiv.2510.27016.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file legend_pii-0.1.0.tar.gz.
File metadata
- Download URL: legend_pii-0.1.0.tar.gz
- Upload date:
- Size: 114.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74b2fae9c37b931666481fa7aaa180161fef5143ffe378cb9450d51f44053021
|
|
| MD5 |
8954a7965c34c8ceb4c5d3eb44c250e1
|
|
| BLAKE2b-256 |
1b509f9b094186855c027524676ed445d84fe17692b83359fbabb8b759b00deb
|
Provenance
The following attestation bundles were made for legend_pii-0.1.0.tar.gz:
Publisher:
release.yml on deconvolute-labs/legend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
legend_pii-0.1.0.tar.gz -
Subject digest:
74b2fae9c37b931666481fa7aaa180161fef5143ffe378cb9450d51f44053021 - Sigstore transparency entry: 1668836100
- Sigstore integration time:
-
Permalink:
deconvolute-labs/legend@3d709989eb12959452d4fedb2a865db5cf1525bc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/deconvolute-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3d709989eb12959452d4fedb2a865db5cf1525bc -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file legend_pii-0.1.0-py3-none-any.whl.
File metadata
- Download URL: legend_pii-0.1.0-py3-none-any.whl
- Upload date:
- Size: 41.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06ae096dd138b96c18b9fa61f9ff5f8831e082a3e1aba2ac4ecb0f9996992275
|
|
| MD5 |
667442d8797c64e95b4827df4dd49c21
|
|
| BLAKE2b-256 |
90f48578779972a1cad5c95dee8fd53143448986dd6aafd101db4ce260fd9841
|
Provenance
The following attestation bundles were made for legend_pii-0.1.0-py3-none-any.whl:
Publisher:
release.yml on deconvolute-labs/legend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
legend_pii-0.1.0-py3-none-any.whl -
Subject digest:
06ae096dd138b96c18b9fa61f9ff5f8831e082a3e1aba2ac4ecb0f9996992275 - Sigstore transparency entry: 1668836220
- Sigstore integration time:
-
Permalink:
deconvolute-labs/legend@3d709989eb12959452d4fedb2a865db5cf1525bc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/deconvolute-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3d709989eb12959452d4fedb2a865db5cf1525bc -
Trigger Event:
workflow_dispatch
-
Statement type: