Skip to main content

Data model

The Postgres schema is the source of truth for everything Eden stores. This page walks the major tables and how they relate.

Core tables

telemetry_events

Every event from every SDK lands here. One row per event.

ColumnTypeNotes
event_idtextevt_<sha256[:16]> — content-derived
org_iduuidRLS-scoped
trace_idtexttrc_<sha256[:16]>
span_idtextspn_<sha256[:16]>
parent_span_idtextnullable
source_formattextopenai, anthropic, langchain, custom, etc.
nametexte.g. openai.chat.completions, checkout
modeltextnullable
outcometextsuccess, error, timeout
started_attimestamptz
ended_attimestamptz
duration_msinteger
input_tokensintegernullable
output_tokensintegernullable
cost_usdnumeric(12,8)nullable
prompttextPII-redacted
responsetextPII-redacted
attributesjsonbfree-form
tagstext[]
created_attimestamptzdefaults to now()

Indexes: (org_id, started_at DESC), (org_id, trace_id), (org_id, agent_id, started_at DESC), (org_id, model, started_at DESC).

judge_scores

One row per judge scoring, joined to telemetry_events on trace_id.

ColumnType
score_idtext (sc_<sha256[:16]>)
org_iduuid
judge_idtext
trace_idtext
span_idtext
scorenumeric(5,2)
passedboolean
rubricjsonb
reasoningtext
created_attimestamptz

usage_events

Token and cost rollups — the source for cost intelligence.

ColumnType
usage_idtext (usg_<sha256[:16]>)
org_iduuid
agent_idtext
modeltext
event_typetext
quantityinteger
cost_usdnumeric(12,8)
attributesjsonb
created_attimestamptz

agents

Agent registry — one row per registered agent.

ColumnType
agent_idtext
org_iduuid
nametext
frameworktext
versiontext
repo_urltext
tagstext[]
metadatajsonb
registered_attimestamptz
last_seen_attimestamptz
archived_attimestamptz

notifications

Alerts, in-app notifications, and the alert queue. Same table, discriminated by type.

ColumnType
iduuid
org_iduuid
user_iduuid
typetext
titletext
bodytext
datajsonb
read_attimestamptz
created_attimestamptz

org_audit_log

Append-only audit log.

ColumnType
iduuid
org_iduuid
user_iduuid
user_emailtext
actiontext
target_typetext
target_idtext
metadatajsonb
ip_addressinet
user_agenttext
created_attimestamptz

pii_redactions

Compliance evidence pack for SOC 2 / HIPAA / GDPR.

ColumnType
redaction_iduuid
org_iduuid
event_idtext
kindtext
fieldtext
created_attimestamptz

Intelligence tables

Each intelligence service has its own table set. The full list:

ServiceTables
Trace replaytrace_replays, trace_replay_steps
RCA & incidentsincident_clusters, incident_alerts, incident_summaries
Cost intelligencecost_summaries_daily, cost_anomalies, cost_forecasts
Retrieval intelligenceretrieval_logs, retrieval_gaps, chunk_attributions
Memory intelligencememory_attributions, memory_timeline, memory_impacts
Agent intelligenceagent_performance_daily, agent_relationships, handoff_log
User intelligenceuser_sessions, user_behavioural_signals, user_satisfaction_corrected
Governancecompliance_evidence_packs, data_lineage, pii_policies
Outcome / ROIoutcomes, outcome_rollups
Experimentationexperiments, experiment_splits, experiment_assignments, experiment_results
Predictivepredictions, drift_alerts
Autonomousinvestigations, recommendations, self_heal_proposals
APM depthapm_sampling_decisions, apm_k8s_enrichment_log, trace_step_profile_links

The full schema is in src/eden/db_migrations/versions/. Each migration is reversible (Alembic with explicit downgrade()).

Content-derived IDs

Almost every ID in Eden is content-derived:

event_id = "evt_" + sha256(org_id + trace_id + span_id + source_format + prompt + response + started_at)[:16]

This means re-sending the same payload produces the same event_id, so retries are idempotent at the storage layer. The downside is that you can't construct an ID independently — you have to hash the content. The SDKs and API do this for you.

Cardinality and growth

A mid-size customer (10M LLM calls/month) produces:

  • ~10M rows in telemetry_events (300 GB / month at full prompt and response sizes; ~30 GB after compression and prompt truncation)
  • ~3M rows in judge_scores (sampled)
  • ~10M rows in usage_events

The retention policy (configurable per org) drops prompt and response after 30 days by default, keeping the metadata. This keeps the table size manageable without losing the ability to investigate historical regressions (which only need the attributes and judge_scores).

Migrations

Alembic. Every migration has a corresponding downgrade(). New migrations go through:

alembic revision --autogenerate -m "add foo table"
alembic upgrade head # test locally
alembic downgrade -1 # verify the downgrade works
alembic upgrade head # re-apply

Migrations run as a one-shot job in the deploy pipeline.