Observability reference v7

AIDB emits OpenTelemetry (OTel) traces, logs, and metrics for its own function calls, background workers, and SQL query execution. For the GUCs that turn this feature on and configure it, see Configuring AIDB parameters — OpenTelemetry tracing.

Schemas

When aidb.otel_client = 'database', AIDB writes traces, logs, and metrics into three tables in the aidb_otel schema:

  • aidb_otel.traces — one row per span.
  • aidb_otel.logs — one row per log record.
  • aidb_otel.metrics — one row per batch of metric readings, such as a periodic collection of instrument values.

A span describes an operation and its timing. AIDB stores log messages as separate OTel log records, not as events embedded inside spans. A log record can link to its span through the trace_id and span_id columns. Governance decisions are spans named purpose_decision in aidb_otel.traces.

This follows OpenTelemetry's recommendation to record new events as logs correlated with spans. The Span Events API is being deprecated; this doesn't remove span events from the OTel data model or invalidate existing data.

EDB Agent Governance reads spans and log records directly from the database. It doesn't require an OTLP collector and doesn't display all AIDB telemetry. Export to an external monitoring system is configured separately through the exporter worker.

By default, AIDB grants read access to these tables to aidb_governance, not to aidb_users. Administrators can grant a separate login role read access to the tables it needs. For the dashboard, use the dedicated viewer role: membership in aidb_governance also permits retention cleanup and purpose management.

All three tables share a common set of columns, plus a few columns specific to each.

Common columns

Every row in all three tables carries these columns:

ColumnTypeDescription
idbigintRow identity.
recorded_attimestamptzWhen the row was written.
payload_jsonjsonbThe item as an OTLP protojson Export{Metrics,Logs,Trace}ServiceRequest. Populated when aidb.otel_client_storage_format is json or both.
payload_binarybyteaThe same item as raw OTLP protobuf wire bytes. Populated when aidb.otel_client_storage_format is binary or both.
export_statustextpending, exported, or failed. Only meaningful when the exporter worker is enabled — stays pending forever otherwise.
export_attemptsintegerNumber of export attempts made so far.
next_export_attempt_attimestamptzEarliest time the exporter will retry this row. NULL once exported or permanently failed.
last_export_errortextError message from the most recent failed export attempt.
sourcetextinternal for telemetry emitted by AIDB itself; external for telemetry submitted by other callers. Added in AIDB 7.9.0.
principaltextThe database login (session_user) that wrote the row, for both internal and external telemetry. This is separate from the calling user recorded in a governance decision. Added in AIDB 7.9.0.

At least one of payload_json/payload_binary is always populated — see Storage format.

Starting in 7.9.0, use source to distinguish AIDB's own decisions from decisions reported by external senders. Rows retained from an earlier version are labeled internal during upgrade and have no principal; their original source can't be verified from those columns.

aidb_otel.traces — additional columns

Each row is exactly one span. The identifier and attribute columns are stored separately from the payload, so you can query them without parsing JSON or protobuf.

ColumnTypeDescription
trace_idtext32-character lowercase hex trace ID. Indexed (traces_trace_id_idx).
span_idtext16-character lowercase hex span ID.
parent_span_idtext16-character lowercase hex parent span ID. NULL for a root span.
pg_session_idtextThe db.postgresql.session_id attribute, when present. Partial index excluding NULL (traces_pg_session_id_idx).
gen_ai_conversation_idtextThe gen_ai.conversation.id attribute, when present. Partial index excluding NULL (traces_gen_ai_conversation_id_idx).
mcp_session_idtextThe mcp.session.id attribute, when present. Partial index excluding NULL (traces_mcp_session_id_idx).
decision_statustextThe aidb.agent.decision.action_status attribute (allowed or denied), when present — only the one purpose_decision span per enforcement decision carries it. Partial index excluding NULL (traces_decision_status_idx). Lets a per-agent allowed/denied rollup over a time window use an index range scan instead of scanning payload_json. See Purpose-enforcement decisions.
decision_idtextThe aidb.agent.decision.decision_id attribute, when present. Links a denied call to its decision record.
agent_nametextThe gen_ai.agent.name attribute, when present.
span_status_codeintegerOTel span status: 0 (unset), 1 (OK), or 2 (error). This is separate from the governance decision's allowed/denied status.
span_start_timetimestamptzThe start time supplied by an external telemetry sender. NULL for AIDB's own stored spans; their timing remains in the payload. Added in AIDB 7.9.0.
span_end_timetimestamptzThe end time supplied by an external telemetry sender. NULL for AIDB's own stored spans. Added in AIDB 7.9.0.

recorded_at is the time the row was stored, which can be later than the operation itself. Use the span's start and end times to measure its duration.

