Skip to content
Select themeSelect language

Data dictionary

This page maps every term in the user glossary onto the schema that actually backs it. It is the identifier-level companion to the core data model, which draws the tenancy and resource spine; here the unit is the term, not the table. Migration numbers refer to files in server/migrations/ (see the migration index).

Naming is the ubiquitous language of ADR 0040: one term, one meaning across DB, code, API and UI.

  • The tenant table is workspaces, renamed from teams in migration 169 together with team_members → workspace_members, team_invites → workspace_invites, and every team_id FK → workspace_id.
  • organizations.kind='team' stays an internal enum value; its user-facing label is Organization (org_kind_team), against Personal for kind='personal'. Never surface the raw value.
  • team/teams is otherwise reserved for the agent domain (agent_teams, max_teammates). An architecture test fails a new team_id column or a new team-named tenant type, and a frontend i18n lint fails a new user-facing “Team” string outside the agent and delivery_* namespaces.
Term Authority What to know
Organization organizations — id, name, slug UNIQUE, kind kind CHECK is personal or team, NOT NULL DEFAULT team (mig 140). The plan is per organization on subscriptions, whose PK was re-keyed from workspace_id to organization_id in mig 143 — never look for a plan on a workspace.
Organization membership organization_members — user_id, organization_id, role, seat_type_id UNIQUE (user_id) enforces the single-org model (no switcher, no X-Organization-Id). role is the wider org set: owner, billing_admin, admin, member.
Workspace workspaces; FK workspace_id on ~34 tables Renamed from teams in mig 169 (catalog-only rename, no row rewrite). Also carries autonomy_level, autonomy_overrides, experience_tier.
Workspace membership workspace_members — workspace_id, user_id, role role CHECK tightened to owner, admin, member in mig 245. Trigger trg_team_member_org_invariant — deliberately not renamed by 169, so unchanged RLS policies still resolve — requires a matching organization_members row for the workspace’s org; a violation raises check_violation.
Seat seat_types — key UNIQUE, entitlements JSONB, stripe_price_id UNIQUE; assigned via organization_members.seat_type_id Table created in mig 144, which also added the FK (ON DELETE SET NULL, so archiving a seat type unassigns rather than blocks). Entitlements resolve first-non-null: user override › seat type › workspace override › org plan. Billing quantity lives on subscription_items (organization_id, seat_type_id).
Project projects PR-flow autodev config no longer lives here: mig 299 dropped 12 auto_developer_* columns (only auto_developer_workflow_id survives), 302 dropped the auto_issue_debate_* set, 297 dropped the visual-verify columns. The pr_review row of project_automations is the authority. backlog_merge_policy CHECK is pr_only, auto_on_green, full_auto (mig 287).
Term Authority What to know
Task tasks — project_id, agent_type, status, config JSONB, agent_profile_id config is the per-launch override bag (effort, harness knobs, teammates, closeout); agent_profile_id is the WHO authority. The status/branch/timestamp columns are a denormalised cache of the latest run, never the source of truth.
Run runs — kind discriminator, task_id, workflow_id, parent_run_id, workflow_node_key next_event_seq (mig 220) mints a gapless, monotonic events.sequence_num per run via an atomic UPDATE … RETURNING; uq_events_run_seq is the safety net. autonomy_policy_json (mig 214) is the policy frozen at launch. events.task_id is nullable since mig 331 so a workflow-node event can key on a run with no task.
Schedule task_schedules — workspace_id, cadence, next_run_at, agent_profile_id agent_profile_id added in mig 293 (ON DELETE SET NULL). cadence CHECK is once, daily, weekly, monthly, interval since mig 323, which also added interval_minutes bound by CHECK ((cadence = 'interval') = (interval_minutes IS NOT NULL)) and a 5–1440 range — cadence and column are inseparable.
Workflow workflows + workflow_nodes + workflow_edges workflow_nodes is UNIQUE (workflow_id, node_key); edges join by the node_key text (source_node/target_node), with source_handle defaulting to out. workflows.stages JSONB arrived in mig 311.
Term Authority What to know
Agent tasks.agent_type (plus the resolved container image) Not a table of its own — the agent is the harness process a run starts. The image is chosen from the repository’s detected language at launch.
Agent profile agent_profiles — execution_mode, is_workspace_default, autonomy_level, interactive_approval execution_mode CHECK is normal or workflow since mig 285; ultracode was retired to the effort axis (minimal…max), though the runtime still tolerates a legacy in-flight value in task config. is_workspace_default (mig 119) is bounded by the partial unique index uq_agent_profiles_workspace_default — at most one default per workspace.
Teammates Not a column. tasks.config keys agent_teams (bool) and max_teammates (int) No table and no migration; ADR 0040 keeps the key name because it is Claude Code’s own. The runtime emits CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS / CLAUDE_CODE_MAX_TEAMMATES for claude, and AGENT_TEAMS=1 for codex/gemini. max_teammates is emitted only when agent_teams is true.
Skill agent_capabilities with kind='skill' — Markdown in config.instructions kind CHECK is mcp or skill; UNIQUE (workspace_id, kind, name). config also carries targets, the per-harness allowlist. An induced skill is staged in the separate skill_proposals table (mig 218) and only becomes an agent_capabilities row once published. Capability writes are workspace-admin gated.
Council councils + council_participants + council_turns mode CHECK is analyze, propose, review, standup — the UI labels differ from these stored values, so map rather than assume. target_type gained workflow_step in mig 314, which is how a pipeline stage runs a council. The synthesis lands on judge_profile_id / judge_synthesis / judge_confidence.
Memory memories — scope, source_type; siblings memory_versions, memory_review_items, memory_embeddings scope CHECK is personal, workspace, project. The source_type CHECK is deliberately wider than the user-facing allowlist (it admits repo_sync, agent_experiment and a harness_memory:% arm); validate_memory_source_type is the narrow gate, and system paths bypass it via create_system_memory. Governance is two-tier: workspace_settings.memory_governance_policy (mig 249) with a project override projects.memory_governance_policy (mig 289).
MCP No table. The catalog is code, exported to docs/mcp/ and drift-gated Per-task/workspace/profile caps are derived from audit_events so they survive restarts; in-flight reservations live in mcp_cap_reservations, whose task_id is nullable since mig 268 for the task-less external-gateway principal.
Term Authority What to know
Autonomy level workspaces.autonomy_level (NOT NULL DEFAULT 0, CHECK 0–100), projects.autonomy_level (nullable), agent_profiles.autonomy_level (nullable) — all mig 214 NULL means inherit, so the chain profile › project › workspace always terminates at a concrete workspace value. The result is then clamped by the plan ceiling plans.features->'autonomy.level_max' and the onboarding cap; a per-arm project_automations.autonomy_override (mig 210) can only lower it. runs.autonomy_policy_json freezes the outcome at launch.
Approval (gate) task_tool_approvals — kind, status, timeout_policy, deadline_at kind CHECK is tool, question, visual, tier_change (widened by mig 321). tier_change is deliberately not question, because the parent-answer guard admits only question — a machine must not self-approve the gate it raised. Partial index uq_task_tool_approvals_task_pending allows at most one pending approval per task, and hold-policy rows are never auto-swept.
Backlog backlog_items plus the dispatch arm project_automations with kind='backlog' project_automations.kind CHECK is now pr_review, intake, backlog: tracker was renamed to intake (mig 303) and debate was retired (mig 291). Issue import and dispatch are separate paths — import runs regardless of mode.
Backlog item backlog_items — source, external_id, scope, state Dedupe key is UNIQUE (project_id, source, external_id): mig 303 added source and backfilled it from tracker, mig 304 dropped the old column and its unique index. scope is nullable with CHECK small, medium, large (mig 226); NULL is routed as neutral. convergence_round / convergence_cost_usd bound the review loop (mig 232), and closed_at carries CHECK (closed_at IS NULL OR state = 'done') (mig 310).
Concept approval mode project_automations.pipeline_config JSONB → stages.plan.approval (bool) The approval_mode column was dropped in mig 301 after mig 300 folded it: concept → stages.plan.approval, pr_draft → stages.implement.draft_pr_approval, human_gate → projects.backlog_merge_policy = 'pr_only'. concept_gate_on reads the flag with no enum fallback left, and the gate applies only to a kind='backlog' arm.
Completion action project_automations.post_completion_comment × close_source_issue (both nullable, mig 295) The two booleans span all four outcomes, including the previously inexpressible silent close (false, true). The legacy completion_action column still exists (close, comment, nothing) as a lossy dual-read fallback for a row whose boolean pair is NULL; its drop is deferred. Resolve the pair first, the enum only as fallback.
Closeout workspace_settings.closeout_config (JSONB NOT NULL, full default) + projects.closeout_config (nullable, sparse) + task.config.closeout; signals on task_outcomes.closeout_signals — mig 283 Resolution is per step, first-non-null: task ?? project ?? workspace ?? built-in. There is deliberately no master switch; the fold lives once in sc-persistence::models::closeout and is shared by the memory gate, the env injection and the status signal.
Term Authority What to know
App apps — UNIQUE (workspace_id, name), manifest_yaml; source tree in app_files (mig 107) apps.db_namespace_id (mig 279) is the App’s database authority; app_db_resource_id survives only as the hint for which physical Postgres. app_migrations is no longer a table — mig 280 folded it onto db_namespace_migrations and left a read-only compatibility view. The schema is app_<id>, and db_namespace_bindings enforces exactly one binder.
Script scripts (mig 066) locally; the catalog row is marketplace_items with kind='script' An installed script is tracked on marketplace_installs.installed_script_ids. The Wasm script substrate is pure-compute and DB-isolated by design; it reaches the network only through a bound resource’s allowlisted authority.
Connector marketplace_items with kind='node', plus the per-workspace registry workflow_node_types workflow_node_types is UNIQUE (workspace_id, slug) and carries preset_config, required_resource_kinds, source_item_version_id. The component bytes ride the version manifest as component_data_b64.
Resource resources — UNIQUE (workspace_id, name), kind, config JSONB, secret_vault_path kind is a CHECK list, currently smtp, telegram_bot, webhook_endpoint, imap, postgresql, s3, discord, seafile_webdav, fints_bank, chatwoot, intercom, custom (last widened by mig 324). Each kind gates on the plan feature key resources.<kind>. secret_vault_path is nullable, so a secret-less resource is representable. The UI calls these Connections.
Runner runners — status, drain_state, capabilities JSONB, auth_token_hash UNIQUE status CHECK is pending, online, offline, disabled; drain_state CHECK is active, cordoned, draining (mig 234). Dispatch reads status='online' AND drain_state='active'. lease_epoch is not on runners — it is on runner_work_assignments / connector_run_assignments (mig 239), because fencing belongs to the assignment. Enrollment lives in runner_enrollments + runner_grants (mig 236).
Term Authority What to know
Marketplace marketplace_items (UNIQUE (source_id, slug)) + marketplace_item_versions + marketplace_installs marketplace_items.kind CHECK is app, script, bundle, runner_offer, workflow, node — six values, so do not omit runner_offer. marketplace_installs is UNIQUE (workspace_id, item_id) since mig 112: a re-install upserts, never duplicates. install_manifest_sha256 drives drift detection.
Bundle marketplace_items with kind='bundle'; contents ride marketplace_item_versions.package_manifest JSONB under members members is a manifest block, not a column — a bundle is folded at publish into one members block plus a unioned install spec and requirements, so one install is one marketplace_installs row. Publish rejects fewer than two members, a member-slug collision, a self-reference, a cycle, more than one member db_namespace, and a paid member in a free bundle. An App member may carry its tree inline instead of a source_tarball_url / source_tarball_sha256 pair.
Term Authority What to know
Live mode No table. Per-surface session state in sc-iface-telegram/src/live_mode and sc-iface-discord/src/live_mode, and the web follow engine Follow is per surface: a Telegram launch streams only in Telegram. Both chat surfaces share the same pure render substrate; only the transport differs.
Verbosity chat_platform_accounts.verbosity + user_profiles.default_verbosity (mig 255) Both nullable with CHECK reduced or chatty. NULL is the layering signal, not a value: per-account ?? user default ?? built-in reduced. Orthogonal to debug mode — two axes, one renderer, one redaction wall.
Debug mode workspace_settings.allow_debug BOOLEAN NOT NULL DEFAULT false (mig 254) Fail-closed by default, unlike onboarding_mode, which coalesces to TRUE. The per-session activation on top of this ceiling is ephemeral and never persisted; the ceiling is enforced server-side on every surface.
Delivery board Frontend route /reports/delivery; HTTP surface GET /api/team-board; Rust services::team_board and sc-persistence::queries::delivery_board The UI, i18n (delivery_*) and route say delivery; the HTTP path and Rust module still say team_board. That split is intentional and unrenamed — there is no /delivery backend route. Only one i18n key is literally delivery_board_*.

