Guide: Add a Migration
Worked example: adding a retry_count column to tool_calls. Two edits — the model and the migration dict — plus a verification step most people skip.
Background: Migrations — why the platform uses create_all + a startup column-adder instead of Alembic.
Step 1 — Add the column to the model
retry_count = Column(Integer, nullable=True)The mechanism can only ADD COLUMN. The column must be nullable or defaulted — SQLite cannot add a NOT-NULL column without a default to a table with rows. Renames, type changes, drops, and backfills are out of scope (decision flowchart).
Step 2 — Add the matching migration entry
tool_call_columns = {
"model_name": "VARCHAR(100)",
"estimated_tokens": "INTEGER",
"estimated_cost": "VARCHAR(30)",
"retry_count": "INTEGER", # ← new
}The SQL type must match the model's column type (Integer → INTEGER, String(30) → VARCHAR(30), Text → TEXT).
Different table? Add a parallel dict + PRAGMA/ALTER block for that table in the same function — the existing block is the template:
task_columns = {"archived": "VARCHAR(10)"}
with engine.begin() as connection:
existing = {row[1] for row in connection.execute(text("PRAGMA table_info(tasks)"))}
for name, type_ in task_columns.items():
if name not in existing:
connection.execute(text(f"ALTER TABLE tasks ADD COLUMN {name} {type_}"))Step 3 — Expose it (if the API should return it)
retry_count: int | None = NoneAnd mirror it in any frontend type that renders tool calls (typing discipline).
Step 4 — Verify against an existing database
This is the step that catches real breakage — fresh DBs always work (they get the column from create_all); it's long-lived dev DBs that break:
cd backend/app
uv run uvicorn main:app --reload --port 8001 # boot against your existing agentops.db
sqlite3 agentops.db "PRAGMA table_info(tool_calls);" # confirm retry_count appears
curl http://localhost:8001/agents/1/tool-calls | jq '.[0]' # no 'no such column' errorIf you see sqlite3.OperationalError: no such column, the migration entry is missing or misspelled — the failure signature.
Step 5 — Document
Update ORM Models, the Schema ERD, and the schema page if API-visible. Column semantics belong in the docs the moment they exist — undocumented columns rot fastest.
New table instead of new column?
Just define the model — create_all creates it on next boot, no migration entry needed. Remember cascade="all, delete" on the agent relationship if the agent owns it, and an index=True primary key, matching the house schema conventions.
Related pages
- Migrations — strategy and limits
- Database Layer — the code you edited