Database Migrations — Technical Debt Inventory¶
All migrations run in Database.initialize() on every startup. They are
idempotent (safe to re-run) but represent technical debt from schema evolution.
This document catalogs every migration and its removal criteria.
Overview¶
Database.initialize()
├── SCHEMA (DDL) — CREATE TABLE IF NOT EXISTS (14 tables)
├── Column migrations (ALTER TABLE) — 11 one-liners
├── Index creation — 2 indexes
├── _migrate_agent_workspaces() — agents.checkout_path → agent_workspaces
├── _migrate_repos_to_projects() — repos table → projects.repo_url columns
├── _migrate_agent_workspaces_to_workspaces() — agent_workspaces → workspaces
├── _cleanup_cross_project_workspaces() — fix stale cross-project entries
└── COMMIT
Legacy Tables (candidates for DROP)¶
repos table¶
Schema location: database.py line ~57
Created: Original schema
Superseded by: projects.repo_url + projects.repo_default_branch columns
Migration that replaced it: _migrate_repos_to_projects()
Still referenced by:
- _migrate_repos_to_projects() — reads from repos to populate project columns
- _migrate_agent_workspaces_to_workspaces() — reads repos.source_type
- _cmd_get_task_diff() in command_handler.py — fallback path resolution via task.repo_id
- _resolve_repo_path() in command_handler.py — fallback when no workspace found
- Database.create_repo(), get_repo(), list_repos(), delete_repo() — full CRUD still exists
- delete_project() cascade — DELETE FROM repos WHERE project_id = ?
Removal plan:
1. Audit _resolve_repo_path() — replace repo.source_path/repo.checkout_base_path fallbacks with workspace lookups
2. Remove task.repo_id column (and tasks.repo_id FK in schema)
3. Remove agents.repo_id column from schema
4. Remove all Database.*_repo() methods
5. Drop repos table from SCHEMA
6. Remove _migrate_repos_to_projects()
7. Remove RepoConfig from models.py
agent_workspaces table¶
Schema location: database.py line ~179
Created: Intermediate migration step (agents had per-project workspace assignments)
Superseded by: workspaces table (project-scoped, agent-agnostic with dynamic locking)
Migration that replaced it: _migrate_agent_workspaces_to_workspaces()
Still referenced by:
- _migrate_agent_workspaces() — writes into it from agents.checkout_path
- _migrate_agent_workspaces_to_workspaces() — reads from it to populate workspaces
- delete_agent() cascade — DELETE FROM agent_workspaces WHERE agent_id = ?
- delete_project() cascade — DELETE FROM agent_workspaces WHERE project_id = ?
Known bug (fixed): This table contained a stale row mapping
(agent-queue-worker, moss-and-spade-inventory-manager) → /mnt/d/Dev/agent-queue2
which caused the migration to re-create a bogus workspace entry on every restart,
sending agents to the wrong project directory.
Removal plan:
1. Remove _migrate_agent_workspaces() method
2. Remove _migrate_agent_workspaces_to_workspaces() method
3. Remove cascade DELETEs in delete_agent() and delete_project()
4. Drop agent_workspaces table from SCHEMA
Legacy Columns (candidates for DROP)¶
agents.checkout_path and agents.repo_id¶
Schema location: database.py line ~125-126
Superseded by: workspaces table (agents acquire workspace locks dynamically)
Migration that reads it: _migrate_agent_workspaces() — copies to agent_workspaces
Removal plan: Drop columns from SCHEMA DDL. The migration already checks
if "checkout_path" not in columns: return so it's safe to remove the column first.
projects.workspace_path¶
Schema location: database.py line ~51
Superseded by: workspaces table
Column migration: ALTER TABLE projects ADD COLUMN workspace_path TEXT (line ~299)
Still referenced by: Only the schema DDL and the ALTER migration. No production code reads it.
Removal plan: Remove from SCHEMA DDL and remove the ALTER migration line.
projects.discord_control_channel_id¶
Schema location: database.py line ~53 (in DDL after ALTER migration)
Column migration: line ~305
Status: Appears unused — discord_channel_id is the only channel column used in production.
Removal plan: Verify no code reads it, then remove from SCHEMA and migrations.
Column Migrations (ALTER TABLE)¶
All in Database.initialize() lines ~298-311. Each runs in a try/except that
swallows "column already exists" errors.
| # | Migration SQL | Target | Status |
|---|---|---|---|
| 1 | ALTER TABLE projects ADD COLUMN workspace_path TEXT |
projects |
Removable — superseded by workspaces table |
| 2 | ALTER TABLE repos ADD COLUMN source_type TEXT NOT NULL DEFAULT 'clone' |
repos |
Removable when repos table is dropped |
| 3 | ALTER TABLE repos ADD COLUMN source_path TEXT NOT NULL DEFAULT '' |
repos |
Removable when repos table is dropped |
| 4 | ALTER TABLE tasks ADD COLUMN requires_approval INTEGER NOT NULL DEFAULT 0 |
tasks |
Keep — already in SCHEMA DDL, but migration needed for pre-existing DBs |
| 5 | ALTER TABLE tasks ADD COLUMN pr_url TEXT |
tasks |
Keep — same |
| 6 | ALTER TABLE projects ADD COLUMN discord_channel_id TEXT |
projects |
Keep — actively used |
| 7 | ALTER TABLE projects ADD COLUMN discord_control_channel_id TEXT |
projects |
Removable — appears unused |
| 8 | ALTER TABLE tasks ADD COLUMN plan_source TEXT |
tasks |
Keep — actively used |
| 9 | ALTER TABLE tasks ADD COLUMN is_plan_subtask INTEGER NOT NULL DEFAULT 0 |
tasks |
Keep — actively used |
| 10 | ALTER TABLE tasks ADD COLUMN task_type TEXT |
tasks |
Keep — actively used |
| 11 | ALTER TABLE projects ADD COLUMN repo_url TEXT DEFAULT '' |
projects |
Keep — actively used |
| 12 | ALTER TABLE projects ADD COLUMN repo_default_branch TEXT DEFAULT 'main' |
projects |
Keep — actively used |
Note: Migrations 4-12 are technically no-ops for new databases (the columns are already in the SCHEMA DDL) but are needed for databases created before those columns were added. Once all production databases have been migrated, they can be removed.
Data Migration Methods¶
_migrate_agent_workspaces()¶
Location: database.py line ~337
Purpose: Copy agents.checkout_path + agents.repo_id into agent_workspaces table
Source: agents table (columns: checkout_path, repo_id, current_task_id)
Destination: agent_workspaces table
Guard: Returns early if checkout_path column no longer exists in agents
Status: Removable — intermediate step, feeds into _migrate_agent_workspaces_to_workspaces
_migrate_repos_to_projects()¶
Location: database.py line ~420
Purpose: Copy first repo's url/default_branch into projects.repo_url/repo_default_branch
Source: repos table
Destination: projects table
Guard: Only writes to projects where repo_url IS NULL OR repo_url = ''
Status: Removable once repos table is dropped and all projects have repo_url populated
_migrate_agent_workspaces_to_workspaces()¶
Location: database.py line ~444
Purpose: Deduplicate agent_workspaces rows into the canonical workspaces table
Source: agent_workspaces table
Destination: workspaces table
Guard: INSERT OR IGNORE + UNIQUE(project_id, workspace_path) constraint. Skips entries where workspace path belongs to a different project.
Status: Removable once agent_workspaces table is dropped
_cleanup_cross_project_workspaces()¶
Location: database.py line ~500
Purpose: Remove workspace entries where the same path is claimed by multiple projects
Trigger: Runs every startup to catch stale data from old migrations
Status: Keep for now — defensive cleanup. Can be removed once agent_workspaces table (the source of bad data) is dropped.
Removal Checklist¶
When you're ready to clean up, do this in order:
Phase 1: Drop agent_workspaces (safest, highest value)¶
- [ ] Remove
_migrate_agent_workspaces()method - [ ] Remove
_migrate_agent_workspaces_to_workspaces()method - [ ] Remove
_cleanup_cross_project_workspaces()method (no longer needed) - [ ] Remove
DELETE FROM agent_workspacesindelete_agent()anddelete_project() - [ ] Remove
agent_workspacesfrom SCHEMA DDL - [ ] Remove migration call chain from
initialize()
Phase 2: Drop repos table¶
- [ ] Refactor
_resolve_repo_path()to use onlyworkspacestable (noreposfallback) - [ ] Remove
task.repo_idusage — grep forrepo_idin command_handler.py and orchestrator.py - [ ] Remove
Database.create_repo(),get_repo(),list_repos(),delete_repo() - [ ] Remove
_migrate_repos_to_projects()method - [ ] Remove
DELETE FROM reposindelete_project() - [ ] Remove
reposfrom SCHEMA DDL - [ ] Remove
RepoConfigfrom models.py - [ ] Remove ALTER TABLE migrations #2 and #3
Phase 3: Drop legacy columns¶
- [ ] Remove
agents.checkout_pathandagents.repo_idfrom SCHEMA DDL - [ ] Remove
projects.workspace_pathfrom SCHEMA DDL + ALTER migration #1 - [ ] Remove
projects.discord_control_channel_idfrom SCHEMA DDL + ALTER migration #7 - [ ] Remove
tasks.repo_idfrom SCHEMA DDL
Phase 4: Clean up remaining ALTER migrations¶
- [ ] Once confident all production DBs have the columns, remove migrations #4-12
- [ ] The SCHEMA DDL alone is sufficient for new databases