trace_id/span_id/parent_span_id are lowercase hex, the same representation payload_json's own traceId/spanId/parentSpanId fields use — the two match as strings, so you can look up a row by either. (This isn't the same as payload_binary's raw OTLP protobuf bytes fields, which aren't hex or text at all.)

aidb_otel.logs — additional columns

Each row is one log record, with two columns linking it back to the span it was emitted under:

ColumnTypeDescription
trace_idtextTrace ID of the span this log record was emitted under. NULL if emitted outside any active span. Indexed (logs_trace_id_idx).
span_idtextSpan ID of the span this log record was emitted under. NULL under the same condition as trace_id.

Configuring storage format

The aidb.otel_client_storage_format GUC controls which payload column the database client (one of the aidb.otel_client destinations — see Configuring AIDB parameters) writes to: payload_json, payload_binary, or both — the two columns described under Common columns. Values:

Valuepayload_jsonpayload_binaryNotes
json✅—Default. Human-readable, directly queryable with standard jsonb operators.
binary—✅Raw OTLP protobuf. Cheaper to produce (no intermediate JSON tree), but not directly queryable.
both✅✅Both populated from the same underlying data. Roughly doubles storage and write cost.

aidb.otel_client_storage_format only affects the database client (aidb.otel_client = 'database'). It has no effect on noop (nothing is written), stdout or grpc (both always emit the full OTLP payload directly, not through these columns), or log, which always writes payload_json regardless of this setting, since a server log line is a human-readable debugging aid, not storage a collector later forwards.

When exporting a row that has both payloads populated (storage_format = both), the exporter worker sends only payload_binary, since it's cheaper to have produced.

Starting in 7.9.0, telemetry ingested from external senders retains the format received: JSON or protobuf. aidb.otel_client_storage_format controls AIDB's own telemetry, not these incoming records.

Triggering retention cleanup

The cleaner background worker deletes old rows automatically (see Running background workers), but this function lets you trigger a cleanup pass immediately instead of waiting for its next tick.

aidb.run_otel_retention_cleanup()

Manually triggers a full retention cleanup pass, deleting rows older than aidb.otel_cleaner_retention_days from all three aidb_otel tables, in the same bounded batches the cleaner background worker uses. Runs immediately, regardless of aidb.otel_cleaner_enabled — useful for an ops task or a test that shouldn't wait for the worker's own wake-up interval.

Requires the aidb_governance role (see Managing audit access) — aidb_users has no EXECUTE on this function.

Takes no arguments and returns void. Safe to call while the cleaner background worker is running — the two don't compete over the same rows.

SELECT aidb.run_otel_retention_cleanup();
Note

There's no equivalent manual trigger for the exporter. Forwarding to an external collector only ever runs from the exporter worker's own tick, so that a delivery in progress never shares a transaction with anything else that could roll it back and risk sending the same row twice.

Internally, the database client writes through aidb_otel.store_metrics(), aidb_otel.store_logs(), and aidb_otel.store_traces(). These aren't meant to be called directly — aidb_users has EXECUTE on them (needed for any traced call to work) but no SELECT on the tables they write to.

Running background workers

Two background workers run per database, each spawned only when enabled for that database (see Configuring AIDB parameters for the governing GUCs).

Exporting traces, logs, and metrics

Export is disabled by default. Enable aidb.otel_exporter_enabled and configure aidb.otel_exporter_endpoint to forward stored telemetry. EDB Agent Governance reads the database directly and doesn't need this worker.

Forwards pending, due rows from aidb_otel.{traces,logs,metrics} to the OTLP/HTTP collector at aidb.otel_exporter_endpoint, on a 15-second tick. payload_json rows are sent as OTLP/HTTP+JSON. payload_binary rows are sent as OTLP/HTTP+protobuf (Content-Type: application/x-protobuf) — a real OTel Collector's HTTP receiver dispatches on that header, so both formats share the same endpoint. Every outbound request goes through AIDB's egress allowlist.

Failed deliveries back off per row (not per tick), so one row's retry schedule never blocks the rest of the batch. After aidb.otel_exporter_max_retries attempts, a row is marked export_status = 'failed' and stops being retried automatically — find these with:

SELECT * FROM aidb_otel.traces WHERE export_status = 'failed';

Cleaning up expired rows

Deletes rows older than aidb.otel_cleaner_retention_days from all three tables, in batches of aidb.otel_cleaner_batch_size, on an hourly tick. Set aidb.otel_cleaner_retention_days = 0 to disable cleanup entirely without stopping the worker itself.

Traced attributes

AIDB records attributes automatically when telemetry is enabled. It follows OpenTelemetry semantic conventions where applicable (gen_ai.*, db.*, error.type), with custom aidb.* attributes for other AIDB concepts. The reference below covers the attributes emitted by AIDB on spans, logs, and metrics. Each section identifies where they appear; an attribute can be absent when it doesn't apply to the operation.

