molecule-core/docs/architecture/database-schema.md
Hongming Wang 24fec62d7f initial commit — Molecule AI platform
Forked clean from public hackathon repo (Starfire-AgentTeam, BSL 1.1)
with full rebrand to Molecule AI under github.com/Molecule-AI/molecule-monorepo.

Brand: Starfire → Molecule AI.
Slug: starfire / agent-molecule → molecule.
Env vars: STARFIRE_* → MOLECULE_*.
Go module: github.com/agent-molecule/platform → github.com/Molecule-AI/molecule-monorepo/platform.
Python packages: starfire_plugin → molecule_plugin, starfire_agent → molecule_agent.
DB: agentmolecule → molecule.

History truncated; see public repo for prior commits and contributor
attribution. Verified green: go test -race ./... (platform), pytest
(workspace-template 1129 + sdk 132), vitest (canvas 352), build (mcp).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:55:37 -07:00

7.6 KiB

Database Schema

Postgres Tables

workspaces — Workspace Registry (Current State)

The mutable projection of structure_events. Represents the current state of all workspaces.

CREATE TABLE workspaces (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name          TEXT NOT NULL,
  role          TEXT,
  tier          INTEGER DEFAULT 1,
  status        TEXT DEFAULT 'provisioning',
  source_bundle_id TEXT,
  agent_card    JSONB,
  url           TEXT,
  parent_id     UUID REFERENCES workspaces(id),
  forwarded_to  UUID REFERENCES workspaces(id),
  last_heartbeat_at  TIMESTAMPTZ,
  last_error_rate    FLOAT DEFAULT 0,
  last_sample_error  TEXT,
  active_tasks       INTEGER DEFAULT 0,
  uptime_seconds     INTEGER DEFAULT 0,
  created_at    TIMESTAMPTZ DEFAULT now(),
  updated_at    TIMESTAMPTZ DEFAULT now()
);
Column Purpose
id Unique workspace identifier
name Display name
role The org chart role (e.g. "Marketing", "QA")
tier 1-4, determines deployment method
status provisioning, online, degraded, offline, failed, or removed
agent_card Full A2A Agent Card as JSONB
url Current endpoint URL
parent_id Parent workspace (defines hierarchy AND communication topology)
source_bundle_id Original bundle ID this workspace was created from
forwarded_to Redirect pointer when workspace is replaced, expanded, or moved (see Registry — Workspace Forwarding)
last_heartbeat_at Timestamp of last heartbeat received
last_error_rate Latest self-reported error rate (triggers degraded at >= 0.5)
last_sample_error Latest sample error message (shown on canvas tooltip)
active_tasks Number of tasks currently running (shown as busy indicator on canvas)
uptime_seconds Seconds since container start
current_task Human-readable description of what the agent is currently working on (set via heartbeat)

agents — Agent Assignments

CREATE TABLE agents (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id    UUID REFERENCES workspaces(id),
  model           TEXT,
  status          TEXT DEFAULT 'active',
  removed_at      TIMESTAMPTZ,
  removal_reason  TEXT,
  created_at      TIMESTAMPTZ DEFAULT now()
);

Tracks which AI model is assigned to which workspace, and the history of assignments.

workspace_secrets — Encrypted Credentials

CREATE TABLE workspace_secrets (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id  UUID REFERENCES workspaces(id),
  key           TEXT NOT NULL,
  encrypted_value BYTEA NOT NULL,
  created_at    TIMESTAMPTZ DEFAULT now(),
  updated_at    TIMESTAMPTZ DEFAULT now(),
  UNIQUE(workspace_id, key)
);

Stores API keys, credentials, and other secrets needed by workspace agents. Values are encrypted with AES-256 at the application layer. The encryption key comes from the SECRETS_ENCRYPTION_KEY environment variable on the platform — never stored in the database.

The provisioner reads secrets from this table, decrypts them, and injects them as environment variables when spinning up workspace containers. Secrets are never included in bundles (see Constraints — Rule 5).

canvas_layouts — Node Layout

CREATE TABLE canvas_layouts (
  workspace_id  UUID REFERENCES workspaces(id) ON DELETE CASCADE,
  x             FLOAT NOT NULL DEFAULT 0,
  y             FLOAT NOT NULL DEFAULT 0,
  collapsed     BOOLEAN DEFAULT false,
  PRIMARY KEY (workspace_id)
);

Stores the visual position and UI state of each workspace node on the canvas. One row per workspace. Updated via PATCH /workspaces/:id when the user drags a node.

canvas_viewport — Viewport State

CREATE TABLE canvas_viewport (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  x          FLOAT NOT NULL DEFAULT 0,
  y          FLOAT NOT NULL DEFAULT 0,
  zoom       FLOAT NOT NULL DEFAULT 1,
  saved_at   TIMESTAMPTZ DEFAULT now()
);

Single row — upserted on viewport change. Stores the canvas pan and zoom position so the user returns to the same view. Separate from canvas_layouts to avoid bloating the per-node table.

structure_events — Immutable Event Log

CREATE TABLE structure_events (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  event_type    TEXT NOT NULL,
  workspace_id  UUID,
  agent_id      UUID,
  target_id     UUID,
  payload       JSONB,
  created_at    TIMESTAMPTZ DEFAULT now()
);

Append-only. Never UPDATE or DELETE rows. See Event Log.

activity_logs — Operational Activity Log

CREATE TABLE activity_logs (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id  UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
  activity_type TEXT NOT NULL,
  source_id     UUID,
  target_id     UUID,
  method        TEXT,
  summary       TEXT,
  request_body  JSONB,
  response_body JSONB,
  duration_ms   INTEGER,
  status        TEXT DEFAULT 'ok',
  error_detail  TEXT,
  created_at    TIMESTAMPTZ DEFAULT now()
);
Column Purpose
activity_type a2a_send, a2a_receive, task_update, agent_log, or error
source_id Workspace that initiated the action (nullable for canvas-originated)
target_id Workspace that received the action (for A2A communications)
method A2A method name (e.g. message/send) or task action
request_body Full request payload as JSONB (for A2A)
response_body Full response payload as JSONB (for A2A)
duration_ms Operation duration in milliseconds
status ok, error, or timeout
error_detail Error message when status is not ok

Separate from structure_events by design: structure_events tracks lifecycle/structural changes (append-only, never deleted), while activity_logs tracks operational activity (A2A communications, task updates, agent logs) and has a 7-day retention policy with automatic cleanup.

Indexes: composite (workspace_id, activity_type, created_at DESC) for filtered queries, standalone created_at for retention cleanup.

Redis Keys

Key Pattern Value TTL Purpose
ws:{id} "online" 60s Liveness detection
ws:{id}:url "https://..." 5min URL cache for fast resolution
events:broadcast pub/sub channel -- Push events to canvas/workspace WebSocket

Redis Configuration

Keyspace notifications must be enabled for liveness detection:

notify-keyspace-events = KEA

This allows the platform to subscribe to key expiry events without polling.

Design Decisions

  • Postgres is source of truth. Redis is ephemeral. If Redis is wiped, workspaces re-register on next heartbeat and state is restored. Nothing critical lives only in Redis.
  • org_id is omitted from MVP schema. Added later in the SaaS migration for multi-tenancy.
  • wal_level=logical is set from the start to enable future streaming of change events without a schema migration.