PHI De-identifier
De-identify clinical / health text to the HIPAA Safe Harbor standard (or a
Limited Data Set) using the bundled, offline scripts/deidentify.py. Every
run produces two things: the redacted text and an audit manifest listing
what was removed, by category, plus anything that needs human review.
When to use
- "De-identify / anonymise / scrub / redact this note (or file / folder)."
- "Remove patient identifiers before I share this."
- "Make this Safe Harbor compliant" or "produce a limited data set."
- Preparing PHI-free samples for demos, test fixtures, analytics, or model prompts.
Do not use it to answer clinical questions or to claim legal certification —
it is a mechanical redactor plus an audit trail for a human to verify.
How to run it
Always call the bundled script; never improvise redaction logic.
# Safe Harbor (default): writes NOTE.deid.txt + NOTE.manifest.json next to input
python scripts/deidentify.py NOTE.txt
# Limited Data Set: retains dates and city/state/ZIP, strips direct identifiers
python scripts/deidentify.py NOTE.txt --mode limited
# Inline text, machine-readable result to stdout
python scripts/deidentify.py --text "John Doe, SSN 123-45-6789" --json
# HYBRID name recall: pass the person names YOU (the agent) spotted in free prose
python scripts/deidentify.py --text "<text>" --names "Rafael Alcaraz,Ngozi Okafor" --json
# STRUCTURED / JSON input: redact by FIELD NAME (MRN, City, Employer, DOB, ...)
# Use this whenever the input is a JSON object or a key/value record — NOT free text.
python scripts/deidentify.py intake_record.json --format record --json
# ALSO emit a reversible token->value crosswalk (SENSITIVE — re-identifying)
python scripts/deidentify.py NOTE.txt --map crosswalk.json
Steps:
- Identify the input (a file path, a folder, or inline text) and the target
standard: Safe Harbor (default) or Limited Data Set (
--mode limited).
- Pick the right mode for the shape of the input. If the input is a
structured record — a JSON object, or clear
Field: value key/value pairs
(e.g. an intake record, an API payload, an exported row) — run it with
--format record. Record mode maps each field by its NAME (MRN, City,
Employer, DOB, SSN, …) and redacts deterministically; free-text mode will
MISS quoted/structured identifiers like "MRN": "33108876" because there is
no adjacent label to anchor on. Use the default (free-text) mode only for
prose. Still run the name-recall pass on any free-text field VALUES (e.g. a
ChiefComplaint or Notes field that contains a person's name).
- Name-recall pass (do this every time on free prose). Before running, read
the text yourself and list every PERSON name and PLACE (city / facility) you
can identify — including unusual or non-English names that a regex or the
bundled lexicon would miss (e.g. "Rafael Alcaraz", "Okafor"). This is the
model/agent contribution to the hybrid; you are the recall booster.
- Run
scripts/deidentify.py, passing the names from step 3 via
--names "Name One,Name Two" (or a --names-file with {"names":[...], "places":[...],"orgs":[...]}). The script deterministically redacts every
occurrence, tokenizes consistently, runs its guard, and writes the manifest.
The script — not your paraphrase — is the auditable system of record.
- Return the redacted text exactly as produced, and summarise the
manifest: how many identifiers of each category were removed.
- Surface
needs_human_review. Names caught heuristically, and any unknown
capitalised text flagged for review, are listed there — tell the user these
must be verified before release, and that bare names in free prose may still
be missed.
- Only write the
--map crosswalk when the user explicitly needs to
re-identify later; warn that it is sensitive and must be stored securely,
separately from the de-identified output.
Use as a safety net before an outbound action
Agents often carry actions that emit content — Send Email, Create Record,
Post Message, Export. When an agent maker points one of those actions at this
skill (e.g. an instruction like "before Send Email, de-identify the content
using phi-deidentifier"), apply this skill as a gate:
- Take the exact text about to leave the agent (e.g. the email Subject + Body,
or the field values of the record).
- De-identify it with the steps above (including the name-recall pass).
- Populate the action's inputs from the redacted result only — never from
the original text.
- If
needs_human_review is non-empty, do not fire the action; show the
redacted draft plus the review items and ask the user to confirm first.
- On success, tell the user the content was de-identified and how many
identifiers were removed. Never place the
--map crosswalk in the outbound
content or in chat.
The maker only needs to reference this skill by name in the action's instruction;
the operational steps above live here, in the skill, so they do not have to be
re-written per agent.
What it removes (HIPAA Safe Harbor, 18 identifiers)
Handled deterministically by regex (high confidence): names in labelled
fields and after titles, street addresses, city + state, geographic ZIP
(truncated to 3 digits; restricted prefixes zeroed), all date elements finer
than year, ages 90+ (aggregated), phone, fax, email, URL, IP address, SSN, MRN,
health-plan / account / member numbers, certificate / license / DEA / NPI
numbers, device serial numbers, VIN.
Handled best-effort and flagged for review: person names in free prose.
Names are caught by (a) title/label patterns ("Patient: …", "Dr. …") and (b) a
bundled offline name lexicon (assets/name_lexicon.txt) that catches many
bare names with no title or label — all fully local, no network or extra
packages, so it works in locked-down agent sandboxes (e.g. the Copilot Studio
Python container where you cannot pip install). Names not in the lexicon can
still be missed and word-like names may be over-redacted, so human review of
free text is required; extend the lexicon for your population. (If a runtime
already ships spaCy it is used automatically as a bonus, but installation is
never assumed.) City/state written without a comma or trailing ZIP may also be
missed. Full-face photos and biometric identifiers are out of scope — this
skill processes text only; call that out if the source contains images.
Transformations (so downstream data stays useful)
- Consistent pseudonyms: each distinct value maps to a stable token, e.g.
every occurrence of the same MRN becomes
[MRN-1], so linkage is preserved
without exposing the value.
- Dates → year only:
03/15/1985 → [DATE:1985] (Safe Harbor keeps year).
- Ages: ≤89 retained; 90+ →
[AGE:90+].
- ZIP → first 3 digits:
98104 → [ZIP:981XX]; HHS-restricted low-population
prefixes → [ZIP:000].
- Limited Data Set mode keeps dates and city/state/ZIP intact while still
removing names, contacts, SSN, MRN, and account numbers.
The audit manifest
NOTE.manifest.json records the mode, a per-category count summary, one entry
per token with a salted hash of the original value (never the plaintext),
the needs_human_review list, and explanatory notes. Use it as the evidence
trail for a reviewer — it answers "what did the tool remove, and what still
needs eyes?" without itself containing PHI.
Guardrails
- Never claim compliance. Report what was removed; a qualified human makes
the Safe Harbor / expert-determination call.
- Never invent or "restore" content. Only redact.
- Do not paste the crosswalk into chat or co-locate it with the output; it
re-identifies the data.
- Always relay
needs_human_review and remind the user to review free text —
regex cannot guarantee every bare name or unusual identifier is caught.
- Text only: if the source has images/scans, say so and recommend a separate
image-redaction step.
References
references/hipaa-safe-harbor.md — the 18 identifiers, ZIP/date/age rules,
and Safe Harbor vs Limited Data Set, as implemented here.
Run this — do not improvise
This capability's deterministic implementation is a RAPP single-file agent, linked beside this file as phi_deidentifier_agent.py and embedded as the fenced Python below (sha256 9dd1a9b5d2e52b47…; a byte-exact copy is also vaulted in the capsule comment at the end of this file). On a host with sandbox execution, run the linked file directly — if it is missing, write the fence contents verbatim to phi_deidentifier_agent.py first:
python3 phi_deidentifier_agent.py '{"key": "value"}' # arguments as one JSON object
echo '{"key": "value"}' | python3 phi_deidentifier_agent.py # or on stdin
python3 phi_deidentifier_agent.py --tool # emit the JSON tool contract
Treat stdout as a tool result. If it reports missing or unresolved inputs, stop and collect them. If it returns steps, execute those steps in order exactly as returned; if it returns instructions, follow them with the supplied inputs. Otherwise use the result verbatim. Do not invent behavior beyond that output. On a host without code execution, treat the Parameters schema and the code below as the exact specification and never paraphrase a step. Never edit inside the generated markers; a converter-equipped host can instead restore the original file checksum-verified with the installed rapp-agent-converter/scripts/toast.py convert SKILL.md --to agent.
"""PhiDeidentifier -- Use this skill whenever the user asks to de-identify, redact, anonymise, scrub, or remove PHI/PII from clinical text, notes, transcripts, or documents before sharing, exporting, or using them for analytics, testing, or demos — e.g. "de-identify this note", "strip patient info", "make this HIPAA Safe Harbor compliant", "produce a limited data set". It removes the 18 HIPAA Safe Harbor identifiers and emits an audit manifest of what was removed. Run the bundled scripts/deidentify.py — do NOT hand-write redaction regexes yourself. This skill prepares de-identified text and an audit trail for human review; it does NOT certify legal compliance or make a formal expert determination.
Generated by the rapp skill from phi-deidentifier. The RCI capsule at the bottom of this file carries the full original; `toast.py convert` restores it byte-exact."""
import json
import re
import sys
try:
from agents.basic_agent import BasicAgent
except ImportError: # running OUTSIDE a brainstem -- stay executable anyway.
class BasicAgent: # noqa: D101 - minimal stand-in, same contract
def __init__(self, name=None, metadata=None):
if name:
self.name = name
if metadata:
self.metadata = metadata
def perform(self, **kwargs):
return "Not implemented."
def system_context(self):
return None
def to_tool(self):
return {"type": "function", "function": {
"name": self.name,
"description": self.metadata.get("description", ""),
"parameters": self.metadata.get("parameters", {})}}
# The procedural layer, verbatim from the source capability.
INSTRUCTIONS = '# PHI De-identifier\r\n\r\nDe-identify clinical / health text to the **HIPAA Safe Harbor** standard (or a\r\n**Limited Data Set**) using the bundled, offline `scripts/deidentify.py`. Every\r\nrun produces two things: the **redacted text** and an **audit manifest** listing\r\nwhat was removed, by category, plus anything that needs human review.\r\n\r\n## When to use\r\n\r\n- "De-identify / anonymise / scrub / redact this note (or file / folder)."\r\n- "Remove patient identifiers before I share this."\r\n- "Make this Safe Harbor compliant" or "produce a limited data set."\r\n- Preparing PHI-free samples for demos, test fixtures, analytics, or model prompts.\r\n\r\nDo **not** use it to answer clinical questions or to claim legal certification —\r\nit is a mechanical redactor plus an audit trail for a human to verify.\r\n\r\n## How to run it\r\n\r\nAlways call the bundled script; never improvise redaction logic.\r\n\r\n```bash\r\n# Safe Harbor (default): writes NOTE.deid.txt + NOTE.manifest.json next to input\r\npython scripts/deidentify.py NOTE.txt\r\n\r\n# Limited Data Set: retains dates and city/state/ZIP, strips direct identifiers\r\npython scripts/deidentify.py NOTE.txt --mode limited\r\n\r\n# Inline text, machine-readable result to stdout\r\npython scripts/deidentify.py --text "John Doe, SSN 123-45-6789" --json\r\n\r\n# HYBRID name recall: pass the person names YOU (the agent) spotted in free prose\r\npython scripts/deidentify.py --text "<text>" --names "Rafael Alcaraz,Ngozi Okafor" --json\r\n\r\n# STRUCTURED / JSON input: redact by FIELD NAME (MRN, City, Employer, DOB, ...)\r\n# Use this whenever the input is a JSON object or a key/value record — NOT free text.\r\npython scripts/deidentify.py intake_record.json --format record --json\r\n\r\n# ALSO emit a reversible token->value crosswalk (SENSITIVE — re-identifying)\r\npython scripts/deidentify.py NOTE.txt --map crosswalk.json\r\n```\r\n\r\nSteps:\r\n\r\n1. Identify the input (a file path, a folder, or inline text) and the target\r\n standard: **Safe Harbor** (default) or **Limited Data Set** (`--mode limited`).\r\n2. **Pick the right mode for the shape of the input.** If the input is a\r\n **structured record** — a JSON object, or clear `Field: value` key/value pairs\r\n (e.g. an intake record, an API payload, an exported row) — run it with\r\n `--format record`. Record mode maps each field by its NAME (MRN, City,\r\n Employer, DOB, SSN, …) and redacts deterministically; free-text mode will\r\n MISS quoted/structured identifiers like `"MRN": "33108876"` because there is\r\n no adjacent label to anchor on. Use the default (free-text) mode only for\r\n prose. Still run the name-recall pass on any free-text field VALUES (e.g. a\r\n `ChiefComplaint` or `Notes` field that contains a person's name).\r\n3. **Name-recall pass (do this every time on free prose).** Before running, read\r\n the text yourself and list every PERSON name and PLACE (city / facility) you\r\n can identify — including unusual or non-English names that a regex or the\r\n bundled lexicon would miss (e.g. "Rafael Alcaraz", "Okafor"). This is the\r\n model/agent contribution to the hybrid; you are the recall booster.\r\n4. Run `scripts/deidentify.py`, passing the names from step 3 via\r\n `--names "Name One,Name Two"` (or a `--names-file` with `{"names":[...],\r\n "places":[...],"orgs":[...]}`). The script deterministically redacts every\r\n occurrence, tokenizes consistently, runs its guard, and writes the manifest.\r\n The script — not your paraphrase — is the auditable system of record.\r\n5. Return the **redacted text** exactly as produced, and summarise the\r\n **manifest**: how many identifiers of each category were removed.\r\n6. **Surface `needs_human_review`.** Names caught heuristically, and any unknown\r\n capitalised text flagged for review, are listed there — tell the user these\r\n must be verified before release, and that bare names in free prose may still\r\n be missed.\r\n7. Only write the `--map` crosswalk when the user explicitly needs to\r\n re-identify later; warn that it is sensitive and must be stored securely,\r\n separately from the de-identified output.\r\n\r\n## Use as a safety net before an outbound action\r\n\r\nAgents often carry actions that emit content — **Send Email, Create Record,\r\nPost Message, Export**. When an agent maker points one of those actions at this\r\nskill (e.g. an instruction like *"before Send Email, de-identify the content\r\nusing phi-deidentifier"*), apply this skill as a gate:\r\n\r\n1. Take the exact text about to leave the agent (e.g. the email Subject + Body,\r\n or the field values of the record).\r\n2. De-identify it with the steps above (including the name-recall pass).\r\n3. Populate the action's inputs from the **redacted** result only — never from\r\n the original text.\r\n4. If `needs_human_review` is non-empty, **do not fire the action**; show the\r\n redacted draft plus the review items and ask the user to confirm first.\r\n5. On success, tell the user the content was de-identified and how many\r\n identifiers were removed. Never place the `--map` crosswalk in the outbound\r\n content or in chat.\r\n\r\nThe maker only needs to reference this skill by name in the action's instruction;\r\nthe operational steps above live here, in the skill, so they do not have to be\r\nre-written per agent.\r\n\r\n## What it removes (HIPAA Safe Harbor, 18 identifiers)\r\n\r\nHandled **deterministically by regex** (high confidence): names in labelled\r\nfields and after titles, street addresses, city + state, geographic ZIP\r\n(truncated to 3 digits; restricted prefixes zeroed), all date elements finer\r\nthan year, ages 90+ (aggregated), phone, fax, email, URL, IP address, SSN, MRN,\r\nhealth-plan / account / member numbers, certificate / license / DEA / NPI\r\nnumbers, device serial numbers, VIN.\r\n\r\nHandled **best-effort** and flagged for review: person names in free prose.\r\nNames are caught by (a) title/label patterns ("Patient: …", "Dr. …") and (b) a\r\nbundled offline **name lexicon** (`assets/name_lexicon.txt`) that catches many\r\nbare names with no title or label — all fully local, no network or extra\r\npackages, so it works in locked-down agent sandboxes (e.g. the Copilot Studio\r\nPython container where you cannot `pip install`). Names not in the lexicon can\r\nstill be missed and word-like names may be over-redacted, so **human review of\r\nfree text is required**; extend the lexicon for your population. (If a runtime\r\nalready ships spaCy it is used automatically as a bonus, but installation is\r\nnever assumed.) City/state written without a comma or trailing ZIP may also be\r\nmissed. **Full-face photos and biometric identifiers are out of scope** — this\r\nskill processes text only; call that out if the source contains images.\r\n\r\n## Transformations (so downstream data stays useful)\r\n\r\n- **Consistent pseudonyms:** each distinct value maps to a stable token, e.g.\r\n every occurrence of the same MRN becomes `[MRN-1]`, so linkage is preserved\r\n without exposing the value.\r\n- **Dates → year only:** `03/15/1985` → `[DATE:1985]` (Safe Harbor keeps year).\r\n- **Ages:** ≤89 retained; 90+ → `[AGE:90+]`.\r\n- **ZIP → first 3 digits:** `98104` → `[ZIP:981XX]`; HHS-restricted low-population\r\n prefixes → `[ZIP:000]`.\r\n- **Limited Data Set mode** keeps dates and city/state/ZIP intact while still\r\n removing names, contacts, SSN, MRN, and account numbers.\r\n\r\n## The audit manifest\r\n\r\n`NOTE.manifest.json` records the mode, a per-category count summary, one entry\r\nper token with a **salted hash** of the original value (never the plaintext),\r\nthe `needs_human_review` list, and explanatory notes. Use it as the evidence\r\ntrail for a reviewer — it answers "what did the tool remove, and what still\r\nneeds eyes?" without itself containing PHI.\r\n\r\n## Guardrails\r\n\r\n- **Never claim compliance.** Report what was removed; a qualified human makes\r\n the Safe Harbor / expert-determination call.\r\n- **Never invent or "restore" content.** Only redact.\r\n- **Do not paste the crosswalk into chat** or co-locate it with the output; it\r\n re-identifies the data.\r\n- **Always relay `needs_human_review`** and remind the user to review free text —\r\n regex cannot guarantee every bare name or unusual identifier is caught.\r\n- Text only: if the source has images/scans, say so and recommend a separate\r\n image-redaction step.\r\n\r\n## References\r\n\r\n- `references/hipaa-safe-harbor.md` — the 18 identifiers, ZIP/date/age rules,\r\n and Safe Harbor vs Limited Data Set, as implemented here.'
# Ordered commands lifted verbatim from the capability's own documentation.
STEPS = []
class PhiDeidentifierAgent(BasicAgent):
def __init__(self):
self.name = 'PhiDeidentifier'
self.metadata = {
"name": "PhiDeidentifier",
"description": "Use this skill whenever the user asks to de-identify, redact, anonymise, scrub, or remove PHI/PII from clinical text, notes, transcripts, or documents before sharing, exporting, or using them for analytics, testing, or demos \u2014 e.g. \"de-identify this note\", \"strip patient info\", \"make this HIPAA Safe Harbor compliant\", \"produce a limited data set\". It removes the 18 HIPAA Safe Harbor identifiers and emits an audit manifest of what was removed. Run the bundled scripts/deidentify.py \u2014 do NOT hand-write redaction regexes yourself. This skill prepares de-identified text and an audit trail for human review; it does NOT certify legal compliance or make a formal expert determination.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
super().__init__(name=self.name, metadata=self.metadata)
def perform(self, **kwargs): # toaster:generated-perform
return json.dumps({"status": "ok", "instructions": INSTRUCTIONS,
"inputs": kwargs,
"note": "Prose-only capability: follow INSTRUCTIONS "
"with the given inputs."}, indent=2)
if __name__ == "__main__":
# echo '{"arg": "value"}' | python3 phi_deidentifier_agent.py
# python3 phi_deidentifier_agent.py '{"arg": "value"}'
# python3 phi_deidentifier_agent.py --tool # emit the JSON tool contract
_a = sys.argv[1:]
if _a and _a[0] == "--tool":
print(json.dumps(PhiDeidentifierAgent().to_tool(), indent=2))
else:
_raw = _a[0] if _a else (sys.stdin.read().strip() or "{}")
print(PhiDeidentifierAgent().perform(**json.loads(_raw)))
# rci-capsule:v1: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
1---2name: phi-deidentifier3description: Use this skill whenever the user asks to de-identify, redact, anonymise, scrub, or remove PHI/PII from clinical text, notes, transcripts, or documents before sharing, exporting, or using them for analytics, testing, or demos — e.g. "de-identify this note", "strip patient info", "make this HIPAA Safe Harbor compliant", "produce a limited data set". It removes the 18 HIPAA Safe Harbor identifiers and emits an audit manifest of what was removed. Run the bundled scripts/deidentify.py — do NOT hand-write redaction regexes yourself. This skill prepares de-identified text and an audit trail for human review; it does NOT certify legal compliance or make a formal expert determination.4---56# PHI De-identifier78De-identify clinical / health text to the **HIPAA Safe Harbor** standard (or a9**Limited Data Set**) using the bundled, offline `scripts/deidentify.py`. Every10run produces two things: the **redacted text** and an **audit manifest** listing11what was removed, by category, plus anything that needs human review.1213## When to use1415- "De-identify / anonymise / scrub / redact this note (or file / folder)."16- "Remove patient identifiers before I share this."17- "Make this Safe Harbor compliant" or "produce a limited data set."18- Preparing PHI-free samples for demos, test fixtures, analytics, or model prompts.1920Do **not** use it to answer clinical questions or to claim legal certification —21it is a mechanical redactor plus an audit trail for a human to verify.2223## How to run it2425Always call the bundled script; never improvise redaction logic.2627```bash28# Safe Harbor (default): writes NOTE.deid.txt + NOTE.manifest.json next to input29python scripts/deidentify.py NOTE.txt3031# Limited Data Set: retains dates and city/state/ZIP, strips direct identifiers32python scripts/deidentify.py NOTE.txt --mode limited3334# Inline text, machine-readable result to stdout35python scripts/deidentify.py --text "John Doe, SSN 123-45-6789" --json3637# HYBRID name recall: pass the person names YOU (the agent) spotted in free prose38python scripts/deidentify.py --text "<text>" --names "Rafael Alcaraz,Ngozi Okafor" --json3940# STRUCTURED / JSON input: redact by FIELD NAME (MRN, City, Employer, DOB, ...)41# Use this whenever the input is a JSON object or a key/value record — NOT free text.42python scripts/deidentify.py intake_record.json --format record --json4344# ALSO emit a reversible token->value crosswalk (SENSITIVE — re-identifying)45python scripts/deidentify.py NOTE.txt --map crosswalk.json46```4748Steps:49501. Identify the input (a file path, a folder, or inline text) and the target51 standard: **Safe Harbor** (default) or **Limited Data Set** (`--mode limited`).522. **Pick the right mode for the shape of the input.** If the input is a53 **structured record** — a JSON object, or clear `Field: value` key/value pairs54 (e.g. an intake record, an API payload, an exported row) — run it with55 `--format record`. Record mode maps each field by its NAME (MRN, City,56 Employer, DOB, SSN, …) and redacts deterministically; free-text mode will57 MISS quoted/structured identifiers like `"MRN": "33108876"` because there is58 no adjacent label to anchor on. Use the default (free-text) mode only for59 prose. Still run the name-recall pass on any free-text field VALUES (e.g. a60 `ChiefComplaint` or `Notes` field that contains a person's name).613. **Name-recall pass (do this every time on free prose).** Before running, read62 the text yourself and list every PERSON name and PLACE (city / facility) you63 can identify — including unusual or non-English names that a regex or the64 bundled lexicon would miss (e.g. "Rafael Alcaraz", "Okafor"). This is the65 model/agent contribution to the hybrid; you are the recall booster.664. Run `scripts/deidentify.py`, passing the names from step 3 via67 `--names "Name One,Name Two"` (or a `--names-file` with `{"names":[...],68 "places":[...],"orgs":[...]}`). The script deterministically redacts every69 occurrence, tokenizes consistently, runs its guard, and writes the manifest.70 The script — not your paraphrase — is the auditable system of record.715. Return the **redacted text** exactly as produced, and summarise the72 **manifest**: how many identifiers of each category were removed.736. **Surface `needs_human_review`.** Names caught heuristically, and any unknown74 capitalised text flagged for review, are listed there — tell the user these75 must be verified before release, and that bare names in free prose may still76 be missed.777. Only write the `--map` crosswalk when the user explicitly needs to78 re-identify later; warn that it is sensitive and must be stored securely,79 separately from the de-identified output.8081## Use as a safety net before an outbound action8283Agents often carry actions that emit content — **Send Email, Create Record,84Post Message, Export**. When an agent maker points one of those actions at this85skill (e.g. an instruction like *"before Send Email, de-identify the content86using phi-deidentifier"*), apply this skill as a gate:87881. Take the exact text about to leave the agent (e.g. the email Subject + Body,89 or the field values of the record).902. De-identify it with the steps above (including the name-recall pass).913. Populate the action's inputs from the **redacted** result only — never from92 the original text.934. If `needs_human_review` is non-empty, **do not fire the action**; show the94 redacted draft plus the review items and ask the user to confirm first.955. On success, tell the user the content was de-identified and how many96 identifiers were removed. Never place the `--map` crosswalk in the outbound97 content or in chat.9899The maker only needs to reference this skill by name in the action's instruction;100the operational steps above live here, in the skill, so they do not have to be101re-written per agent.102103## What it removes (HIPAA Safe Harbor, 18 identifiers)104105Handled **deterministically by regex** (high confidence): names in labelled106fields and after titles, street addresses, city + state, geographic ZIP107(truncated to 3 digits; restricted prefixes zeroed), all date elements finer108than year, ages 90+ (aggregated), phone, fax, email, URL, IP address, SSN, MRN,109health-plan / account / member numbers, certificate / license / DEA / NPI110numbers, device serial numbers, VIN.111112Handled **best-effort** and flagged for review: person names in free prose.113Names are caught by (a) title/label patterns ("Patient: …", "Dr. …") and (b) a114bundled offline **name lexicon** (`assets/name_lexicon.txt`) that catches many115bare names with no title or label — all fully local, no network or extra116packages, so it works in locked-down agent sandboxes (e.g. the Copilot Studio117Python container where you cannot `pip install`). Names not in the lexicon can118still be missed and word-like names may be over-redacted, so **human review of119free text is required**; extend the lexicon for your population. (If a runtime120already ships spaCy it is used automatically as a bonus, but installation is121never assumed.) City/state written without a comma or trailing ZIP may also be122missed. **Full-face photos and biometric identifiers are out of scope** — this123skill processes text only; call that out if the source contains images.124125## Transformations (so downstream data stays useful)126127- **Consistent pseudonyms:** each distinct value maps to a stable token, e.g.128 every occurrence of the same MRN becomes `[MRN-1]`, so linkage is preserved129 without exposing the value.130- **Dates → year only:** `03/15/1985` → `[DATE:1985]` (Safe Harbor keeps year).131- **Ages:** ≤89 retained; 90+ → `[AGE:90+]`.132- **ZIP → first 3 digits:** `98104` → `[ZIP:981XX]`; HHS-restricted low-population133 prefixes → `[ZIP:000]`.134- **Limited Data Set mode** keeps dates and city/state/ZIP intact while still135 removing names, contacts, SSN, MRN, and account numbers.136137## The audit manifest138139`NOTE.manifest.json` records the mode, a per-category count summary, one entry140per token with a **salted hash** of the original value (never the plaintext),141the `needs_human_review` list, and explanatory notes. Use it as the evidence142trail for a reviewer — it answers "what did the tool remove, and what still143needs eyes?" without itself containing PHI.144145## Guardrails146147- **Never claim compliance.** Report what was removed; a qualified human makes148 the Safe Harbor / expert-determination call.149- **Never invent or "restore" content.** Only redact.150- **Do not paste the crosswalk into chat** or co-locate it with the output; it151 re-identifies the data.152- **Always relay `needs_human_review`** and remind the user to review free text —153 regex cannot guarantee every bare name or unusual identifier is caught.154- Text only: if the source has images/scans, say so and recommend a separate155 image-redaction step.156157## References158159- `references/hipaa-safe-harbor.md` — the 18 identifiers, ZIP/date/age rules,160 and Safe Harbor vs Limited Data Set, as implemented here.161162<!-- toaster:generated:begin -->163164## Run this — do not improvise165166This capability's deterministic implementation is a RAPP single-file agent, linked beside this file as `phi_deidentifier_agent.py` and embedded as the fenced Python below (sha256 9dd1a9b5d2e52b47…; a byte-exact copy is also vaulted in the capsule comment at the end of this file). On a host with sandbox execution, run the linked file directly — if it is missing, write the fence contents verbatim to `phi_deidentifier_agent.py` first:167168```bash169python3 phi_deidentifier_agent.py '{"key": "value"}' # arguments as one JSON object170echo '{"key": "value"}' | python3 phi_deidentifier_agent.py # or on stdin171python3 phi_deidentifier_agent.py --tool # emit the JSON tool contract172```173174Treat stdout as a tool result. If it reports missing or unresolved inputs, stop and collect them. If it returns `steps`, execute those steps in order exactly as returned; if it returns `instructions`, follow them with the supplied inputs. Otherwise use the result verbatim. Do not invent behavior beyond that output. On a host without code execution, treat the Parameters schema and the code below as the exact specification and never paraphrase a step. Never edit inside the generated markers; a converter-equipped host can instead restore the original file checksum-verified with the installed `rapp-agent-converter/scripts/toast.py convert SKILL.md --to agent`.175176````python # rapp:deterministic177"""PhiDeidentifier -- Use this skill whenever the user asks to de-identify, redact, anonymise, scrub, or remove PHI/PII from clinical text, notes, transcripts, or documents before sharing, exporting, or using them for analytics, testing, or demos — e.g. "de-identify this note", "strip patient info", "make this HIPAA Safe Harbor compliant", "produce a limited data set". It removes the 18 HIPAA Safe Harbor identifiers and emits an audit manifest of what was removed. Run the bundled scripts/deidentify.py — do NOT hand-write redaction regexes yourself. This skill prepares de-identified text and an audit trail for human review; it does NOT certify legal compliance or make a formal expert determination.178179Generated by the rapp skill from phi-deidentifier. The RCI capsule at the bottom of this file carries the full original; `toast.py convert` restores it byte-exact."""180181import json182import re183import sys184185try:186 from agents.basic_agent import BasicAgent187except ImportError: # running OUTSIDE a brainstem -- stay executable anyway.188 class BasicAgent: # noqa: D101 - minimal stand-in, same contract189 def __init__(self, name=None, metadata=None):190 if name:191 self.name = name192 if metadata:193 self.metadata = metadata194195 def perform(self, **kwargs):196 return "Not implemented."197198 def system_context(self):199 return None200201 def to_tool(self):202 return {"type": "function", "function": {203 "name": self.name,204 "description": self.metadata.get("description", ""),205 "parameters": self.metadata.get("parameters", {})}}206207# The procedural layer, verbatim from the source capability.208INSTRUCTIONS = '# PHI De-identifier\r\n\r\nDe-identify clinical / health text to the **HIPAA Safe Harbor** standard (or a\r\n**Limited Data Set**) using the bundled, offline `scripts/deidentify.py`. Every\r\nrun produces two things: the **redacted text** and an **audit manifest** listing\r\nwhat was removed, by category, plus anything that needs human review.\r\n\r\n## When to use\r\n\r\n- "De-identify / anonymise / scrub / redact this note (or file / folder)."\r\n- "Remove patient identifiers before I share this."\r\n- "Make this Safe Harbor compliant" or "produce a limited data set."\r\n- Preparing PHI-free samples for demos, test fixtures, analytics, or model prompts.\r\n\r\nDo **not** use it to answer clinical questions or to claim legal certification —\r\nit is a mechanical redactor plus an audit trail for a human to verify.\r\n\r\n## How to run it\r\n\r\nAlways call the bundled script; never improvise redaction logic.\r\n\r\n```bash\r\n# Safe Harbor (default): writes NOTE.deid.txt + NOTE.manifest.json next to input\r\npython scripts/deidentify.py NOTE.txt\r\n\r\n# Limited Data Set: retains dates and city/state/ZIP, strips direct identifiers\r\npython scripts/deidentify.py NOTE.txt --mode limited\r\n\r\n# Inline text, machine-readable result to stdout\r\npython scripts/deidentify.py --text "John Doe, SSN 123-45-6789" --json\r\n\r\n# HYBRID name recall: pass the person names YOU (the agent) spotted in free prose\r\npython scripts/deidentify.py --text "<text>" --names "Rafael Alcaraz,Ngozi Okafor" --json\r\n\r\n# STRUCTURED / JSON input: redact by FIELD NAME (MRN, City, Employer, DOB, ...)\r\n# Use this whenever the input is a JSON object or a key/value record — NOT free text.\r\npython scripts/deidentify.py intake_record.json --format record --json\r\n\r\n# ALSO emit a reversible token->value crosswalk (SENSITIVE — re-identifying)\r\npython scripts/deidentify.py NOTE.txt --map crosswalk.json\r\n```\r\n\r\nSteps:\r\n\r\n1. Identify the input (a file path, a folder, or inline text) and the target\r\n standard: **Safe Harbor** (default) or **Limited Data Set** (`--mode limited`).\r\n2. **Pick the right mode for the shape of the input.** If the input is a\r\n **structured record** — a JSON object, or clear `Field: value` key/value pairs\r\n (e.g. an intake record, an API payload, an exported row) — run it with\r\n `--format record`. Record mode maps each field by its NAME (MRN, City,\r\n Employer, DOB, SSN, …) and redacts deterministically; free-text mode will\r\n MISS quoted/structured identifiers like `"MRN": "33108876"` because there is\r\n no adjacent label to anchor on. Use the default (free-text) mode only for\r\n prose. Still run the name-recall pass on any free-text field VALUES (e.g. a\r\n `ChiefComplaint` or `Notes` field that contains a person's name).\r\n3. **Name-recall pass (do this every time on free prose).** Before running, read\r\n the text yourself and list every PERSON name and PLACE (city / facility) you\r\n can identify — including unusual or non-English names that a regex or the\r\n bundled lexicon would miss (e.g. "Rafael Alcaraz", "Okafor"). This is the\r\n model/agent contribution to the hybrid; you are the recall booster.\r\n4. Run `scripts/deidentify.py`, passing the names from step 3 via\r\n `--names "Name One,Name Two"` (or a `--names-file` with `{"names":[...],\r\n "places":[...],"orgs":[...]}`). The script deterministically redacts every\r\n occurrence, tokenizes consistently, runs its guard, and writes the manifest.\r\n The script — not your paraphrase — is the auditable system of record.\r\n5. Return the **redacted text** exactly as produced, and summarise the\r\n **manifest**: how many identifiers of each category were removed.\r\n6. **Surface `needs_human_review`.** Names caught heuristically, and any unknown\r\n capitalised text flagged for review, are listed there — tell the user these\r\n must be verified before release, and that bare names in free prose may still\r\n be missed.\r\n7. Only write the `--map` crosswalk when the user explicitly needs to\r\n re-identify later; warn that it is sensitive and must be stored securely,\r\n separately from the de-identified output.\r\n\r\n## Use as a safety net before an outbound action\r\n\r\nAgents often carry actions that emit content — **Send Email, Create Record,\r\nPost Message, Export**. When an agent maker points one of those actions at this\r\nskill (e.g. an instruction like *"before Send Email, de-identify the content\r\nusing phi-deidentifier"*), apply this skill as a gate:\r\n\r\n1. Take the exact text about to leave the agent (e.g. the email Subject + Body,\r\n or the field values of the record).\r\n2. De-identify it with the steps above (including the name-recall pass).\r\n3. Populate the action's inputs from the **redacted** result only — never from\r\n the original text.\r\n4. If `needs_human_review` is non-empty, **do not fire the action**; show the\r\n redacted draft plus the review items and ask the user to confirm first.\r\n5. On success, tell the user the content was de-identified and how many\r\n identifiers were removed. Never place the `--map` crosswalk in the outbound\r\n content or in chat.\r\n\r\nThe maker only needs to reference this skill by name in the action's instruction;\r\nthe operational steps above live here, in the skill, so they do not have to be\r\nre-written per agent.\r\n\r\n## What it removes (HIPAA Safe Harbor, 18 identifiers)\r\n\r\nHandled **deterministically by regex** (high confidence): names in labelled\r\nfields and after titles, street addresses, city + state, geographic ZIP\r\n(truncated to 3 digits; restricted prefixes zeroed), all date elements finer\r\nthan year, ages 90+ (aggregated), phone, fax, email, URL, IP address, SSN, MRN,\r\nhealth-plan / account / member numbers, certificate / license / DEA / NPI\r\nnumbers, device serial numbers, VIN.\r\n\r\nHandled **best-effort** and flagged for review: person names in free prose.\r\nNames are caught by (a) title/label patterns ("Patient: …", "Dr. …") and (b) a\r\nbundled offline **name lexicon** (`assets/name_lexicon.txt`) that catches many\r\nbare names with no title or label — all fully local, no network or extra\r\npackages, so it works in locked-down agent sandboxes (e.g. the Copilot Studio\r\nPython container where you cannot `pip install`). Names not in the lexicon can\r\nstill be missed and word-like names may be over-redacted, so **human review of\r\nfree text is required**; extend the lexicon for your population. (If a runtime\r\nalready ships spaCy it is used automatically as a bonus, but installation is\r\nnever assumed.) City/state written without a comma or trailing ZIP may also be\r\nmissed. **Full-face photos and biometric identifiers are out of scope** — this\r\nskill processes text only; call that out if the source contains images.\r\n\r\n## Transformations (so downstream data stays useful)\r\n\r\n- **Consistent pseudonyms:** each distinct value maps to a stable token, e.g.\r\n every occurrence of the same MRN becomes `[MRN-1]`, so linkage is preserved\r\n without exposing the value.\r\n- **Dates → year only:** `03/15/1985` → `[DATE:1985]` (Safe Harbor keeps year).\r\n- **Ages:** ≤89 retained; 90+ → `[AGE:90+]`.\r\n- **ZIP → first 3 digits:** `98104` → `[ZIP:981XX]`; HHS-restricted low-population\r\n prefixes → `[ZIP:000]`.\r\n- **Limited Data Set mode** keeps dates and city/state/ZIP intact while still\r\n removing names, contacts, SSN, MRN, and account numbers.\r\n\r\n## The audit manifest\r\n\r\n`NOTE.manifest.json` records the mode, a per-category count summary, one entry\r\nper token with a **salted hash** of the original value (never the plaintext),\r\nthe `needs_human_review` list, and explanatory notes. Use it as the evidence\r\ntrail for a reviewer — it answers "what did the tool remove, and what still\r\nneeds eyes?" without itself containing PHI.\r\n\r\n## Guardrails\r\n\r\n- **Never claim compliance.** Report what was removed; a qualified human makes\r\n the Safe Harbor / expert-determination call.\r\n- **Never invent or "restore" content.** Only redact.\r\n- **Do not paste the crosswalk into chat** or co-locate it with the output; it\r\n re-identifies the data.\r\n- **Always relay `needs_human_review`** and remind the user to review free text —\r\n regex cannot guarantee every bare name or unusual identifier is caught.\r\n- Text only: if the source has images/scans, say so and recommend a separate\r\n image-redaction step.\r\n\r\n## References\r\n\r\n- `references/hipaa-safe-harbor.md` — the 18 identifiers, ZIP/date/age rules,\r\n and Safe Harbor vs Limited Data Set, as implemented here.'209210# Ordered commands lifted verbatim from the capability's own documentation.211STEPS = []212213214class PhiDeidentifierAgent(BasicAgent):215 def __init__(self):216 self.name = 'PhiDeidentifier'217 self.metadata = {218 "name": "PhiDeidentifier",219 "description": "Use this skill whenever the user asks to de-identify, redact, anonymise, scrub, or remove PHI/PII from clinical text, notes, transcripts, or documents before sharing, exporting, or using them for analytics, testing, or demos \u2014 e.g. \"de-identify this note\", \"strip patient info\", \"make this HIPAA Safe Harbor compliant\", \"produce a limited data set\". It removes the 18 HIPAA Safe Harbor identifiers and emits an audit manifest of what was removed. Run the bundled scripts/deidentify.py \u2014 do NOT hand-write redaction regexes yourself. This skill prepares de-identified text and an audit trail for human review; it does NOT certify legal compliance or make a formal expert determination.",220 "parameters": {221 "type": "object",222 "properties": {},223 "required": []224 }225 }226 super().__init__(name=self.name, metadata=self.metadata)227228 def perform(self, **kwargs): # toaster:generated-perform229 return json.dumps({"status": "ok", "instructions": INSTRUCTIONS,230 "inputs": kwargs,231 "note": "Prose-only capability: follow INSTRUCTIONS "232 "with the given inputs."}, indent=2)233234if __name__ == "__main__":235 # echo '{"arg": "value"}' | python3 phi_deidentifier_agent.py236 # python3 phi_deidentifier_agent.py '{"arg": "value"}'237 # python3 phi_deidentifier_agent.py --tool # emit the JSON tool contract238 _a = sys.argv[1:]239 if _a and _a[0] == "--tool":240 print(json.dumps(PhiDeidentifierAgent().to_tool(), indent=2))241 else:242 _raw = _a[0] if _a else (sys.stdin.read().strip() or "{}")243 print(PhiDeidentifierAgent().perform(**json.loads(_raw)))244245# rci-capsule:v1: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````247248<!-- toaster:generated:end -->249250<!-- rci-capsule:v1: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…(truncated)