Skip to content

Metrics catalogue

Every metric Lorebase emits, with the Prometheus series it becomes, its labels and where in the code it is recorded. The dashboards and alert rules in deploy/observability use only the series listed here; tests/tooling/test_observability_bundle.py fails when they name another one, and when an instrument in packages/*/src is missing from the tables.

Services export OpenTelemetry metrics over OTLP/gRPC to the collector when telemetry.otlp_endpoint is set (design 14; with it unset nothing is created). The collector's Prometheus exporter (deploy/observability/otel-collector.yaml, port 8889) is what Prometheus scrapes. Traces go to Jaeger or Tempo through the same collector.

Name conversion. Dots become underscores; the unit is appended (s becomes _seconds, ms becomes _milliseconds; counted units such as {job} add nothing); counters end in _total; histograms add _bucket, _sum and _count. Attribute names follow the same dot rule (http.route is http_route). With resource_to_telemetry_conversion every series also carries service_name (telemetry.service_name of the process) and service_namespace.

Keeper

Meter lorebase.keeper.

OpenTelemetry name Prometheus series Type Unit Labels Meaning Emitted by
http.server.request.duration http_server_request_duration_seconds (_bucket, _sum, _count) histogram s http_request_method, http_route, http_response_status_code Duration of every HTTP request until its response is complete (health checks included). http_route is the route template (/domains/{domain_id}/...), unmatched when no route matched. Its _count is the request count. lorebase_keeper.middleware.RequestContextMiddleware
lorebase.http.rate_limited lorebase_http_rate_limited_total counter {request} class Requests refused with 429 because a token bucket was empty. class is reads, writes, searches, context, admin, or auth_failures (the bucket of failed authentications, see operations). lorebase_keeper.ratelimit.limiter.RateLimiter
lorebase.http.quota_exceeded lorebase_http_quota_exceeded_total counter {request} quota Requests refused with 429 because an API key used up a daily quota. quota is requests or context_tokens. lorebase_keeper.ratelimit.quotas.QuotaTracker
lorebase.audit.write_failures lorebase_audit_write_failures_total counter {entry} reason Audit entries that could not be written (timeout or database error). reason is the exception class. lorebase_keeper.auth.audit.AuditWriter
lorebase.audit.reads.recorded lorebase_audit_reads_recorded_total counter {entry} none Read records written to audit_read_log (keeper.auth.audit.reads). lorebase_keeper.auth.read_audit.ReadAuditor
lorebase.audit.reads.dropped lorebase_audit_reads_dropped_total counter {entry} reason, sensitive Read records dropped: queue_full, write_failed (a batch failed or timed out, after a retry when it held a sensitive read) or shutdown; sensitive is true for reads that showed or masked a sensitive value. lorebase_keeper.auth.read_audit.ReadAuditor
lorebase.audit.reads.refused lorebase_audit_reads_refused_total counter {request} sensitive Reads answered 503 because their record found no place in the queue (on_overflow: reject). lorebase_keeper.auth.read_audit.ReadAuditor
lorebase.audit.reads.write_failures lorebase_audit_reads_write_failures_total counter {batch} none Failed attempts to write a batch of read records (with on_overflow: reject the batch is retried). lorebase_keeper.auth.read_audit.ReadAuditor
lorebase.trace.steps.recorded lorebase_trace_steps_recorded_total counter {step} none Agent trace steps written to agent_trace_steps (keeper.trace). lorebase_keeper.tracing.recorder.TraceRecorder
lorebase.trace.steps.dropped lorebase_trace_steps_dropped_total counter {step} reason Agent trace steps dropped: queue_full, write_failed, session_full (over max_steps_per_session) or shutdown. lorebase_keeper.tracing.recorder.TraceRecorder
lorebase.resolve.misses.recorded lorebase_resolve_misses_recorded_total counter {phrase} none Resolution misses written to resolve_misses (store.resolve_misses, design 8.10). lorebase_keeper.reading.misses.MissRecorder
lorebase.resolve.misses.dropped lorebase_resolve_misses_dropped_total counter {phrase} reason Resolution misses dropped: queue_full, domain_full (over max_per_domain), write_failed or shutdown. lorebase_keeper.reading.misses.MissRecorder
lorebase.ratelimit.shared.queries lorebase_ratelimit_shared_queries_total counter {query} store, outcome Queries to the shared rate limit buckets (backend: postgres). store is principals or auth_failures; outcome is ok, stale (a failed-authentication check decided from a recent reading during an outage), timeout or the exception class of a failure. lorebase_keeper.ratelimit.shared.PostgresRateStore
lorebase.db.reads lorebase_db_reads_total counter none source, reason Reads by the database that served them. source is primary or replica. reason is why: replica_fresh (a replica served it), no_replicas, not_preferred, stale, lagging, unreachable. lorebase_keeper.db.create_database (route callback of lorebase_store.replicas.ReadSessionFactory)
lorebase.db.replica.lag lorebase_db_replica_lag_milliseconds gauge ms replica Replay lag of each read replica at its latest probe. Absent while a replica has not answered a probe, and when no replica is configured (database.read_dsn). lorebase_keeper.db._observe_lag
lorebase.db.replica.up lorebase_db_replica_up gauge none replica 1 when the replica answered its latest probe, 0 otherwise. Absent when no replica is configured. lorebase_keeper.db._observe_lag
lorebase.keeper.cache.requests lorebase_keeper_cache_requests_total counter {request} result Lookups of the read response cache by GET reads (a 304 answered from If-None-Match is not counted). result is hit, miss or bypass (read response cache disabled). lorebase_keeper.reading.caching.respond_with
lorebase.auth.failures lorebase_auth_failures_total counter {request} reason Requests whose authentication failed. reason is missing (no credential), malformed (unsupported Authorization header, or a token not shaped like a key), method_not_accepted (API keys or bearer tokens not enabled), unknown_key, wrong_secret, revoked, expired, network (client address outside the key's allowed networks), principal_missing, invalid_token (OIDC token rejected) or no_account (valid token, no account and no auto provisioning; answered 403). lorebase_keeper.auth.principals.Authenticator

The refusal of clients that keep failing to authenticate is lorebase_http_rate_limited_total{class="auth_failures"}; it counts the requests answered 429, while lorebase_auth_failures_total counts the failed authentications.

Route classes

The latency histogram carries the route, not the rate limit class. The dashboards and alerts rebuild the classes from the method and the route suffix, with the same defaults as keeper.limits.rate.routes:

Class Selector
reads method GET, HEAD or OPTIONS (health checks excluded)
queries POST to a route ending in /query, /traverse or /neighborhood (rate limit class reads)
searches POST to a route ending in /search
context POST to a route ending in /context
writes any other POST, PUT, PATCH or DELETE

admin is chosen by OpenAPI tag and cannot be told apart in this metric. If you change the suffixes in keeper.limits.rate.routes, change the selectors in the dashboards and rules too.

Store

Meter lorebase.store (lorebase_store.telemetry). The store code runs in whichever process calls it: Keeper records commits, merges and reads, Scribe the commits of its jobs (Source syncs, extraction), job claims, garbage collection and webhook deliveries. Both export the series, so sum over service_name for the installation total.

OpenTelemetry name Prometheus series Type Unit Labels Meaning Emitted by
lorebase.store.commit.duration lorebase_store_commit_duration_seconds (_bucket, _sum, _count) histogram s origin Time of a successful commit through the commit pipeline (lock, validation, tree, Revision, materialization, ref swap, events). It does not include the caller's final transaction commit when the pipeline runs inside the caller's transaction. origin is the origin type of the Revision: operation, source_sync, extraction, schema_change, import, and so on. lorebase_store.commit.pipeline.commit
lorebase.store.merge.attempts lorebase_store_merge_attempts_total counter {attempt} result Merge runs of a Proposal (POST merge) by outcome. result is merged, fast_forward, up_to_date or conflicts (open Conflicts left, nothing written). lorebase_store.merging.service.MergeService.merge
lorebase.store.reads.definer lorebase_store_reads_definer_total counter {call} function Calls of the SECURITY DEFINER read functions, only when store.reads.use_definer_functions is on. function is lb_search, lb_links_in, lb_neighborhood, lb_query_by_attribute or one of their _overlay variants. lorebase_store.reads, lorebase_store.traverse
lorebase.jobs.claimed lorebase_jobs_claimed_total counter {job} type Jobs claimed by workers from the queue. lorebase_store.jobs.claim_jobs
lorebase.jobs.reclaimed lorebase_jobs_reclaimed_total counter {job} type, outcome Running jobs whose last heartbeat was older than store.jobs.stale_after_seconds (their worker is gone), taken back by the scheduler leader. outcome is requeued (queued again, due at once) or failed (store.jobs.max_attempts used). lorebase_store.jobs.requeue_stale
lorebase.jobs.released lorebase_jobs_released_total counter {job} type Running jobs a stopping worker put back in the queue without counting the attempt. lorebase_store.jobs.release_jobs
lorebase.webhooks.deliveries lorebase_webhooks_deliveries_total counter {delivery} outcome Webhook delivery attempts by outcome: delivered, retrying (failed, another attempt is scheduled) or dead (attempts used up, or the target was refused). lorebase_store.webhooks.delivery.deliver_pending
lorebase.outbox.handoffs lorebase_outbox_handoffs_total counter {event} consumer Outbox events a consumer handed off (for webhooks: fanned out into deliveries) and no longer has pending. lorebase_store.webhooks.delivery.deliver_pending
lorebase.outbox.pruned lorebase_outbox_pruned_total counter {event} reason Outbox events deleted. reason is retention (handed off by every consumer and older than store.outbox.retention_s) or expired (older than store.outbox.max_age_s while a consumer still had them pending: that consumer lost them). lorebase_store.webhooks.delivery.deliver_pending
lorebase.outbox.prune.backlog lorebase_outbox_prune_backlog gauge {event} Outbox events still eligible for pruning (see lorebase.outbox.pruned) after the latest pruning pass of the process, counted up to 100000 (PRUNE_BACKLOG_CAP), so a larger backlog reads 100000. Absent until the first pass. lorebase_store.webhooks.delivery.deliver_pending
lorebase.operations.events lorebase_operations_events_total counter {event} Operation side effect events written to the outbox by committed executions. lorebase_keeper.services.operations.execute
lorebase.webhooks.timeouts lorebase_webhooks_timeouts_total counter {attempt} Webhook delivery attempts cut off by webhooks.timeout_s (receiver gone, silent, dropping packets or slow); each is also counted in lorebase.webhooks.deliveries as retrying or dead. lorebase_store.webhooks.delivery.deliver_pending
lorebase.store.branch_jobs.rows lorebase_store_branch_jobs_rows_total counter {row} job Rows read by the background branch jobs (operations). job is materialize (base rows a copy went through), delete (rows of a deleted branch removed) or sensitivity_refresh (Records whose title and search vector were checked, lorebase_store.materialize.stale). lorebase_store.materialize.background
lorebase.store.branch_jobs.runs lorebase_store_branch_jobs_runs_total counter {run} job, outcome Runs of the background branch jobs. outcome is done, deferred (no free slot of store.materialize.job_concurrency, or the branch stayed locked; queued again), waiting (a deletion waits for the copies of overlays based on the branch; queued again), skipped (another job works on the branch), cancelled (the branch is gone, or is not being deleted) or failed (a run raised); for copies also retried (its job used every attempt; queued again with backoff), fallback (failed copy_max_failures times: the overlay was detached and is built from its tree) and given_up (a build failed as often; the branch stays building). lorebase_store.materialize.background
lorebase.store.branch_jobs.batch.rows lorebase_store_branch_jobs_batch_rows (_bucket, _sum, _count) histogram {row} job Rows a batch of a background branch job was sized for: between job_batch_min_rows and job_batch_rows, smaller while commits wait for the branch lock. lorebase_store.materialize.background
lorebase.store.branch_jobs.batch.duration lorebase_store_branch_jobs_batch_duration_seconds (_bucket, _sum, _count) histogram s job One batch transaction of a background branch job. Batches close to database.statement_timeout_ms call for a lower store.materialize.job_batch_rows. lorebase_store.materialize.background
lorebase.gc.runs lorebase_gc_runs_total counter {run} dry_run Completed garbage collections of a Domain. dry_run is true or false. lorebase_store.gc.collect_garbage
lorebase.gc.freed_bytes lorebase_gc_freed_bytes_total counter By content Bytes of tree nodes and objects deleted by garbage collection; dry runs add nothing. content is nodes or objects. The name already ends in bytes, so the conversion adds nothing to it. lorebase_store.gc.collect_garbage

Scribe

Meter lorebase.scribe (lorebase_scribe.telemetry).

OpenTelemetry name Prometheus series Type Unit Labels Meaning Emitted by
lorebase.scribe.jobs lorebase_scribe_jobs_total counter {job} type, status Job runs. type is source_sync, webhook_delivery, store.index, store.materialize, store.branch_delete, gc, extraction, embeddings, reconcile_sweep, audit_verify or audit_checkpoint. status is the state after the run: succeeded, queued (failed and requeued for another attempt: a retry), failed (attempts used up, or a permanent error), lost (the job was reclaimed from this worker while it ran, see lorebase.jobs.reclaimed, so its outcome was not recorded) or unrecorded (the store refused to take the outcome twice; the job runs again once its lock goes stale, see Scribe). lorebase_scribe.worker.Worker
lorebase.scribe.job.duration lorebase_scribe_job_duration_seconds (_bucket, _sum, _count) histogram s type Wall time of one run of a job, including the write of its outcome. lorebase_scribe.worker.Worker
lorebase.scribe.worker.slots lorebase_scribe_worker_slots gauge {slot} state Slots of this process's job loop: live (claiming and running jobs) or lost (ended with an error; started again after scribe.worker.poll_interval_s). Absent before the loop starts and after it stops. lorebase_scribe.worker.Worker
lorebase.scribe.scheduled lorebase_scribe_scheduled_total counter {job} none Jobs the scheduler queued in one tick (the webhook delivery pass, scheduled Source syncs, garbage collection, embedding passes). Jobs queued by the API are not counted. lorebase_scribe.scheduler.Scheduler
lorebase.gc.scheduled lorebase_gc_scheduled_total counter {job} dry_run gc jobs the scheduler queued at scribe.scheduler.gc_schedule, one per Domain and firing. dry_run follows scribe.scheduler.gc_dry_run (true or false). The series starts at 0 on the leader while a schedule is set, and is absent when gc_schedule is null. Collections queued by hand are not counted. lorebase_scribe.scheduler.Scheduler
lorebase.scribe.source.rows lorebase_scribe_source_rows_total counter {row} lorebase_source Rows read from a Source by sync runs. lorebase_scribe.sources.sync.SourceSync
lorebase.scribe.source.changes lorebase_scribe_source_changes_total counter {change} lorebase_source, dry_run Data changes a Source run computed against the Domain (committed unless dry_run is true). lorebase_scribe.sources.sync.SourceSync
lorebase.jobs.queue_depth lorebase_jobs_queue_depth gauge {job} type, status Rows of the jobs table waiting or running, counted by the scheduler leader at each tick (scribe.scheduler.tick_s). status is queued (claimable now), retrying (queued with a run_after still ahead: a failed job waiting for store.jobs.retry_delay_seconds, or a job deferred to later) or running. Only the leader reports it, so it is absent when no Scribe holds leadership; a type and status pair that empties reports 0. lorebase_scribe.scheduler.Scheduler
lorebase.extraction.evidence_withheld lorebase_extraction_evidence_withheld_total counter {observation} none Schema suggestion observations kept without evidence because it held a sensitive value. For operators only; never exposed per job or per observation. lorebase_scribe.extraction.pipeline
lorebase.extraction.items lorebase_extraction_items_total counter {item} tier, decision Items an extraction job proposes. tier is 0, 1, 2 or none (items without a tier). decision is accepted (accepted at once by the confidence rules) or pending (left for review). lorebase_scribe.extraction.pipeline
lorebase.scribe.embeddings.records lorebase_scribe_embeddings_records_total counter {record} outcome Records an embedding pass rendered. outcome is embedded (sent to the model), reused (the vector of an identical text was copied), unchanged (the stored vector has the same text) or empty (the template rendered nothing). lorebase_scribe.embeddings.EmbeddingPass
lorebase.scribe.embeddings.call.duration lorebase_scribe_embeddings_call_duration_seconds (_bucket, _sum, _count) histogram s none Duration of one call of the embedding model by an embedding job, failed attempts included. lorebase_scribe.embeddings.EmbeddingPass
lorebase.scribe.reconciliation.candidates lorebase_scribe_reconciliation_candidates_total counter {candidate} type, outcome Reconciliation candidates the detection stages produced (design 10.6). type is duplicate, contradiction, superseded or stale; outcome is raised (a new candidate), claim_added (a known candidate gained a claim), filtered (values that differ only until normalized, counted as contradiction, or text values the semantic stage found at semantic.equivalence_threshold or more similar, counted with their candidate type) or obsolete (the sweep found the disagreement gone). A candidate seen again unchanged is not counted. lorebase_scribe.reconcile.detector.Detector
lorebase.scribe.reconciliation.sweep.duration lorebase_scribe_reconciliation_sweep_duration_seconds (_bucket, _sum, _count) histogram s none Wall time of one reconcile_sweep job (a continuation is a job of its own). lorebase_scribe.reconcile.sweep.ReconcileSweep
lorebase.scribe.reconciliation.open lorebase_scribe_reconciliation_open gauge {candidate} domain, type Open reconciliation candidates by Domain and type, counted at the end of each complete reconcile_sweep in the process that ran it; a type that empties reports 0. Absent until a sweep of the Domain completed in the process. lorebase_scribe.reconcile.sweep.ReconcileSweep
lorebase.audit.verifications lorebase_audit_verifications_total counter {verification} outcome, stream Verifications of every audit hash chain by audit_verify jobs. outcome is intact or broken (an entry does not verify; the job result names the first one); stream is actions or reads. lorebase_scribe.jobs
lorebase.audit.checkpoints lorebase_audit_checkpoints_total counter {checkpoint} outcome Audit checkpoints of audit_checkpoint jobs: published (one per stream) or failed (a job attempt that stored none). lorebase_scribe.jobs

Job counts per type answer most questions about the work: runs of source_sync are Source sync runs, extraction is extraction throughput, webhook_delivery is the delivery pass, gc is garbage collection, embeddings the embedding passes (a pass larger than scribe.embeddings.max_records_per_job runs as several jobs). A retry is a queued run.

Guide

Meter lorebase.guide (lorebase_guide.telemetry), exported by the optional Guide service.

OpenTelemetry name Prometheus series Type Unit Labels Meaning Emitted by
lorebase.guide.requests lorebase_guide_requests_total counter {request} route, status HTTP requests answered by Guide. lorebase_guide.app
lorebase.guide.request.duration lorebase_guide_request_duration_seconds (_bucket, _sum, _count) histogram s route Duration of one HTTP request; for /explore, until the stream ends. lorebase_guide.app
lorebase.guide.explorations lorebase_guide_explorations_total counter {exploration} outcome Explorations by outcome (answered, not_found, budget, timeout, model, ...). lorebase_guide.app
lorebase.guide.steps lorebase_guide_steps_total counter {call} tool, ok Keeper reads the model asked for. lorebase_guide.agent.Explorer
lorebase.guide.tokens lorebase_guide_tokens_total counter {token} direction Model tokens, input or output. lorebase_guide.agent.Explorer
lorebase.guide.embeddings lorebase_guide_embeddings_total counter {text} mode Texts embedded for the web app's semantic search. lorebase_guide.app
lorebase.guide.refused lorebase_guide_refused_total counter {request} reason Requests refused before any work (auth, origin, rate, busy, ...). lorebase_guide.app

Spans

Traces use the same pipeline. Instrumentation names lorebase.keeper and lorebase.scribe.

Span Attributes Emitted by
{METHOD} {route} (server span of each HTTP request; the W3C trace context of the caller is continued) http.request.method, url.path, http.route, http.response.status_code, lorebase.request_id; status error on 5xx lorebase_keeper.middleware.RequestContextMiddleware
scribe.job {type} lorebase.job.type, lorebase.job.id, lorebase.domain, lorebase.job.status; error status and the exception when the attempt fails lorebase_scribe.worker.Worker
scribe.source_sync lorebase.domain, lorebase.source, lorebase.run, lorebase.dry_run, lorebase.changes lorebase_scribe.sources.sync.SourceSync
scribe.scheduler.tick none lorebase_scribe.scheduler.Scheduler
scribe.embeddings lorebase.domain, lorebase.complete lorebase_scribe.embeddings.EmbeddingPass
scribe.reconcile_sweep lorebase.domain, lorebase.branch, lorebase.complete lorebase_scribe.reconcile.sweep.ReconcileSweep

The spans of design 14 beyond these (authentication, query compile, SQL, commit and merge phases, extraction steps) are not emitted yet. Herald emits no telemetry of its own.

Histogram buckets

The latency histograms (http_server_request_duration_seconds, lorebase_scribe_job_duration_seconds and lorebase_store_commit_duration_seconds) are recorded in seconds with explicit boundaries: 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5 and 10 (lorebase_platform.telemetry.LATENCY_BUCKETS_S, declared through explicit_bucket_boundaries_advisory). Everything slower than 10 seconds lands in the +Inf bucket, where histogram_quantile returns the highest finite boundary. The p50 and p95 panels of the dashboards and the latency alerts are meaningful between 5 ms and 10 s, and every budget they use (100 ms, 250 ms, 1 s, 2.5 s) is a boundary.

Not emitted yet

Design 14 lists signals that no code records, and the dashboards and alerts do not cover them: database pool usage, extraction tokens and cost, changes per commit, Conflicts by code, schema cache hits, and spans beyond those listed above.