Audit log
Every prompt, response, tool call, tool result, permission decision, and hook firing lands in a local hash-chained audit log before it's shown in the UI. The log is the load-bearing artifact behind Kenaz's "you can prove what happened" pitch.
Where it lives
The event log lives inside the harness data directory, which is the same SQLite database that holds sessions and other harness state:
~/.kenaz/harness/<env>/
<env> is prod by default; dev or stage for non-production builds (controlled by KENAZ_HARNESS_ENV). The database is plain SQLite — you can query it with the sqlite3 CLI directly if you want to skip the UI.
What gets logged
Each event has:
event_id— a ULID; lexicographically sortable by time.session_id— the session it belongs to (null/empty for session-less events).emitted_at— RFC 3339 timestamp.emitter_id— the source subsystem (e.g.llm/connector,mcp/client,session/).kind— a namespaced dotted string; see the kinds table below.payload— kind-specific structured data (redacted before persistence).payload_hash— SHA-256 of the canonical-JSON payload bytes.prev_hash— thepayload_hashof the previous event in the session chain. The all-zero hash marks the first event in a session. Tampering with any past event invalidates every hash that follows.
Event kinds
Kinds use a namespaced dotted format (<namespace>.<dotted.path>). The built-in kinds registered in the harness include:
| Kind | Fired when |
|---|---|
event-log.retention.started / completed | Retention sweep begins / ends |
event-log.redaction.salt-rotated | Redaction salt rotation |
event-log.session.branched | A session branch is created |
event-log.replay.raw-opened | Raw replay cursor opened (operator action) |
permission.granted / denied / prompted / timeout / revoked | Permission gate outcome |
permission.bash / filesystem / credential / tool | Per-family permission gate |
mcp.recipe.added / removed / tested | MCP server recipe lifecycle |
settings.shortcut.overridden | Keyboard shortcut binding changed |
harness-self.tool.called / policy.proposed / policy.written / policy.rejected | Harness-self MCP tool dispatch |
storage.migration.drift-detected | Migration ledger drift at boot |
llm.request.started / completed / failed | LLM request lifecycle (emitted via the llm/connector emitter) |
secret_reference.resolved | Model-side @secret: reference resolution |
Additional kinds are emitted by their respective subsystems (hooks, compaction, workflows, agentgraph). The full registered list is available at runtime via the harness's diagnostics surface.
Browsing the log
Audit log in the left rail:
- Timeline — newest first. Filter by kind/category, actor (emitter ID), time range (since/until), and free-text search.
- Detail panel — click any event to inspect the full payload. Copies as JSON.
- Verify chain — runs through the hash chain for the visible range. Reports how many events were checked and points at the first broken link if any is found.
Verifying integrity
The hash chain is verifiable without the Kenaz UI. Each event's payload_hash is sha256(canonical_json(payload)); each event's prev_hash holds the previous event's payload_hash in the session chain (the zero hash for the first event). Short version:
prev = [32]byte{} // zero hash for first event
for each event in session order:
expected_payload_hash = sha256(canonical_json(event.payload))
if expected_payload_hash != event.payload_hash: TAMPER at event.event_id
if event.prev_hash != prev: CHAIN BREAK at event.event_id
prev = event.payload_hash
The Verify chain button in the Audit log view calls the backend verifier over the visible range and reports the first broken link.
Exports
Audit log → Export. Three formats (select from the dropdown, then click Export):
- JSONL — one event per line. Best for piping into
jq,grep, or another tool. - CSV — flattened payload columns. Good for spreadsheets or quick visualization.
- PDF — a formatted report with chain status embedded in the header. Suitable for attaching to an incident report or handing to a compliance auditor.
Retention
Configure via Settings → Audit. Three strategies:
- Keep forever (default) — events are never deleted.
- Delete after window — events older than the configured window are permanently deleted in the nightly sweep.
- Archive then delete after window — events older than the window are written to a JSONL archive file under
~/.kenaz/harness/<env>/audit-archive/, then removed from the database.
The retention sweep runs nightly. Chain verification still works after a sweep because the chain is per-session and old sessions are swept as a unit.
Privacy of the log itself
- The audit log is on-disk, on your machine, in a directory readable only by your user.
- It contains the exact prompts, responses, and tool arguments — including any sensitive content the model saw.
- Kenaz does not transmit the audit log anywhere by itself. If you want to ship it to your SIEM, set up a hook that exports each event as it lands.
- For tighter privacy, set audit retention to 7 days; the chain still verifies, but historical content drops off the prune cycle quickly.
Common queries
The event log is stored in the harness SQLite database. You can query it directly with sqlite3 (the exact table name and column layout depend on the harness version):
-- Permission denials in the last week
SELECT emitted_at, session_id, emitter_id, payload
FROM events
WHERE kind = 'permission.denied'
AND emitted_at > datetime('now', '-7 days');
-- All MCP recipe changes
SELECT emitted_at, kind, payload
FROM events
WHERE kind LIKE 'mcp.recipe.%'
ORDER BY emitted_at DESC;
Use the Audit log view's free-text search and kind filter for interactive exploration; raw SQL is useful for programmatic analysis or integration with external tooling.