Хранение состояния сессий и запусков
Статус: принято 29 июля 2026 года.
Контекст
Session runs ранее хранились в одном authoritative runs.jsonl для каждой
session. Обновление одного run разбирало и переписывало все retained runs,
поэтому стоимость одной update росла вместе с session history, хотя менялся
bounded state одного run.
Замена должна сохранить прозрачные recoverable files, append order, redaction, per-session serialization, Windows-safe replacement, legacy state и rebuildable SQLite projections. Она не должна ослаблять durability barrier или делать derived index authoritative.
Варианты
Append-only versioned run-state log
Каждый patch мог бы добавлять новую version, а latest-offset projection разрешала бы current state. Writes остались бы последовательными, но retained history росла бы с каждым patch, compaction стала бы отдельной migration, а append-order pages пришлось бы различать run identity и state-version order.
Per-run current-state files
Каждый run может иметь один atomically replaced current-state file с его стабильной append position. Небольшой append-order file остаётся rebuildable projection. Тогда update одного run читает и заменяет один bounded file.
Решение
Использовать per-run current-state files.
runs.jsonlостаётся immutable migration input для legacy sessions.- Authoritative current records находятся в
run_records/. Имена файлов — SHA-256 digests run IDs, поэтому caller-provided IDs не могут выйти из session directory. - Каждый record хранит schema version, immutable append position и redacted run payload.
run_order.jsonlиrun_revision.jsonявляются derived. Их можно удалить или перестроить сканированием current-state files с сортировкой по position.- Migration сериализуется тем же per-session lock, что append и patch. Durable
marker записывается до materialization; revision публикуется только после
готовности всех current-state files и order projection. Restart с marker
детерминированно повторяет migration из
runs.jsonl. - Patch читает и atomically replaces только выбранный current-state file.
Temporary и destination files находятся в одной directory, handles закрыты
до
os.replace, а authoritative file flushed и synced до replacement. - Concurrent patches берут один per-session process/thread lock и перечитывают current state внутри lock, поэтому сохраняют поля предыдущего patch.
- Существующий SQLite lookup остаётся derived и перестраивается из current files и append positions.
Storage не описывается как transactional. Будущий bounded write-batch contract может координировать несколько файлов с явными recovery markers, но не должен заявлять crash atomicity, которой это решение не предоставляет.
Последствия
Read/write работа update одного run не зависит от числа retained runs. Full
export и legacy list_runs остаются явными O(N) operations. Во время migration
нужно дополнительное место, потому что runs.jsonl сохраняется как recoverable
input; cleanup требует отдельного compatibility decision.