PostgreSQL 13+. Run migrations in order from migrations/ to set up the schema.
LangGraph's own tables (checkpoints, checkpoint_blobs, checkpoint_writes) are
created automatically by AsyncPostgresSaver.setup() on startup — they live in the same
database but are not listed here.
psql $DATABASE_URL -f migrations/001_initial.sql
psql $DATABASE_URL -f migrations/002_soft_delete.sql
psql $DATABASE_URL -f migrations/003_usage_events_metadata.sql
psql $DATABASE_URL -f migrations/004_users_rbac.sql
psql $DATABASE_URL -f migrations/005_alerts.sql
psql $DATABASE_URL -f migrations/006_app_config.sql
# Continue through the remaining numbered migrations, including:
psql $DATABASE_URL -f migrations/012_incident_claims.sqlAll migrations are idempotent (IF NOT EXISTS, IF column does not already exist).
User accounts with password-based auth and RBAC roles.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | gen_random_uuid() |
org_id |
UUID FK → organizations | Nullable — unused in OSS single-tenant |
email |
TEXT UNIQUE | |
name |
TEXT | |
password_hash |
TEXT | bcrypt hash. NULL for pre-RBAC rows |
role |
TEXT | admin or user. Default user |
created_at |
TIMESTAMPTZ | |
updated_at |
TIMESTAMPTZ |
Constraint: role IN ('admin', 'user')
First registered user (first row with a password_hash) automatically gets role = 'admin'.
One session = one LangGraph thread_id. The id column is the thread_id passed to LangGraph.
user_id and org_id are nullable so the app works with auth disabled.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | Same value as LangGraph thread_id |
user_id |
UUID FK → users | Nullable |
org_id |
UUID FK → organizations | Nullable |
aws_profile_id |
UUID FK → aws_profiles | Nullable |
title |
TEXT | Auto-set from first 80 chars of first user message |
model |
TEXT | LiteLLM model ID used |
aws_region |
TEXT | AWS region at time of session |
is_deleted |
BOOLEAN | Soft delete — false by default |
deleted_at |
TIMESTAMPTZ | Set when soft-deleted |
created_at |
TIMESTAMPTZ | |
last_active_at |
TIMESTAMPTZ | Updated on every agent turn |
Indexes: user_id, org_id, last_active_at DESC, is_deleted (partial, where false)
Every user and assistant message in a session, in order.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
session_id |
UUID FK → sessions | Cascade delete |
role |
TEXT | user or assistant |
content |
TEXT | Full message text |
metadata |
JSONB | LangChain run_id, tags, RunnableConfig extras |
created_at |
TIMESTAMPTZ |
Indexes: (session_id, created_at)
Every AWS tool invocation per agent turn.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
session_id |
UUID FK → sessions | |
message_id |
UUID FK → messages | Links to the assistant message. Nullable. |
tool_name |
TEXT | e.g. get_alarms, query_logs_insights |
args |
JSONB | Input arguments |
result |
JSONB | Tool output |
error |
TEXT | Non-null when tool returned {"error": ...} |
duration_ms |
INTEGER | |
created_at |
TIMESTAMPTZ |
Indexes: session_id, message_id, tool_name
One row per completed agent turn: token counts, cost, latency.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
session_id |
UUID FK → sessions | |
message_id |
UUID FK → messages | |
model |
TEXT | LiteLLM model ID |
input_tokens |
INTEGER | |
output_tokens |
INTEGER | |
cost_usd |
NUMERIC(14,8) | Computed from LiteLLM pricing map |
latency_ms |
INTEGER | Wall-clock time for the full turn |
tool_call_count |
INTEGER | Number of tool calls in this turn |
metadata |
JSONB | Per-event context (e.g. summarization: true, chars_removed) |
created_at |
TIMESTAMPTZ |
Indexes: session_id, created_at DESC
Top-level tenant for multi-org / SaaS support. Table exists in the schema but is not used by the application in the current OSS release.
Per-org named AWS connection configs for multi-account support. Table exists but not yet wired up.
Structured root-cause analysis rows extracted from agent final answers. Table exists but not yet populated. Migration 015 adds a hypotheses JSONB column (default []) for the ranked-hypotheses conclusion schema; the replayable evidence pack currently reads the conclusion straight from tool_calls (the submit_investigation row) rather than from this table.
Hashed API keys for programmatic access. Table exists but not yet implemented.
Persisted event-driven and proactive polling investigation results shown on /monitoring.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | gen_random_uuid() |
service |
TEXT | Affected service/resource label |
error |
TEXT | Root cause summary |
resolution |
TEXT | Mitigation steps joined as text |
confidence |
TEXT | HIGH, MEDIUM, or LOW |
sns_sent |
BOOLEAN | Whether SNS publish succeeded |
dedup_key |
TEXT | Canonical incident key used by the poller/event consumer |
status |
TEXT | completed or failed |
session_id |
UUID FK → sessions | Investigation session that produced the alert |
trigger_source |
TEXT | poller or event_consumer |
created_at |
TIMESTAMPTZ |
Durable pre-investigation claims used to prevent duplicate autonomous agent runs.
| Column | Type | Notes |
|---|---|---|
incident_key |
TEXT PK | Canonical key such as cloudwatch_alarm:us-east-1:high-error-rate |
trigger_source |
TEXT | poller or event_consumer |
status |
TEXT | claimed, completed, or failed |
session_id |
UUID FK → sessions | Set when an investigation completes |
claimed_at |
TIMESTAMPTZ | Last time the incident was claimed |
completed_at |
TIMESTAMPTZ | Set after a completed or failed investigation |
Application-level key/value configuration shared by all server instances. The init wizard stores its setup and event-infrastructure state under key init.
| Column | Type | Notes |
|---|---|---|
key |
TEXT PK | e.g. init |
value |
JSONB | Setup state, AWS region, SQS URL, rule ARNs |
updated_at |
TIMESTAMPTZ |
users
sessions ──── messages ──── tool_calls
└─────────────── usage_events
sessions.idis the LangGraphthread_id— no join needed to link conversation history to app data.messages.metadata JSONBstores arbitrary LangChain runtime context without schema changes.tool_calls.erroris a separate TEXT column (not just checkingresult.error) so failed calls are queryable withWHERE error IS NOT NULL.usage_events.cost_usdis computed by the app from the LiteLLM pricing map — not trusted from the API.usage_events.metadatatracks per-turn context like whether the turn triggered conversation summarization.user_id/org_idon sessions are nullable intentionally: the app works without auth (JWT_SECRETunset).password_hashon users is nullable: pre-RBAC rows and future OAuth users won't have one.