Usage events
The append-only record of metered work, kept for operational accounting and product insight.
zero-memory records every unit of metered work as a row in an append-only
table, public.usage_events: an LLM extraction, an embedding batch, an MCP
tool call. The table is the deployment's own operational accounting ledger. It
stays inside your database, and it never contains memory content.
Why the ledger starts on day one
Usage history is unrecoverable: if events are not written from day one, the data simply never existed. A single append-only table, captured from the start, covers two needs at once. Operational accounting asks how much work ran, for whom, in what units; product insight asks which tools and paths are actually used. Neither one ships anything to an external analytics service.
Rows, types, and attribution
The table
Each event is one row:
| Column | Meaning |
|---|---|
occurred_at | When the work happened |
user_id | Whom the work was for |
agent_name | The agent principal, when the caller is an agent |
event_type | What kind of work (see below) |
quantity | How much of it (defaults to 1) |
unit | count or tokens |
metadata | Small structured context — normally counters and identifiers; recall queries are the bounded exception described below |
request_id | Correlation id shared with logs and the audit log |
The table is append-only and indexed by time and by (user_id, occurred_at).
Row-level security is enabled with no policies, a deliberate deny-all:
only the server's service role can access the table directly. End users never
query raw rows; owner-scoped dashboard/activity RPCs expose only the caller's
derived view.
Event types
| Event type | Emitted when | Unit |
|---|---|---|
llm_extraction | A metered LLM call completes — ingest extraction, translation, or background judging (hygiene, usefulness, benchmark); metadata carries the input/output token split, the model id, and a purpose tag | tokens |
embedding | An embedding batch runs; quantity is the number of texts | count |
mcp_tool_call | Any MCP tool is dispatched; metadata carries the tool name and bounded result counters. Read tools add the scope-usage audit trail: read_mode (explicit, all, hint, session, or open), the session's attached project at call time, and a per-scope count of what was returned | count |
ingest_chunk | The ingest path accepts a chunk | count |
session_briefing | A briefing build_context call is delivered | tokens |
recall_used | A recall outcome is attributed: a later remember supersedes or derives from a recalled memory, the usefulness judge credits it, or a challenge records the negative counterpart (valence: misled) | count |
Emission is centralized on purpose: MCP tool calls are counted once at the tool dispatcher, not inside every tool, so adding a tool never requires adding instrumentation.
Recording is fire-and-forget
Writes go through a small domain port (UsageRecorder) whose calls are never
awaited on the critical path. If the ledger write fails, the failure is logged
and the primary operation completes normally, whether that is the recall, the
remember or the ingest. Accounting must never break the product.
Attribution
user_id comes from the authenticated request context. Background work (for
example, watcher passes) runs outside any request but always knows whose
corpus it touched, so it passes the subject explicitly. Otherwise the ledger
and any check against it would describe different things. agent_name is set
when the actor is an agent principal, and
request_id ties the event to the same request in the logs — see
Observability.
Questions the ledger answers
Query the table directly with operator (service-role) access. Grouping by
event_type and unit over a time window gives volume by type: extraction
token spend, embedding volume, and tool-call counts. The
(user_id, occurred_at) index makes per-user accounting cheap. Filtering by
request_id traces one request, showing every unit of work it produced
alongside its log lines and audit entries.
The scope-usage audit has the same shape. Group read-tool rows by read_mode,
or unnest the per-scope return counts, to answer "are sessions reading pinned
to their project, or degrading to everything visible?" The fields are recorded
at the source, so the audit is a GROUP BY instead of a fragile
reconstruction.
One thing is deliberately not in this ledger: refused writes. A refused
remember emits no usage event. It is counted by the
mcp_write_refused_total Prometheus counter, labelled by refusal reason
(see Observability).
Higher-level questions, about what the memory actually contributed rather than
what it cost, are covered by Value metering,
which builds on this ledger's recall_used signal.
What the ledger refuses
The ledger is an own table rather than external analytics. A hosted analytics service would mean a premature dependency and data leaving the deployment. One append-only table covers both accounting and funnel analysis, and keeps a self-hosted installation self-contained.
Memory content never enters the ledger. Metadata is normally restricted to
counters and identifiers: token counts, model ids, tool names, and returned
memory ids. The deliberate exception is the user's own recall query,
truncated to 200 characters so the activity feed can explain an empty search.
It is stored exactly as it was sent, which is also exactly what was searched,
since nothing rewrites a query. Treat usage retention as operational metadata
retention, not as a content-free guarantee.
Deny-all RLS is a pattern here. Enabling row-level security while creating no policies is the standard shape for operational tables: nothing short of the service role can touch them, and the posture is explicit in the schema rather than implied by omission.