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.