Skip to main content

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",
"payload": { "to": "[email protected]", "subject": "Q3 Report" },
"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"
}
FieldTypeDescription
auditIdstringUnique ID. Retries are idempotent on this ID, so an event is never stored twice.
agentIdstringThe agent that attempted the action.
userIdstring?The user or session that triggered the agent.
actionstringThe action name (send_email, execute_query, ...).
payloadobjectThe payload passed to guard(), after redaction (see below).
approvedbooleanWhether the action was allowed.
reason, matchedRulestring?Why it was blocked, and by which rule.
tagsobject?Up to 20 string key/value pairs for filtering.
timestampISO 8601When the SDK evaluated the action (your clock).
environmentstringThe 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" }
What this does and does not guarantee

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) or aegis.flush() (Python) before a serverless function returns or the process exits.
  • Delivery problems are reported to your onError / on_error callback; 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.