A reference implementation for a privacy-first AI agent. It uses the Mask SDK to protect PII before it reaches cloud LLMs, while keeping tools (like Gmail) 100% functional via Transparent Detokenization.
Standard agents leak PII into cloud context windows. Redaction breaks tools. Mask solves this via Just-In-Time (JIT) Encryption:
- Intercept: Input is scanned locally; PII is replaced with cryptographic tokens.
- Reason: The LLM reasons over the tokenized prompt (zero-knowledge).
- Execute: Authorized tools (e.g.,
send_email) automatically decrypt tokens via the@secure_toolhook to run with real data.
| Component | Responsibility |
|---|---|
agent.py |
Orchestration, Mask initialization, and Tool definitions. |
gmail_client.py |
Wrapper for OAuth 2.0 and Gmail API execution. |
.env |
API Keys (Model-agnostic). |
- Install uv
- Configure Gmail API: Enable the API in GCP Console, set up an OAuth Desktop app, and save the JSON as
credentials.json.
uv sync
uv run agent.pyNote: Run python -m spacy download en_core_web_sm and es_core_news_sm if needed for the local engine.
Run the agent to witness token-in, tool-out privacy:
- Authorize: A browser opens on first run to link your Gmail.
- Interact: Provide a prompt with PII.
- Audit: Watch the logs to see tokens sent to the LLM and real data securely passed to the Gmail API.
If you observe the agent's raw LangGraph trace during execution, you will see exactly how Mask intercepts and encrypts data in-flight. Below is a condensed snippet of the raw output demonstrating the LLM's blindness to the PII:
[llm/start] [chain:LangGraph > chain:agent > chain:RunnableSequence > llm:ChatOpenAI] Entering LLM run with input:
{
"prompts": [
"...Human: Encuentra el correo de [TKN-b568c6c6] y envíale su nueva contraseña 'Secret123'. \nMenciona que mi número de DNI para la validación es [TKN-483155ce]."
]
}
...
[tool/start] [chain:LangGraph > chain:tools > tool:search_contacts] Entering Tool run with input:
"{'name': '[TKN-b568c6c6]'}"
[tool/end] [chain:LangGraph > chain:tools > tool:search_contacts] [156ms] Exiting Tool run with output:
"content='tkn-686c625c@email.com' name='search_contacts' tool_call_id='call_a6THF2EMn4N279bvBNkYq4Dv'"
...
[tool/start] [chain:LangGraph > chain:tools > tool:send_email] Entering Tool run with input:
"{'to_email': 'tkn-686c625c@email.com', 'subject': 'Nueva Contraseña y Validación de DNI', 'body': "Hola,\n\nTu nueva contraseña es 'Secret123'. Por favor, utiliza mi número de DNI [TKN-483155ce] para la validación.\n\nSaludos."}"
[Gmail API] Email successfully sent to 'millingtonsully@gmail.com' (Message ID: 19d56bfc7e282eba)
[tool/end] [chain:LangGraph > chain:tools > tool:send_email] [2.43s] Exiting Tool run with output:
"content='Successfully sent email to tkn-c23295f9@email.com' name='send_email' tool_call_id='call_bKiihx1yGbffKCcdpRR31umG'"
The agent uses LangChain. To switch providers (e.g., to Claude), swap the LLM instantiation in agent.py:
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-3-5-sonnet")- Service Accounts: Replace user OAuth with Google Cloud Service Accounts for server environments.
- Key Management: Store
MASK_ENCRYPTION_KEYin a secure vault (e.g., AWS KMS). - Audits: Redirect
MaskCallbackHandleroutput to a centralized logging system.
Apache License, Version 2.0. Copyright (c) 2026 Mask AI Solutions.