Skip to content

Commit be53f15

Browse files
authored
chore: bump v0.16.0 (#3095)
2 parents d8b3bb8 + c99ff56 commit be53f15

174 files changed

Lines changed: 12901 additions & 2583 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
name: DB migrations and schema changes
3+
description: >-
4+
Workflows and commands for managing Alembic database migrations and schema changes
5+
in the letta-cloud core app, including using uv, just, LETTA_PG_URI, and
6+
switching between SQLite and Postgres.
7+
---
8+
9+
# DB migrations and schema changes (letta-cloud core)
10+
11+
Use this skill whenever you need to change the database schema or debug Alembic
12+
migrations in `apps/core` of the letta-cloud repo.
13+
14+
This skill assumes:
15+
- Working directory: `apps/core`
16+
- Migrations: Alembic in `apps/core/alembic`
17+
- Python runner: `uv`
18+
- Helper: `just ready` for environment + DB setup
19+
20+
## Quick start
21+
22+
1. Ensure environment is ready:
23+
- `just ready`
24+
2. For Postgres migrations, set:
25+
- `export LETTA_PG_URI=postgresql+pg8000://postgres:postgres@localhost:5432/letta-core`
26+
3. Make your ORM/schema change.
27+
4. Autogenerate migration:
28+
- `uv run alembic revision --autogenerate -m "<short_message>"`
29+
5. Apply migration:
30+
- `uv run alembic upgrade head`
31+
32+
See `references/migration-commands.md` for exact commands and variants.
33+
34+
## Standard workflows
35+
36+
### 1. Add or modify a column (ORM-first)
37+
38+
1. Identify the ORM model and table.
39+
2. Update the SQLAlchemy model in `letta/orm/...`:
40+
- Prefer using mixins (e.g. `ProjectMixin`) when available instead of
41+
duplicating columns.
42+
3. Run `just ready` if dependencies or environment may have changed.
43+
4. Ensure `LETTA_PG_URI` is set if you want the migration to target Postgres.
44+
5. Autogenerate Alembic revision with `uv`.
45+
6. Inspect the generated file under `alembic/versions/`:
46+
- Confirm `op.add_column` / `op.alter_column` match expectations.
47+
7. Apply migrations with `uv run alembic upgrade head`.
48+
49+
Use this pattern for changes like adding `project_id` columns via `ProjectMixin`.
50+
51+
### 2. Data backfill / one-off data migration
52+
53+
1. Make sure the schema change (if any) is already represented in ORM + Alembic.
54+
2. Create a new Alembic revision **without** autogenerate (or edit an
55+
autogen file) and add Python logic in `upgrade()` that:
56+
- Uses `op.get_bind()` and SQLAlchemy Core/SQL to backfill data.
57+
3. Keep `downgrade()` simple and safe (ideally reversible).
58+
4. Run against Postgres with `LETTA_PG_URI` set, using `uv run alembic upgrade head`.
59+
60+
### 3. Fixing a bad migration
61+
62+
Typical cases:
63+
- Migration fails only on SQLite (ALTER constraint limitations).
64+
- Migration was generated while pointing at SQLite instead of Postgres.
65+
66+
Workflow:
67+
1. Identify the failing revision in `alembic/versions/`.
68+
2. If failure is SQLite-specific, prefer running migrations against Postgres by
69+
exporting `LETTA_PG_URI` and re-running upgrade.
70+
3. If logic is wrong, create a **new** migration that fixes the problem rather
71+
than editing an applied revision (especially in shared environments).
72+
4. For purely local/dev history, you can delete and regenerate migrations but
73+
only if no one else depends on them.
74+
75+
See `references/sqlite-vs-postgres-gotchas.md` for SQLite-specific issues.
76+
77+
### 4. Switching between SQLite and Postgres
78+
79+
Alembic picks the engine based on `letta.settings.DatabaseChoice` and
80+
environment variables.
81+
82+
General rules:
83+
- For local dev stateful runs, `just ready` handles baseline migrations.
84+
- For schema design and production-like migrations, prefer Postgres and set
85+
`LETTA_PG_URI`.
86+
87+
Workflow for Postgres-targeted migration:
88+
1. `export LETTA_PG_URI=postgresql+pg8000://postgres:postgres@localhost:5432/letta-core`
89+
2. From `apps/core`:
90+
- `uv run alembic upgrade head`
91+
- `uv run alembic revision --autogenerate -m "..."`
92+
93+
## Troubleshooting
94+
95+
- **"Target database is not up to date" when autogenerating**
96+
- First run `uv run alembic upgrade head` (with appropriate engine/URI).
97+
- **SQLite NotImplementedError about ALTER CONSTRAINT**
98+
- Switch to Postgres by setting `LETTA_PG_URI` and rerun.
99+
- **Autogenerated migration missing expected changes**
100+
- Ensure ORM imports and metadata (`Base.metadata`) are correct and that the
101+
changed model is imported in Alembic env context.
102+
- **Autogenerated migration has unexpected drops/renames**
103+
- Review model changes; consider explicit operations instead of relying on
104+
autogenerate.
105+
106+
## References
107+
108+
- `references/migration-commands.md` — canonical commands for `uv`, Alembic,
109+
and `just`.
110+
- `references/sqlite-vs-postgres-gotchas.md` — engine-specific pitfalls and
111+
how to avoid them.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Migration commands (letta-cloud core)
2+
3+
Working directory for all commands: `apps/core`.
4+
5+
## Environment setup
6+
7+
- One-shot environment + DB setup:
8+
- `just ready`
9+
10+
## Postgres connection
11+
12+
Set the Postgres URI (adjust as needed for your env):
13+
14+
```bash
15+
export LETTA_PG_URI=postgresql+pg8000://postgres:postgres@localhost:5432/letta-core
16+
```
17+
18+
Alembic will log the effective URL (e.g. `Using database: postgresql+pg8000://...`).
19+
20+
## Alembic basics (with uv)
21+
22+
- Upgrade to latest:
23+
24+
```bash
25+
uv run alembic upgrade head
26+
```
27+
28+
- Downgrade one step:
29+
30+
```bash
31+
uv run alembic downgrade -1
32+
```
33+
34+
- Downgrade to a specific revision:
35+
36+
```bash
37+
uv run alembic downgrade <revision_id>
38+
```
39+
40+
- Generate new revision (autogenerate):
41+
42+
```bash
43+
uv run alembic revision --autogenerate -m "short_message"
44+
```
45+
46+
- Generate empty revision (manual operations):
47+
48+
```bash
49+
uv run alembic revision -m "manual_migration"
50+
```
51+
52+
## Typical workflow snippets
53+
54+
### Add/modify column
55+
56+
```bash
57+
cd apps/core
58+
just ready # optional but recommended
59+
export LETTA_PG_URI=postgresql+pg8000://postgres:postgres@localhost:5432/letta-core
60+
uv run alembic upgrade head # ensure DB is up to date
61+
uv run alembic revision --autogenerate -m "add_<column>_to_<table>"
62+
uv run alembic upgrade head
63+
```
64+
65+
### Re-run last migration after edit (local only)
66+
67+
```bash
68+
cd apps/core
69+
export LETTA_PG_URI=postgresql+pg8000://postgres:postgres@localhost:5432/letta-core
70+
uv run alembic downgrade -1
71+
uv run alembic upgrade head
72+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# SQLite vs Postgres gotchas (letta-cloud core)
2+
3+
## SQLite limitations
4+
5+
SQLite has limited support for `ALTER TABLE`, especially around constraints and
6+
foreign keys. In Alembic this often shows up as:
7+
8+
- `NotImplementedError: No support for ALTER of constraints in SQLite dialect...`
9+
10+
In `apps/core`, you may hit this when running migrations against SQLite that
11+
drop or change foreign keys or constraints.
12+
13+
### How to handle
14+
15+
- Prefer running schema-changing migrations against Postgres by setting
16+
`LETTA_PG_URI` and using:
17+
18+
```bash
19+
uv run alembic upgrade head
20+
```
21+
22+
- If you must support SQLite, use Alembic batch mode patterns, but for this
23+
project most complex migrations should target Postgres.
24+
25+
## Autogenerate differences
26+
27+
Running `alembic revision --autogenerate` against SQLite vs Postgres can
28+
produce different diffs (especially around indexes and constraints).
29+
30+
Recommendations:
31+
32+
- For production/real migrations, always autogenerate against Postgres.
33+
- If you see lots of unexpected index drops/adds, confirm which engine the
34+
migration is inspecting and rerun with Postgres.
35+
36+
## Engine selection reminder
37+
38+
- Engine is controlled by `letta.settings.DatabaseChoice` and environment
39+
(notably `LETTA_PG_URI`).
40+
- `just ready` will also run migrations; ensure your desired engine is set
41+
before relying on its database steps.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Add prompt_tokens_details to steps table
2+
3+
Revision ID: 175dd10fb916
4+
Revises: b1c2d3e4f5a6
5+
Create Date: 2025-11-28 12:00:00.000000
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "175dd10fb916"
17+
down_revision: Union[str, None] = "b1c2d3e4f5a6"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
# Add prompt_tokens_details JSON column to steps table
24+
# This stores detailed prompt token breakdown (cached_tokens, cache_read_tokens, cache_creation_tokens)
25+
op.add_column("steps", sa.Column("prompt_tokens_details", sa.JSON(), nullable=True))
26+
27+
28+
def downgrade() -> None:
29+
op.drop_column("steps", "prompt_tokens_details")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""add project_id to tools
2+
3+
Revision ID: 2e5e90d3cdf8
4+
Revises: af842aa6f743
5+
Create Date: 2025-12-03 11:55:57.355341
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "2e5e90d3cdf8"
17+
down_revision: Union[str, None] = "af842aa6f743"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
op.add_column("tools", sa.Column("project_id", sa.String(), nullable=True))
24+
25+
26+
def downgrade() -> None:
27+
op.drop_column("tools", "project_id")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""add tool indexes for organization_id
2+
3+
Revision ID: af842aa6f743
4+
Revises: 175dd10fb916
5+
Create Date: 2025-12-07 15:30:43.407495
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "af842aa6f743"
17+
down_revision: Union[str, None] = "175dd10fb916"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_index("ix_tools_organization_id", "tools", ["organization_id"], unique=False)
25+
op.create_index("ix_tools_organization_id_name", "tools", ["organization_id", "name"], unique=False)
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade() -> None:
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.create_index(op.f("ix_step_metrics_run_id"), "step_metrics", ["run_id"], unique=False)
32+
op.create_index(op.f("idx_messages_step_id"), "messages", ["step_id"], unique=False)
33+
# ### end Alembic commands ###
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""add compaction_settings to agents table
2+
3+
Revision ID: d0880aae6cee
4+
Revises: 2e5e90d3cdf8
5+
Create Date: 2025-12-10 16:17:23.595775
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
from letta.orm.custom_columns import CompactionSettingsColumn
15+
16+
# revision identifiers, used by Alembic.
17+
revision: str = "d0880aae6cee"
18+
down_revision: Union[str, None] = "2e5e90d3cdf8"
19+
branch_labels: Union[str, Sequence[str], None] = None
20+
depends_on: Union[str, Sequence[str], None] = None
21+
22+
23+
def upgrade() -> None:
24+
op.add_column("agents", sa.Column("compaction_settings", CompactionSettingsColumn(), nullable=True))
25+
26+
27+
def downgrade() -> None:
28+
op.drop_column("agents", "compaction_settings")

0 commit comments

Comments
 (0)