23. State DB schema
AstraCode хранит метаданные сессий и tracing-логи в двух SQLite-БД:
state_5.sqlite и logs_2.sqlite. Суффиксы _5 и _2 исторические и не
являются текущими версиями schema; обе схемы развиваются через sqlx migrations
внутри существующих файлов.
Реализация — крейт state/. Миграции — SQL-файлы в state/migrations/.
1. Файлы БД
| Файл | Назначение |
|---|---|
~/.astracode/state_5.sqlite |
Threads metadata, goals, agent jobs, dynamic tools |
~/.astracode/state_5.sqlite-shm |
Shared memory (WAL вспомогательный) |
~/.astracode/state_5.sqlite-wal |
Write-Ahead Log |
~/.astracode/logs_2.sqlite |
Tracing logs |
~/.astracode/logs_2.sqlite-shm |
WAL вспомогательный |
~/.astracode/logs_2.sqlite-wal |
Write-Ahead Log |
Константы — state/src/lib.rs:
pub const STATE_DB_FILENAME: &str = "state_5.sqlite"; pub const LOGS_DB_FILENAME: &str = "logs_2.sqlite";
Отдельных констант STATE_DB_VERSION/LOGS_DB_VERSION нет. Комментарий рядом
с константами прямо фиксирует, что старый механизм file bump/delete больше не
используется. Актуальные схемы задают state/migrations/ и
state/logs_migrations/.
⚠️ Не удалять -wal/-shm файлы во время работы AstraCode — это сломает БД. Они исчезают сами при корректном shutdown через checkpoint.
2. Настройки SQLite
| Опция | Значение | Зачем |
|---|---|---|
journal_mode |
WAL |
Конкурентные read/write без блокировок |
synchronous |
Normal |
Не fsync после каждого write, fsync на checkpoint |
auto_vacuum |
Incremental |
Постепенная очистка освободившихся страниц |
busy_timeout |
5s |
Ждать блокировки до 5 секунд вместо немедленной ошибки |
foreign_keys принудительно не включается в base_sqlite_options; ссылочная целостность обеспечивается миграциями там, где нужно (PRAGMA foreign_keys=ON в конкретных миграциях, напр. 0030_thread_goal_stopped_statuses.sql). auto_vacuum = Incremental задаётся в open_state_sqlite/open_logs_sqlite, а не в base_sqlite_options.
WAL+Normal даёт высокую производительность при большом числе сессий, ценой того, что при kill -9 могут потеряться последние ~1 секунду записей.
3. Таблицы state_5.sqlite
threads — основная таблица
Хранит metadata всех сессий. Полное содержимое сессии — в JSONL rollout (см. 21-rollout-format.md); здесь только индекс для быстрого поиска.
| Колонка | Тип | Описание |
|---|---|---|
id |
TEXT PRIMARY KEY | UUID треда (ThreadId) |
rollout_path |
TEXT NOT NULL | Путь к .jsonl файлу |
created_at |
INTEGER | Unix timestamp в миллисекундах (с миграции 0025) |
updated_at |
INTEGER | Unix timestamp ms |
source |
TEXT | cli/vscode/external_agent/... |
model_provider |
TEXT | id провайдера (openai, anthropic, ...) |
cwd |
TEXT | Абсолютная рабочая директория |
title |
TEXT | Заголовок (обычно — первое user сообщение, truncated) |
sandbox_policy |
TEXT | JSON-сериализованный snapshot политики |
approval_mode |
TEXT | JSON-сериализованный approval-режим |
tokens_used |
INTEGER | Накопительно по сессии |
has_user_event |
INTEGER (0/1) | Есть ли хотя бы одно user message |
archived |
INTEGER (0/1) | Заархивирован ли тред |
archived_at |
INTEGER (nullable) | Когда заархивирован |
git_sha |
TEXT | SHA коммита на момент создания |
git_branch |
TEXT | Активная ветка |
git_origin_url |
TEXT | URL origin remote |
Индексы:
idx_threads_created_at (created_at DESC, id DESC) idx_threads_updated_at (updated_at DESC, id DESC) idx_threads_archived (archived) idx_threads_source (source) idx_threads_provider (model_provider) idx_threads_cwd (cwd) -- миграция 0027
Запросы типа «10 последних сессий в этом репо» используют idx_threads_cwd + idx_threads_updated_at.
Определение — state/migrations/0001_threads.sql.
thread_dynamic_tools — динамические tools на тред
| Колонка | Тип |
|---|---|
thread_id |
TEXT NOT NULL → threads(id) ON DELETE CASCADE |
position |
INTEGER NOT NULL |
name |
TEXT NOT NULL |
description |
TEXT NOT NULL |
input_schema |
TEXT NOT NULL (JSON) |
namespace |
TEXT (добавлен миграцией 0026) |
PRIMARY KEY (thread_id, position).
Каждая строка — один динамический tool (не JSON-массив). Используется для runtime-зарегистрированных tools (от плагинов и пр.) — чтобы при resume сессии restore их definitions.
thread_goals — долгоиграющие цели
С миграции 0029. Соответствует команде /goal (см. 13-goal-and-modes.md).
| Колонка | Тип | Описание |
|---|---|---|
thread_id |
TEXT PRIMARY KEY → threads(id) |
|
goal_id |
TEXT | UUID цели |
objective |
TEXT | Текст цели |
status |
TEXT | active / paused / blocked / complete / usage_limited / budget_limited |
token_budget |
INTEGER (nullable) | Лимит в токенах |
tokens_used |
INTEGER | Накопительно |
time_used_seconds |
INTEGER | Накопительно |
created_at_ms |
INTEGER | Unix ms |
updated_at_ms |
INTEGER | Unix ms |
agent_jobs — batch CSV workflows
Tracking batch-вызовов spawn_agents_on_csv (см. 18-tools-catalog.md).
| Колонка | Тип |
|---|---|
id |
TEXT PRIMARY KEY |
name |
TEXT NOT NULL |
status |
TEXT NOT NULL (running/complete/failed/cancelled) |
instruction |
TEXT NOT NULL |
output_schema_json |
TEXT (nullable) |
input_headers_json |
TEXT NOT NULL |
input_csv_path |
TEXT NOT NULL |
output_csv_path |
TEXT NOT NULL |
auto_export |
INTEGER NOT NULL DEFAULT 1 |
created_at |
INTEGER NOT NULL |
updated_at |
INTEGER NOT NULL |
started_at |
INTEGER (nullable) |
completed_at |
INTEGER (nullable) |
last_error |
TEXT (nullable) |
Строки заданий — отдельная таблица agent_job_items (job_id, item_id, row_index, row_json, status, assigned_thread_id, attempt_count, result_json, ...; FK → agent_jobs(id) ON DELETE CASCADE). Миграция 0014/0015.
backfill_state — миграция rollout → DB
Для прогресса фоновой задачи backfill (заполнение threads из старых JSONL-файлов после апгрейда).
| Колонка | Тип | Описание |
|---|---|---|
id |
INTEGER PRIMARY KEY (CHECK = 1) | Синглтон-строка |
status |
TEXT | Running / Complete |
last_watermark |
TEXT | Точка прогресса в FS |
last_success_at |
INTEGER (nullable) | TS последнего успешного шага |
updated_at |
INTEGER |
Использует 15-минутный lease, чтобы две параллельные сессии AstraCode не делали одну и ту же работу.
Другие таблицы
Зависит от миграций (могут добавиться позже): thread_spawn_edges (связи сабагентов, 0021), stage1_outputs (Phase 1 памяти, 0006), jobs (фоновые job'ы памяти, 0006), device_key_bindings (0028), remote_control_enrollments (0024). Таблиц ui_state/approvals_audit/model_catalog_cache/tool_uses_summary в БД нет — состояние UI лежит в config.toml ([ui_state]), кэш моделей — в файле models_cache.json. Точный список — grep CREATE TABLE state/migrations/*.sql.
4. Таблицы logs_2.sqlite
logs — append-only трассировка
Логи tracing-крейта (debug / info / warn / error). Отдельная БД — чтобы log-write не блокировал критичные транзакции в state_5.
| Колонка | Тип | Описание |
|---|---|---|
id |
INTEGER PRIMARY KEY AUTOINCREMENT | |
ts |
INTEGER | Unix seconds |
ts_nanos |
INTEGER | Доп. наносекунды |
level |
TEXT | DEBUG/INFO/WARN/ERROR |
target |
TEXT | tracing target (модуль) |
feedback_log_body |
TEXT | Тело лога (форматированные fields + message); в SELECT-запросах часто алиасится как message |
module_path |
TEXT | Полный путь модуля |
file |
TEXT | Имя файла |
line |
INTEGER | Номер строки |
thread_id |
TEXT (nullable) | UUID треда, если применимо |
process_uuid |
TEXT (nullable) | UUID процесса (pid:<pid>:<uuid>) |
estimated_bytes |
INTEGER NOT NULL DEFAULT 0 | Оценка размера строки (для cleanup-политик) |
Индексы:
idx_logs_ts (ts DESC, ts_nanos DESC, id DESC) idx_logs_thread_id (thread_id) idx_logs_thread_id_ts (thread_id, ts DESC, ts_nanos DESC, id DESC) idx_logs_process_uuid_threadless_ts (process_uuid, ts ...) WHERE thread_id IS NULL
idx_logs_ts — для типичного «последние 1000 строк лога». Колонка feedback_log_body появилась в logs_migrations/0002 (переименование message); estimated_bytes добавлен позже.
5. Миграции
Используется sqlx::migrate::Migrator. Файлы — state/migrations/0001_*.sql, 0002_*.sql, ... Запускаются последовательно при первом обращении к БД (StateRuntime::init()).
sqlx хранит таблицу _sqlx_migrations с историей применённых миграций.
Ключевые изменения
| # | Когда | Что |
|---|---|---|
| 0001 | начало | CREATE TABLE threads |
| 0006 | … | CREATE TABLE stage1_outputs, jobs (память) |
| 0008 | … | CREATE TABLE backfill_state |
| 0010 | … | logs.process_uuid (в state DB; позже таблица logs дропнута в 0023) |
| 0011 | … | индексы idx_logs_thread_id_ts для cleanup |
| 0012 | … | logs.estimated_bytes |
| 0014 | … | CREATE TABLE agent_jobs, agent_job_items |
| 0025 | … | threads.created_at/updated_at → milliseconds (раньше секунды) |
| 0023 | … | DROP TABLE logs в state DB (логи переехали в logs_2.sqlite) |
| 0027 | … | idx_threads_cwd |
| 0029 | … | CREATE TABLE thread_goals |
| 0030 | … | новые статусы для goals (usage_limited, budget_limited) |
Для logs_2.sqlite — отдельный набор state/logs_migrations/: 0001 создаёт logs, 0002 переименовывает message → feedback_log_body.
Backwards compatibility
- Миграции только forward — нет downgrade-скриптов.
- Runtime migrator устанавливает
ignore_missing = true, поэтому более старый бинарник может открыть БД, где уже отмечены неизвестные ему более новые миграции. Checksum известных миграций при этом продолжает проверяться. - Это не полная гарантия downgrade compatibility: старый код не знает новых колонок и семантики. Перед откатом остановите все процессы и сделайте backup; не удаляйте рабочую БД как первый способ восстановления.
6. Cleanup и ротация
logs ротация
logs может расти быстро (особенно с RUST_LOG=debug). В state/src/runtime.rs реализован cleanup по партициям:
- Лимит на партицию (thread_id или process_uuid): 10 MiB.
- Лимит на партицию: 1000 строк.
- При превышении — старые записи удаляются автоматически (использует
idx_logs_partition_prune).
state cleanup
Для логической очистки используйте archive/delete action в /sessions.
Прямое удаление строк из threads не удаляет соответствующие rollout-файлы и
может рассогласовать индекс с файловым хранилищем.
Если требуется полностью пересоздать state DB, сначала остановите все процессы,
сделайте backup state_5.sqlite вместе с WAL/SHM и sessions/, затем
переместите всю SQLite-группу. create_if_missing(true) и migrator создадут
новую БД при следующем запуске.
7. Backup
Что бэкапить:
tar czf astracode-state-backup.tar.gz \ ~/.astracode/state_5.sqlite \ ~/.astracode/sessions/ # без этого state без полезной нагрузки
Перед backup корректно завершите все процессы AstraCode, чтобы WAL был checkpoint'нут в основной файл. Для hot backup работающей БД используйте SQLite backup API, а не копирование меняющихся файлов по отдельности.
Не бэкапить logs_2.sqlite — это эфемерные данные, переживут лосс без проблем.
8. Reading DB вручную
# 10 последних сессий в этой директории
sqlite3 ~/.astracode/state_5.sqlite "
SELECT id, title, datetime(updated_at/1000, 'unixepoch') as updated, tokens_used
FROM threads
WHERE cwd = '$(pwd)'
ORDER BY updated_at DESC
LIMIT 10;
"
# Активные цели
sqlite3 ~/.astracode/state_5.sqlite "
SELECT t.title, g.objective, g.status, g.tokens_used, g.token_budget
FROM thread_goals g
JOIN threads t ON t.id = g.thread_id
WHERE g.status IN ('active', 'paused')
ORDER BY g.updated_at_ms DESC;
"
# Последние ошибки в логах
sqlite3 ~/.astracode/logs_2.sqlite "
SELECT datetime(ts, 'unixepoch'), level, target, feedback_log_body AS message
FROM logs
WHERE level IN ('ERROR', 'WARN')
ORDER BY ts DESC, ts_nanos DESC
LIMIT 50;
"
# Активность по дням
sqlite3 ~/.astracode/state_5.sqlite "
SELECT date(created_at/1000, 'unixepoch') as day, COUNT(*) as threads, SUM(tokens_used) as tokens
FROM threads
GROUP BY day
ORDER BY day DESC
LIMIT 30;
"
9. Связь с rollout
Каждая строка в threads ссылается через rollout_path на JSONL-файл (см. 21-rollout-format.md). Это две координированные системы:
- Authoritative source — rollout JSONL: туда пишутся все события сессии в полном объёме.
- Index —
threadsтаблица: денормализованный snapshot для быстрого поиска (без чтения JSONL).
При запуске AstraCode проверяет backfill_state — если он Running, запускает фоновую задачу синхронизации (сканирует ~/.astracode/sessions/ и заполняет threads для файлов, которых ещё нет в индексе).
Если threads.rollout_path указывает на отсутствующий файл, сессия уже не
recoverable. При чтении списка runtime обнаруживает stale path и вызывает
delete_thread, удаляя соответствующую metadata row. Поэтому ручное удаление
rollout-файлов является потерей данных, а не поддерживаемой очисткой.
10. Где определены типы
- Точка входа:
state/src/lib.rs. - Runtime:
state/src/runtime.rs. - Миграции:
state/migrations/*.sql. - Структуры (Threads, ThreadGoals, AgentJobs):
state/src/*.rs.
11. Производительность
- List threads: ~1ms на 10k тредов (индекс по
updated_at). - Insert thread: ~3-5ms (с fsync на checkpoint).
- Log write: ~50µs (batch'ится в фоновую транзакцию).
- Cleanup logs: ~10-50ms при превышении партиций.
Bottleneck чаще не БД, а сетевой LLM-запрос — на его фоне SQLite не виден.