Structured agent_events audit log replaces 60+ scattered console.log calls
nwhitehouse replaced the only debug trail for agent activity - scattered `console.log` calls across `chatTools.ts` and `llm/olava.ts` - with an append-only `agent_events` table. The fire-and-forget write pattern and the metadata-only payload rule are worth stealing if your fork has any multi-step agent flow you need to diagnose after the fact.
Migration 004_agent_events.sql creates agent_events(id, chat_id, type, payload jsonb, created_at) with RLS via can_access_chat() - the same predicate as chat_messages, so the audit log is automatically scoped to whoever can see the chat. No new access model to maintain.
lib/agentEvents.ts (65 LOC) exports a single recordEvent() function. It's fire-and-forget: the return type is void, failures are caught, logged, and swallowed. An audit write failure must never interrupt a streaming chat response. The function also skips silently when chatId is null or undefined, covering tabular-review chats that don't have a chats row.
The event taxonomy covers the turn lifecycle: turn.started, model.first_token (with latency_ms), tool.call_started (with name and args_keys), tool.call_succeeded (with latency_ms and result_length), tool.call_failed (reserved for feat-016), loop.escalated (reserved for feat-014), turn.completed (with total_steps and total_latency_ms). The hard rule on payload content is explicit in the migration comment and the JSDoc: metadata only - never user prompts, document text, or tool result bodies. Chat messages hold the content; duplicating it here would bloat the table and risk leaking PII to anyone querying the audit log.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?