Skip to main content

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 — the payload_hash of 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:

KindFired when
event-log.retention.started / completedRetention sweep begins / ends
event-log.redaction.salt-rotatedRedaction salt rotation
event-log.session.branchedA session branch is created
event-log.replay.raw-openedRaw replay cursor opened (operator action)
permission.granted / denied / prompted / timeout / revokedPermission gate outcome
permission.bash / filesystem / credential / toolPer-family permission gate
mcp.recipe.added / removed / testedMCP server recipe lifecycle
settings.shortcut.overriddenKeyboard shortcut binding changed
harness-self.tool.called / policy.proposed / policy.written / policy.rejectedHarness-self MCP tool dispatch
storage.migration.drift-detectedMigration ledger drift at boot
llm.request.started / completed / failedLLM request lifecycle (emitted via the llm/connector emitter)
secret_reference.resolvedModel-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.