Database telemetry from AIDB 7.8.0 uses schema version 1; AIDB 7.9.0 introduces schema version 2. The schema version identifies the telemetry structure and changes independently of the AIDB release number. Upgrading doesn't add new attributes to older records. These fields are available in the stored telemetry; the dashboard displays a subset.

Setting resource attributes

Set once per process, shared by every signal (traces, logs, and metrics):

AttributeDescription
service.versionThe AIDB extension version that produced the data.
service.nameAlways aidb. Identifies AIDB when a monitoring system receives data from multiple services.
aidb.otel_schema_versionThe telemetry schema version: 1 in AIDB 7.8.0 and 2 starting in 7.9.0.
aidb.database.nameName of the database the backend is connected to.
aidb.database.oidOID of that database.

Setting root-span attribute

Set once per trace, only on its root span:

AttributeDescription
db.postgresql.session_idCorrelates a trace with the Postgres server log lines from the same backend. See below for the format and where it's set.

db.postgresql.session_id uses the format <start_time_hex>.<pid_hex>, matching Postgres's own %c log_line_prefix escape. It's per-connection, so it's set only on each trace's root span — not on every span, and not as a resource attribute — since sharing it with the metrics pipeline would inflate metric cardinality. It's promoted to the indexed pg_session_id column on aidb_otel.traces.

Recording Agent Hub attributes

Set on spans for agent conversations, tasks, and messages:

AttributeDescription
gen_ai.operation.nameStatic operation label on agent conversation spans.
gen_ai.agent.nameAgent name.
gen_ai.agent.idAgent ID.
gen_ai.conversation.idConversation ID. Promoted to the indexed gen_ai_conversation_id column on aidb_otel.traces.
aidb.agent.modelModel used by the agent.
aidb.agent.purposeThe agent's declared purpose (see Permissions).
aidb.agent.statusOutcome of the agent call: COMPLETE, TIMEOUT, or ERROR.
aidb.agent.task.idID of one agent invocation, distinct from the conversation ID. Absent for read-only calls.
aidb.agent.message.idID of the message requested by a get_message lookup.
aidb.agent.system_prompt.charsCharacter count of the system prompt, including agent instructions and attached plugins. Added in AIDB 7.9.0.
aidb.agent.plugin.countNumber of plugins attached to the agent. Added in AIDB 7.9.0.
aidb.read_onlyWhether the call was read-only. Shared with Tools Hub — not agent-exclusive.
error.typeError classification on a failed call: timeout or _OTHER. The error message is recorded separately in logs.

Recording Tools Hub attributes

Set on spans for tool calls, including those imported from an MCP server:

AttributeDescription
gen_ai.tool.nameTool name.
aidb.mcp.server_nameName of the MCP server a tool was imported from.
aidb.mcp.urlMCP server URL. Recorded as-is, without redaction; restrict read access to telemetry accordingly.
aidb.mcp.transportMCP transport: streamable_http or sse.
aidb.tool.call_countNumber of tool calls in a batch invocation.
aidb.sleep.secondsRequested duration, in seconds, of a native pause/sleep tool call.
mcp.session.idMCP session ID, when a tool call happens within one. Promoted to the indexed mcp_session_id column on aidb_otel.traces.

Recording purpose-enforcement decisions

With telemetry enabled, purpose-scoped tool SQL produces purpose_decision spans with these attributes. Agents without a purpose don't produce these decision records.

AttributeDescription
aidb.agent.decision.decision_idUnique ID for this decision. Also embedded in the error message text when the action is denied, so an error can be linked back to its decision record.
aidb.agent.decision.action_statusallowed or denied — the only two values. Promoted to the indexed decision_status column on aidb_otel.traces.
aidb.agent.decision.interaction_pointThe kind of interaction being decided. Currently always sql_exec (SQL execution by a tool) — the only interaction point implemented so far.
aidb.agent.decision.principalThe calling user's Postgres identity, captured before switching to the purpose's role.
aidb.agent.decision.agent_roleThe purpose-resolved Postgres role the action ran (or would have run) as.
aidb.agent.decision.denial_reasonPresent only when denied. One of role_not_found (the purpose's role doesn't exist), not_role_member (the caller isn't a member of the purpose's role), escalation_blocked (the SQL tried to change the running role with SET ROLE, RESET ROLE, or SET SESSION AUTHORIZATION, or to change a privilege-restricted setting), engine_privilege_denied (Postgres rejected the SQL with a 42501 permission error), or pre_execution_rejected (the tool SQL failed validation before execution). Not promoted to its own column.
aidb.agent.decision.effective_purposeThe purpose actually enforced for the decision. It can differ from the agent's configured purpose when a delegated agent runs under its caller's purpose. Added in AIDB 7.8.0.
aidb.agent.decision.referenced_relationsA JSON array stored as a string, listing distinct schema-qualified relations named by the SQL statement after parsing and analysis. The list is limited to 5 KiB. Added in AIDB 7.9.0.
aidb.agent.decision.referenced_relations_totalThe number of distinct relations before the list is shortened to fit the limit. Added in AIDB 7.9.0.
aidb.agent.decision.denial_classA category for a PostgreSQL privilege error, such as object_privilege, sequence_privilege, or rls_policy. Added in AIDB 7.9.0.
aidb.agent.decision.denial_funcnameThe PostgreSQL function reported as the source of the error, when available. Useful when investigating a denial. Added in AIDB 7.9.0.
aidb.agent.decision.denial_messageThe database's error message, limited to 5 KiB. It can contain sensitive data. Added in AIDB 7.9.0.
db.response.status_codeThe PostgreSQL SQLSTATE. On a decision span, this is 42501 for a PostgreSQL privilege error. Added in AIDB 7.9.0.

