Skip to content

feat(redaction): add event-level redact_event hook#41

Open
naji247 wants to merge 2 commits into
mainfrom
feat/redact-event-hook
Open

feat(redaction): add event-level redact_event hook#41
naji247 wants to merge 2 commits into
mainfrom
feat/redact-event-hook

Conversation

@naji247

@naji247 naji247 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Redaction decisions often need more context than a single string. The existing redact_sensitive_information hook is applied blindly to every string field in an event, so consumers can't tell which tool produced a value, what type of event is being published, or which field a string belongs to.

This PR adds a new event-level redaction hook, redact_event, alongside the existing string-level hook. It receives the full Event object — resource_name, event_type, parameters, response, user_intent, and the rest — and returns a modified event, or None to drop the event entirely.

from agentcat import AgentCatOptions

def redact_event(event):
    # Drop events from tools that handle secrets entirely
    if event.resource_name == "get_credentials":
        return None
    # Strip response payloads from a specific tool
    if event.resource_name == "export_report":
        modified = event.model_copy()
        modified.response = None
        return modified
    return event

agentcat.track(server, "proj_0000000", AgentCatOptions(redact_event=redact_event))

Behavior

  • Signature: Callable[[Event], Event | None | Awaitable[Event | None]] — sync or async, exported as EventRedactionFunction. Event is now also exported from the package root.
  • Ordering: redact_event runs first in the queue pipeline and sees raw, unredacted values with full event context. redact_sensitive_information (if configured), sanitization, and truncation all run on its output, so the string-level scrubber remains the last line of defense.
  • Drop semantics: returning None drops the event. If the hook raises, the event is dropped and the error is logged to ~/agentcat.log — the same fail-closed semantics as the existing string-level hook.
  • System-managed fields: id, session_id, project_id, event_type, and timestamp are restored if the hook changes or removes them, protecting session and project attribution. Everything else is fully consumer-controlled, and fields the hook clears stay cleared.

Behavior note worth knowing

The string-level redact_sensitive_information hook currently never fires on the live publish path — the recursive walker doesn't descend into Pydantic event models (tracked by the xfail suite in tests/e2e/official/test_redaction_http.py). The new event-level hook operates on the Pydantic model directly and works on the live path today, so it also serves as a working redaction mechanism until that bug is fixed. The structural ordering (event hook first, string hook second) is preserved for when it is.

Test plan

  • Unit tests for the new apply_event_redaction helper: sync/async hooks, None drops, cleared-field honoring, system-managed field restoration, internal function fields hidden from the hook with redaction_fn preserved, error propagation, same-object mutation for pipeline observers (tests/test_redaction.py)
  • Queue-level tests: hook attachment from track() options via publish_event, hook rewrite published, drop-on-None, drop-on-raise, ordering relative to string redaction (tests/test_event_queue.py)
  • End-to-end tests over a real Streamable-HTTP transport (not xfail — the event hook works on the live path): metadata visibility and field rewrite by resource_name, drop-on-None with other events still publishing, and a raising hook dropping only the affected event while the server keeps serving (tests/e2e/official/test_redact_event_http.py)
  • Full suite green: 432 passed, 96 skipped, 3 xfailed; ruff and mypy --strict at their pre-existing baselines with no new findings

Add a redact_event option to AgentCatOptions that receives the full Event
object before publishing, complementing the existing string-level
redact_sensitive_information hook. Consumers can inspect event metadata
(resource_name, event_type, parameters, response, etc.) to make
context-aware redaction decisions, return a modified event, or return
None to drop the event entirely.

- New EventRedactionFunction type; Event and EventRedactionFunction are
  now exported from the package root
- Hook runs before string-level redaction, so it sees raw values;
  string redaction, sanitization, and truncation still run on its output
- System-managed fields (id, session_id, project_id, event_type,
  timestamp) are restored if the hook alters them
- Hook errors drop the event and log, matching existing redaction
  semantics
- Attached centrally in publish_event, covering every event producer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants