Database schema
Riverbeam keeps its data in the Postgres schema ref_beam. The full schema is
migrations/0001_beam_schema.sql in the repository.
Applying the schema
Apply the migration before the first start:
psql "$BEAM_DATABASE_URL" -f riverbeam/migrations/0001_beam_schema.sql
It needs the vector extension and a role allowed to create it.
PgBeamStore::connect and PgVectorStore::connect also create their tables if they are missing, so
a database without the migration still works. Their tables are simpler: no foreign keys and fewer
columns. Every statement uses CREATE TABLE IF NOT EXISTS, so whichever runs first decides the
shape, and running the migration after a component created its tables does not add the missing
columns or keys.
With the migration applied, agent_session.agent_id references agent. Call
BeamWorker::on_startup() (or RiverBeam::checkin_catalog()) before serving, so every registered
agent has its row before the first session is written.
Tables
| Table | Holds | Written by |
|---|---|---|
model | Model catalog | BeamStore::checkin_models, when your code calls it |
agent | Agent catalog | checkin_catalog / on_startup |
agent_session | Conversations | Pipeline, bootstrap |
agent_interaction | User requests | Pipeline |
agent_run | Pipeline passes | Pipeline |
agent_step | Steps within a run | not written yet |
agent_message | Turns, ordered by sequence | Pipeline |
agent_action | Pending and resolved approvals | Pipeline (human approval) |
agent_knowledge_document | Ingested documents | PgVectorStore |
agent_knowledge_chunk | Chunks with embedding vector(1536) | PgVectorStore |
agent_memory | Vector memory | not written yet |
agent_usage_plan, agent_usage_profile, agent_usage_counter, agent_usage_event | Plans, assignments, counters, events | not written yet |
agent_event | Audit events | not written yet |
agent_security_decision, agent_security_rule_narration | Security verdicts | not written yet |
Tables marked "not written yet" exist for parity with other Riverbeam deployments. The shipped usage
store and audit sinks are in memory or stdout; write a UsageStore or AuditSink to persist them.
Execution tables
| Table | Key columns |
|---|---|
agent_session | _id, agent_id, user_id (UUID), profile_id (UUID), name, last_active_at |
agent_interaction | _id, session_id, agent_id, agent_name, status, started_at, ended_at |
agent_run | _id, session_id, interaction_id, agent_id, agent_name, status, started_at, ended_at |
agent_message | _id, sequence, role, type, content (JSONB), tool columns, phase, timestamp, session_id, interaction_id, run_id, request_id |
agent_action | _id, session_id, interaction_id, run_id, agent_id, type, status, payload, result, resolved_at |
Indexes: agent_session(agent_id), agent_interaction(session_id), agent_run(session_id),
agent_message(session_id), agent_action(run_id).
user_id and profile_id are UUID columns. PgBeamStore drops values that are not UUIDs, so use
UUID user ids if you want them on the session.
Knowledge tables
| Table | Key columns |
|---|---|
agent_knowledge_document | _id, scope, agent_id, user_id, title, source, type, status, embedder_name, chunk_count, metadata_json |
agent_knowledge_chunk | _id, document_id, scope, agent_id, user_id, source, chunk_index, token_count, content, embedding vector(1536); unique on (document_id, chunk_index) |
agent_memory | _id, user_id, agent_id, type, content, embedding vector(1536) (nullable), importance, expires_at |
Both vector columns have HNSW indexes with vector_cosine_ops, m = 16, ef_construction = 64.
The width is fixed at 1536, which is why pgvector storage requires a 1536-dimension embedder.