Each of these reads plausibly but is wrong against the current schema.

Stale identifier Current truth Landed in
teams, team_members, team_id workspaces, workspace_members, workspace_id 169
subscriptions.workspace_id re-keyed to organization_id (PK); workspace_id dropped 143
project_automations.approval_mode (with a concept value) column dropped; pipeline_config → stages.plan.approval 300, 301
project_automations.kind='tracker' / 'debate' intake; debate retired 291, 303
backlog_items.tracker, dedupe (project_id, tracker, external_id) source, dedupe (project_id, source, external_id) 303, 304
projects.auto_developer_*, auto_issue_debate_*, visual-verify columns dropped; re-homed onto the project_automations arm 297, 299, 302
merge policy auto_merge_on_green auto_on_green 287
agent_profiles.execution_mode='ultracode' normal or workflow; effort carries the intent 285
runners.lease_epoch on runner_work_assignments / connector_run_assignments 239
app_migrations as a table read-only view over db_namespace_migrations 280
apps.app_db_resource_id as the App DB authority apps.db_namespace_id 279
task_schedules.cadence limited to once/daily/weekly plus monthly, interval (+ interval_minutes) 323
resource kinds ending at intercom custom added 324
workflow_steps, workflow_runs, workflow_node_runs dropped; folded into workflow_nodes and runs 046, 051
  • Glossary — the user-facing half of these terms
  • Core data model — the tenancy and resource spine, with the ERD
  • Migration index — numbering, gaps, and the shared-namespace rule
  • ADR 0040 — Workspace/Team naming cleanup and a single domain vocabulary