Data & Migrations

RoboCo keeps all of its state in PostgreSQL. There is no separate document store, no schema you have to hand-build, and — in normal operation — no migration command you have to remember. This page gives you the operator's-eye view: the handful of entities worth understanding, the one hard database requirement (pgvector), and how the schema keeps itself up to date.

The entities you'll actually see

The full data model spans about thirty Pydantic models in roboco/models/ with matching SQLAlchemy tables in roboco/db/tables.py. You don't need most of them. These are the ones that show up across the panel and explain how work flows:

EntityWhat it is
TaskThe atomic unit of work. Carries its acceptance criteria, status (the 15-state lifecycle), priority, branch name, PR number/URL, ownership (created_by / assigned_to / team), and its place in the tree (parent_task_id, dependency_ids, blocker_ids). A task points at exactly one of a project_id or a product_id — a single-repo task uses the project, a Board/fan-out coordination task uses the product. A validator enforces "one or the other, never both."
ProjectA git repository configuration: git_url, default branch, protected branches, the Fernet-encrypted GitHub PAT (git_token_encrypted — the API only ever returns has_git_token, never the token), the per-project CI/gate commands, and the assigned cell. See Register your first project.
WorkSessionThe link between an agent, a task, and a branch. One is created each time an agent claims a task. It tracks the base/target branches, the commits made, the files modified, the PR and merge outcome, and (when toolchain matching is on) the resolved Python version and toolchain status.
AgentOne row per member of the workforce — role, team, status — plus the human CEO. See Org & roles.
NotificationA formal signal that requires acknowledgment (sent by PMs and the Board), distinct from ad hoc agent communication.
JournalEach agent's personal log of reflections and learnings, with JournalEntry rows underneath.

Two newer entities round out the company-in-a-box features: Product (with ProductProject, the per-cell repo-routing map for fan-out work) and Pitch (a Board proposal that the CEO approves and that can auto-provision a repo). You'll meet these on the Business page.

A handful of more recent additions, all inert while their feature flag is off: users (the single seeded CEO login row backing cloud auth), x_credentials + x_seen_mentions (the Fernet-encrypted OAuth secrets and the mentions dedup ledger behind the X engine), a sandbox_services column on Project (the per-project opt-in for the sandboxed dev DB/Redis), and vault_seen_notes (the dedup ledger behind the Obsidian vault's #roboco inbox loop — the vault's actual content lives on disk as markdown, not in Postgres).

pgvector is required

RoboCo's in-house RAG engine stores embeddings in PostgreSQL using the pgvector extension. The orchestrator enables it for you at startup (CREATE EXTENSION IF NOT EXISTS vector), so the bundled postgres image — which ships pgvector — works out of the box.

If pgvector isn't installed

On a bring-your-own PostgreSQL where the vector extension isn't available, the orchestrator logs a warning and RAG features silently disable — agents lose the knowledge base and mentor lookups, but the rest of the system runs. If you point RoboCo at an external Postgres, make sure pgvector is installed there.

Operationally, PostgreSQL is the one service that must be backed up — everything the company knows lives there. Redis holds only the event bus and short-lived coordination state, and is fine to lose. An always-on sidecar takes a daily pg_dump of Postgres automatically; see Backups for the rotation and restore procedure.

The encryption key is not in the database

Each project's GitHub PAT is stored encrypted with ROBOCO_ENCRYPTION_KEY. A database backup without that key is useless for the tokens — you'll be able to restore every project except its credentials. Keep the key safe and separate. See the environment reference.

The stack migrates itself on startup

You almost never run a migration command by hand. When the orchestrator boots, init_db() enables pgvector and then run_migrations() runs alembic upgrade head in a worker thread:

The practical consequence: a normal docker compose up / restart already applies any new migrations. A fresh database is built entirely by the migration chain from base; an existing one runs only the pending steps (a pre-Alembic database is auto-stamped at the initial revision first, so it isn't re-built).

The manual command is belt-and-suspenders

The documented manual step — after pulling a change that adds a migration —

bash
1docker compose exec orchestrator alembic upgrade head

is a safety net, not a routine requirement, since the orchestrator runs the same command on boot. In host-dev mode (no container) the equivalent is uv run alembic upgrade head, or make migrate.

A fresh DB is built by migrations, not create_all

This is the one gotcha that bites operators who try to reset state by hand. Several migrations embed seed data — most importantly the LLM provider rows (Anthropic, Grok, Codex, Gemini, Kimi, Ollama Cloud, self-hosted). The schema must be built by running the migration chain, never by a bare SQLAlchemy create_all.

Don't reset a database with `create_all`

A create_all-only database has empty provider_configs, so the Settings → Providers endpoints return 404. The fix is always the same: let the migrations run (restart the orchestrator, or run alembic upgrade head). The orchestrator's own startup uses create_all only as a gap-fill on an already-migrated database, never as the builder.

Contributor notes

If you write or review custom migrations:

  • Revision-id length is capped at 32 characters. PostgreSQL stores alembic_version.version_num as VARCHAR(32). A longer id breaks a live upgrade even though the test suite (which renders offline / uses create_all) never catches it. The chain currently has 70 revisions (71 files — revision 026 is split into two consecutive steps); the head is 070_vault_seen_notes, and the longest ids in the tree are 015_drop_task_execution_outputs and 063_video_engine_project_toggle, tied at 31 characters, deliberately just under the limit.
  • The chain is linear. Every revision has a single down_revision; there are no branches or merges to reconcile.
  • Models, tables, and migrations must stay in sync. A Pydantic model in roboco/models/, its ORM table in roboco/db/tables.py, and the migration that creates the column are three layers that move together.

Next

Bootstrap & seeds — what make db-init puts into a fresh database. → Environment reference — every ROBOCO_DATABASE_* knob and the encryption key.

llms.txt