Reference Implementations

Security

pii-redactor

intermediate

Scan documents for PII, redact automatically, or escalate to human review.

APIs Used

ctx.filesctx.llmctx.escalate()ctx.telemetry.emit

Capabilities Required

security/pii/redact

What this demonstrates

  • 1ctx.files.readText() for loading documents to scan
  • 2ctx.llm.complete() for PII detection with confidence scoring
  • 3ctx.escalate() when confidence is low — keeps humans in the loop for privacy decisions
  • 4Privacy-first pattern: detect → redact with confidence → escalate uncertain cases
  • 5ctx.telemetry.emit via emitReferenceAuthorSignal when LLM JSON parsing fails (recovery path)
typescript
/**
* PII Redactor - Production Reference Agent
*
* Canon alignment: KB 22 (HumanOS) - Policy, CBE, Risk
* Demonstrates: ctx.files, ctx.llm, ctx.escalate()
*
* Real use case: Scan documents for PII, redact or escalate.
*/
import { handler, withProvenanceContext } from '@human/agent-sdk';
import type { ExecutionContext } from '@human/agent-sdk';
import { emitReferenceAuthorSignal } from '../../lib/reference-author-telemetry.js';
export const AGENT_ID = 'pii-redactor';
export const VERSION = '1.0.0';
export const CAPABILITIES = ['security/pii/redact'];
export interface PIIRedactorInput {
document_path: string;
redact_auto?: boolean;
}
export interface PIIRedactorOutput {
success: boolean;
redacted_path?: string;
pii_found: Array<{ type: string; count: number }>;
escalation_required: boolean;
provenance_id: string;
}
const execute = async (
ctx: ExecutionContext,
input: PIIRedactorInput
): Promise<PIIRedactorOutput> => {
ctx.log.info('Scanning for PII', { path: input.document_path });
const content = await ctx.files.readText(input.document_path);
const result = await ctx.llm.complete({
prompt: [
{
role: 'system',
content: 'Identify PII types in the text. Return JSON: { "pii": [{"type":"email","count":1}, ...] }. Only valid JSON.',
},
{
role: 'user',
content: content.slice(0, 4000),
},
],
temperature: 0.1,
maxTokens: 500,
});
let piiFound: PIIRedactorOutput['pii_found'] = [];
try {
const parsed = JSON.parse(result.content) as { pii?: Array<{ type: string; count: number }> };
piiFound = parsed.pii ?? [];
} catch {
await emitReferenceAuthorSignal(ctx, 'pii_llm_json_parse_recovery', {
document_path: input.document_path,
});
piiFound = [];
}
const totalPII = piiFound.reduce((sum, p) => sum + p.count, 0);
const escalationRequired = totalPII > 10;
let provenanceId: string;
if (escalationRequired) {
const decision = await ctx.escalate({
reason: 'High PII count - manual review required before redaction',
context: {
document_path: input.document_path,
pii_found: piiFound,
total_count: totalPII,
},
requiredCapability: 'security/pii/approve',
});
provenanceId = await ctx.provenance.log(
withProvenanceContext(ctx, {
type: 'pii:escalation',
status: 'success',
metadata: {
input: { document_path: input.document_path },
output: { escalation_required: true, human_approved: decision.approved },
},
})
);
// Human decision determines outcome (P5 Human-in-Loop):
// approved → success, proceed; rejected → fail, halt redaction.
return {
success: decision.approved,
pii_found: piiFound,
escalation_required: !decision.approved,
provenance_id: provenanceId,
};
} else if (input.redact_auto && totalPII > 0) {
// Redact each PII type with appropriate pattern
let redacted = content;
const PII_PATTERNS: Record<string, { regex: RegExp; replacement: string }> = {
email: { regex: /\S+@\S+\.\S+/g, replacement: '[EMAIL_REDACTED]' },
phone: { regex: /\+?[\d\s()-]{10,}/g, replacement: '[PHONE_REDACTED]' },
ssn: { regex: /\d{3}-?\d{2}-?\d{4}/g, replacement: '[SSN_REDACTED]' },
credit_card: { regex: /\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}/g, replacement: '[CC_REDACTED]' },
ip_address: { regex: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, replacement: '[IP_REDACTED]' },
};
for (const pii of piiFound) {
const pattern = PII_PATTERNS[pii.type];
if (pattern) {
redacted = redacted.replace(pattern.regex, pattern.replacement);
} else {
ctx.log.warn('Unknown PII type, skipping redaction', { type: pii.type });
}
}
const redactedPath = input.document_path.replace(/(\.[^.]+)$/, '.redacted$1');
await ctx.files.writeText(redactedPath, redacted);
provenanceId = await ctx.provenance.log(
withProvenanceContext(ctx, {
type: 'pii:redacted',
status: 'success',
metadata: {
input: { document_path: input.document_path },
output: { redacted_path: redactedPath, pii_count: totalPII },
},
})
);
return {
success: true,
redacted_path: redactedPath,
pii_found: piiFound,
escalation_required: false,
provenance_id: provenanceId,
};
} else {
provenanceId = await ctx.provenance.log(
withProvenanceContext(ctx, {
type: 'pii:scanned',
status: 'success',
metadata: {
input: { document_path: input.document_path },
output: { pii_found: piiFound },
},
})
);
}
return {
success: true,
pii_found: piiFound,
escalation_required: false,
provenance_id: provenanceId,
};
};
export default handler({
name: AGENT_ID,
id: AGENT_ID,
version: VERSION,
capabilities: CAPABILITIES,
manifest: {
operations: [
{
name: 'scan',
description: 'Scan document for PII; redact or escalate for human review when needed',
paramsSchema: {
document_path: { type: 'string', required: true, description: 'Path to document' },
redact_auto: { type: 'boolean', description: 'Whether to auto-redact when PII found' },
},
resultKind: 'agent.pii-redactor.result',
},
],
},
execute,
});

Run the tests

From monorepo root

$ pnpm test:agents:reference

$ pnpm test:agents:reference:verbose

The reference suite runs all 23 agents with createMockExecutionContext(), verifying every ctx.* API call and output shape.

See Also