Referenced relations describe the submitted statement, including relations in joins and subqueries. They aren't a complete record of indirect access through functions or views, and they don't identify the object that caused a denial. An absent attribute means relation information wasn't captured. An empty list means the captured statement named no relations.

The denial class, function name, message, and SQLSTATE apply to PostgreSQL privilege errors. They aren't recorded for decisions rejected before execution, such as a missing purpose role or a caller who isn't a member of that role. See Decision records for interpreting the outcome.

Tracing SQL queries

Set on any span that executes SQL:

AttributeDescription
db.query.textThe SQL statement(s) executed during the traced call. Bound parameter values are included only when aidb.otel_capture_query_parameters is on (see Configuring AIDB parameters).

Tracing pipelines, semantic KBs, and models

These spans cover pipeline operations, semantic search, model inference and maintenance, data processing, and volume access.

AttributeDescription
aidb.pipeline.namePipeline name, on pipeline CRUD and execution spans.
aidb.pipeline.idPipeline ID, on background worker batch-processing spans.
aidb.semantic_kb.nameSemantic knowledge base name.
aidb.model.nameModel name, on model inference and maintenance spans.
aidb.model.providerThe model provider or adapter used for the call.
aidb.hcp.model_nameModel name used by the remote service. It can differ from AIDB's registered model name.
aidb.semantic_alias.nameNamed alias over one or more semantic knowledge bases.
aidb.search.top_kRequested result count for a semantic knowledge base search.
aidb.search.max_resultsRequested result count for a memory-search tool call.
aidb.volume.nameVolume targeted by a file operation.
aidb.volume.file_nameFile targeted by a read, write, or delete operation within the volume.

Recording memory activity

Set on spans for memory and memory-session operations:

AttributeDescription
aidb.memory.nameNamed memory instance used by the call.
aidb.memory.providerConfigured memory runtime, when one is configured.
aidb.memory.namespaceNamespace for a memory session or message operation.
aidb.memory.actor_idUser or agent identity associated with the memory session.
aidb.memory.session_idMemory session read or written by the operation.
aidb.memory.session_closedWhether the session was already closed when a close operation was requested.
aidb.memory.turnsNumber of conversation turns in the memory session at initialization or read time.
aidb.memory.limitItem or character limit applied to a session read.
aidb.memory.itemsNumber of items returned by a session read.
aidb.memory.overflow_charsNumber of characters removed to stay within the read's character limit.
aidb.memory.compacted_entriesNumber of entries combined into a summary during a session read or close.
aidb.memory.warningsNumber of non-fatal warnings during a session read.
aidb.memory.compactingWhether the session read triggered a model call to summarize memory. Also appears on memory metrics when applicable.

Recording memory metrics

The aidb.memory.session.operations counter counts completed memory operations. The aidb.memory.session.operation.duration histogram records their duration in seconds. Their data points carry these attributes:

AttributeDescription
aidb.memory.operationsession_get, session_start, or session_end.
aidb.memory.outcomeok or warning. Failed operations aren't included in these metrics.
aidb.memory.compactingWhether a session read triggered a model call to summarize memory. Absent for operations where this doesn't apply, such as opening or closing a session.

Recording registry and catalog activity

Set on spans for catalog discovery and purpose or plugin management:

AttributeDescription
aidb.catalog.nameName of the catalog object being inspected.
aidb.catalog.schemaSchema searched by the catalog discovery call.
aidb.catalog.object_typeType of object, such as a table, view, function, or sequence.
aidb.purpose.namePurpose being created or read.
aidb.purpose.rolePostgres role assigned to a purpose when it is created.
aidb.agent_plugin.nameAgent plugin being created, updated, or deleted. Added in AIDB 7.9.0.

Recording failures as logs

Failure log records can carry an error attribute containing the error text. This is separate from the span's error.type classification. When the log is emitted within a span, its trace and span IDs link it to that operation. Error text can contain sensitive data; restrict read access to the telemetry accordingly.