Audit Policy
The audit policy category configures what gets logged during workflow execution and how long those logs are retained. Unlike most policies, it never blocks -- it is a must-record handler that runs even when an earlier handler has already blocked the run, so the audit trail captures the blocked attempt itself.
Use it to satisfy regulatory log-retention requirements (SOC2, ISO 27001, HIPAA audit controls) and to standardize redaction of sensitive fields across agents.
Rules
| Rule | Type | Default | Description |
|---|---|---|---|
log_inputs | boolean | true | Log workflow inputs (after redaction) |
log_outputs | boolean | true | Log workflow outputs (after redaction) |
log_steps | boolean | true | Log individual workflow step counts |
log_tool_calls | boolean | true | Log tool invocation counts |
redact_fields | string[] | [] | Additional field names to redact (merged with defaults) |
retention_days | integer | 90 | How long to retain audit logs |
min_log_level | string | "INFO" | Minimum Python logging level (DEBUG/INFO/WARNING/ERROR) |
max_log_entries | integer | 500 | Max log entries per execution (0 = unlimited) |
capture_stdout | boolean | false | Capture print() output as log entries |
Default Redacted Fields
These are always redacted, regardless of redact_fields configuration:
password, secret, token, api_key, apikey, authorization, credential, private_key
Matching is case-insensitive substring -- a field named user_password_hash will be redacted because it contains password.
How It Works
The audit handler runs at before_workflow, after_workflow, and on_failure. It always returns ALLOW; the action is purely informational. Because short_circuit_on_block = False, the handler executes even if a prior policy already blocked the run.
| Phase | What It Logs |
|---|---|
before_workflow | Redacted inputs, agent/workflow IDs, active audit modes |
after_workflow | Step count, tool call count, redacted output |
on_failure | Error type and message |
Context Attributes Read
| Attribute | Phase | Purpose |
|---|---|---|
context.inputs | before_workflow | Redact + log inputs |
context.agent_name | all | Audit log scoping |
context.workflow_name | all | Audit log scoping |
context.workflow_id | all | Audit log correlation |
context.step_logs | after_workflow | Count steps (len(step_logs)) |
context.tool_call_count | after_workflow | Count tool invocations |
result (parameter) | after_workflow | Redact + log final output |
The handler also writes context._audit_rules so downstream components can read the active config.
Example Policy
{
"log_inputs": true,
"log_outputs": true,
"log_steps": true,
"log_tool_calls": true,
"redact_fields": ["ssn", "dob", "patient_id"],
"retention_days": 365,
"min_log_level": "INFO",
"max_log_entries": 1000,
"capture_stdout": false
}
SDK Integration
import waxell_observe as waxell
waxell.init()
@waxell.observe(agent_name="claims-agent", enforce_policy=True)
async def process_claim(claim: dict) -> dict:
return await adjudicate(claim)
Inputs and outputs are automatically logged + redacted on entry/exit. No SDK calls are required to opt in -- assigning an audit policy to the agent is enough.
Observability
| Field | Example |
|---|---|
| Category | audit |
| Action | allow |
| Reason | "Audit logging active (inputs, outputs, steps, tool calls)" |
| Metadata | {"log_config": {"min_log_level": "INFO", "max_log_entries": 500, "capture_stdout": false}, "audit_rules": {...}} |
Common Gotchas
-
auditnever blocks. It is a must-record handler -- it returnsALLOWeven on failure. Combine withkill-switchorsafetyfor blocking behavior. -
Redaction is substring-based.
redact_fields: ["id"]will redact ANY field whose name containsid(includingclient_id,request_id). Use explicit names like"customer_id"to scope the match. -
Defaults are always merged. You cannot disable redaction of
password/token/api_keyby leavingredact_fieldsempty. The defaults list is hardcoded. -
retention_daysis enforced by infra, not the handler. The rule is surfaced in the audit log emission but actual retention is governed by the telemetry pipeline (S3 lifecycle, OpenSearch ILM). -
max_log_entries: 0means unlimited. This is the documented sentinel -- do not treat it as "log nothing". -
capture_stdoutis opt-in.print()calls are NOT captured by default; agents that rely on print debugging will produce no audit output without this flag.
Next Steps
- Privacy Policy -- Redaction of PII before logging
- Compliance Policy -- Meta-validator for SOC2/HIPAA/ISO frameworks
- Policy Categories -- All categories