ADR-018: Redaction Scope Extended to Decision and Provenance Payloads
Date: 2026-08-14
Status: Accepted
Context
Issue #305 shipped redaction for trace envelopes: Tracer.record runs theRedactionFilter over envelope.input/envelope.output before persistence,
so .agenthood/traces/traces.ndjson never contains raw PII or secrets. The
M8 audit that preceded this work found the guarantee was only half-true:RunLifecycle.recordRun persists the raw input and output — unredacted —
into DecisionLog files (.agenthood/decisions/*.json) and into the
ProvenanceStore chain. The same audit found a second integrity wart:createTraceEnvelope hashed the raw input/output while Tracer.record
rewrote the payload redacted, so inputHash never matched the persisted
text — a verifier comparing hash to payload would always fail.
Decision
- One redactor, one boundary. The
RedactionFilterinstance built byApplicationContextis now exposed onExecutionContext.redactor, andBaseAgentruns every payload through it at the single source: trace envelope content, decisiontask/decision, and provenancesourceDocument. Decision/provenance persistence is no longer a bypass path for PII. - Redact before hashing.
recordTraceredacts input/output before callingcreateTraceEnvelope, soinputHash/outputHashare computed over the exact text that is persisted.Tracer.record's own pass remains as a safety net for other recorders; it is idempotent on already-redacted text (placeholders match no rules). - Default-enabled. Redaction applies whenever
observability.redaction.enabledis not explicitlyfalse; unconfigured installs redact with the built-in rules (emails,sk-keys, bearer tokens, URL query values, IP addresses). Set"enabled": falseto opt out.
Alternatives Considered
| Option | Pros | Cons | Why Rejected |
|---|---|---|---|
| Keep decisions raw, document the bypass | No provenance-semantics change | Defeats the redaction feature's purpose for the majority of persisted text | Security-correctness wins |
| Redact only at persist time (in DecisionLog/ProvenanceStore) | Central choke point | Two redaction points with divergent rule sets; store layer shouldn't know about config | Single shared redactor at the source is simpler and deterministic |
| Hash the redacted payload only in the store | Fixes integrity without touching envelope creation | Hash is part of the envelope contract; diverging hashers across callers | Redact-before-hash fixes all callers uniformly |
Consequences
- Decisions, provenance, and traces now carry consistent redacted content;
DecisionSearch/DecisionLog.searchindex the redacted text, which is the intended trade-off for replayable, PII-safe stores. - Hash integrity: any tool comparing
inputHashto persistedinputnow verifies cleanly. - Behavior change applies to every install by default (raw payloads are scrubbed unless redaction is explicitly disabled); operators who rely on raw persisted text can opt out via config.