The audit log
An append-only JSON Lines file. One line per event, each carrying the SHA-256 hash of the line before it, so any later edit shows up.
Events
| Event | Written when |
|---|---|
spotagentiq.start | The proxy starts. Records the agent, version, number of upstreams and rules, and the default action. |
call.allowed | A rule (or the default) allowed the call. |
call.denied | A rule blocked the call. The upstream never saw it. |
call.pending | The call is held for a person. |
call.approved | Someone approved it. Records who. |
call.rejected | Someone rejected it. Records who and their note. |
call.timeout | Nobody decided within approval_timeout. |
call.completed | The upstream returned a result. A short summary is stored. |
call.failed | The upstream returned an error or couldn't be reached. |
A held call that was approved therefore produces three lines: call.pending, call.approved, call.completed. They share a call_id.
Fields
{"seq":14,"time":"2026-11-07T14:03:02.118Z","event":"call.approved",
"call_id":"c_7f3a","agent":"support-bot","principal":"ops@acme.com",
"upstream":"crm","tool":"send_email",
"args":{"to":"dana@northwind.io","subject":"About your refund","api_key":"[redacted]"},
"rule":"send_*","reason":"Outbound communication","approver":"priya",
"prev_hash":"9f2c41e0…","hash":"41ab07d3…"}
| Field | Meaning |
|---|---|
seq | Position in the log, starting at 1. Gaps mean lines were removed. |
time | UTC timestamp. |
event | One of the events above. |
call_id | Ties together the lines for one tool call. |
agent, principal | Which agent, and which person it was acting for, from the config. |
upstream, tool | Which MCP server and tool were called. |
args | The arguments, after redaction. |
rule, reason | The rule that decided, and its reason. |
approver | The name entered in the inbox by whoever approved or rejected. |
result | Short summary of what the upstream returned. |
prev_hash, hash | The chain. See below. |
How the hash chain works
When a line is written, SpotAgentIQ sets prev_hash to the previous line's hash, serialises the entry with hash empty, and stores the SHA-256 of those bytes as hash. The file is opened append-only and fsynced after every write.
To check it, you walk the file from the top and recompute each hash. Three kinds of tampering are caught:
- Editing a line changes its content, so its recomputed hash no longer matches the stored one.
- Deleting a line leaves the next line's
prev_hashpointing at something that isn't there, and leaves a gap inseq. - Inserting a line breaks
prev_hashon the line after it.
Someone with write access could rewrite the whole file from the edit onward with fresh hashes. That's why you should anchor the latest hash somewhere else; see stronger guarantees. There's a longer explanation in Tamper-evident audit logs in 40 lines.
Verifying a log
spotagentiq verify --log audit.jsonl
# OK: 1,204 entries, chain intact.
spotagentiq verify --log audit.jsonl --json
# {"entries":1204,"ok":true,"last_hash":"…"}
The exit code is 0 when the chain is intact and 3 when it isn't, so it works in cron jobs and CI. On failure it prints the first bad seq and what was wrong with it. A running proxy reports the same thing at GET /api/verify, and the inbox shows the chain status at the top of the page.
Exporting for an auditor
spotagentiq export --log audit.jsonl > audit.csv
curl -H "Authorization: Bearer $SPOTAGENTIQ_TOKEN" \
"http://localhost:8484/export.csv?since=2026-11-01T00:00:00Z&until=2026-12-01T00:00:00Z"
The CSV has one row per event with the same fields, including both hashes, so the auditor can re-verify it themselves.
Stronger guarantees
The chain proves the file is internally consistent. To prove nobody rewrote it end to end, put the latest hash somewhere the machine running the agent can't change:
- Ship the file to object storage with versioning or object lock (S3, GCS, R2).
- Post
spotagentiq verify --jsonoutput to a Slack channel or ticket on a schedule. - Timestamp the latest hash with an RFC 3161 timestamping service.
Anyone holding a hash from a given time can later prove the log hasn't been rewritten up to that point. SpotAgentIQ Cloud will do this for you.