Audit Trail
Every guard() call produces an audit event: what the agent tried to do, what the decision was, and why. Events are delivered to Auditail in the background and stored in a tamper-evident, hash-chained log.
What gets recorded
{
"auditId": "aeg_9f3c2a71d4b8e605",
"agentId": "financial-report-bot",
"userId": "user_7291",
"action": "send_email",
"approved": false,
"reason": "Blocked: sending email to an external domain is not permitted.",
"matchedRule": "no-external-email",
"tags": { "team": "finance" },
"timestamp": "2026-01-15T10:30:00.123Z",
"environment": "production",
"sdkVersion": "0.2.0"
}
| Field | Type | Description |
|---|---|---|
auditId | string | Unique ID. Retries are idempotent on this ID, so an event is never stored twice. |
agentId | string | The agent that attempted the action. |
userId | string? | The user or session that triggered the agent. |
action | string | The action name (send_email, execute_query, ...). |
payload | object | The payload passed to guard(), after redaction (see below). |
approved | boolean | Whether the action was allowed. |
reason, matchedRule | string? | Why it was blocked, and by which rule. |
tags | object? | Up to 20 string key/value pairs for filtering. |
timestamp | ISO 8601 | When the SDK evaluated the action (your clock). |
environment | string | The label you set in the constructor. |
The server adds seq, receivedAt (server clock), prevHash and hash.
Human approvals in the log
When a require_approval rule fires, Auditail writes two entries itself, so they are not claims made by the agent: approval.requested (the action, redacted payload and rule) and approval.decided (approved, denied or expired, the approver, their comment and the time). See Human approval.
How tamper-evidence works
Each event's hash is SHA-256(prevHash + the event's contents), and prevHash is the previous event's hash. That makes the log a chain per API key:
event 1 ──hash──▶ event 2 ──hash──▶ event 3 ──hash──▶ ...
If any stored event is modified, removed, or reordered, every hash after it stops matching. Verify integrity (in the dashboard, or GET /v1/verify) re-computes the whole chain and tells you the first event that does not check out:
{ "ok": false, "firstBadSeq": 2, "problem": "content_modified" }
Hash chaining makes tampering detectable. It does not make it impossible: someone with write access to the database could rewrite the entire chain consistently. For stronger guarantees, record the latest hash somewhere you control (a ticket, a git commit, your own database) and compare it later. Auditail is not a SOC2 certification, and we do not claim it makes you compliant on its own.
Redaction
Secrets should not end up in an audit log. Before an event is stored or sent, the SDK replaces the value of any payload key matching a redaction list with "[REDACTED]", at any nesting depth. Your guardrail rules still see the real payload; only the recorded copy is redacted.
Defaults: password, passwd, secret, token, apiKey, api_key, authorization, auth, ssn, creditCard, cardNumber, cvv, privateKey (case-insensitive). Add your own:
const aegis = new Auditail({
apiKey: process.env.AUDITAIL_API_KEY!,
redactKeys: ['patient_name', 'dob'],
});
aegis = Auditail(api_key=os.environ["AUDITAIL_API_KEY"], redact_keys=["patient_name", "dob"])
Redaction is key-based. It does not scan free text for personal data, so do not put secrets inside string values you do not control.
Delivery behavior
guard()never waits on the network. Events are queued and sent in batches in the background.- If Auditail is unreachable, your agent keeps working. Events stay queued (up to 10,000) and retry with backoff.
- Call
await aegis.flush()(JS) oraegis.flush()(Python) before a serverless function returns or the process exits. - Delivery problems are reported to your
onError/on_errorcallback; they are never thrown into your agent.
Viewing events
- Dashboard at app.auditail.io: live stats, filter by decision or agent, expand any event to see its full payload and hashes, and run the integrity check.
- REST API: see the REST API reference.
- Locally:
aegis.getAuditLog()returns this process's events without any network.
Keep the audit ID
Store result.auditId next to the action in your own system. When something goes wrong, it is the key that connects your records to the audit trail.