You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Generic store-migration system (file-based, JSONL + markdown) with CLI + app-wide wiring
Mycelium has no database — state is markdown-with-frontmatter, JSONL, JSON, and TOML files under .mycelium/. Today a format/schema change (e.g. #497/#499 moving the transcript from fenced transcript.md → plain transcript.jsonl) has no migration path: we hand-migrate the local dev service and move on. That's fine pre-release (no users), but we'll need a real system before anything hosted/multi-user, and before the next breaking format change lands on a store someone cares about.
This issue tracks a generic, versioned migration framework for the file store — Alembic/Django-migrations in spirit, but for files, spanning both JSONL and markdown surfaces — with CLI commands and interactions wired through the whole app.
Memory files — .mycelium/rooms/{room}/{namespace}/{key}.md, YAML frontmatter carries a version field already. serialize_memory/parse_memory, fastapi-backend/app/services/filesystem.py.
Search index — .mycelium/rooms/{room}/.search-index.jsonl (JSONL). Already has a rebuild path (mycelium reindex → services/reindex, services/indexer, search_index.py); a migration system should reuse/subsume it, not duplicate.
Episode records — .mycelium/rooms/{room}/log/episodes/{id}.md (markdown). l9_episode.py.
Plan namespace — .mycelium/rooms/{room}/plan/*.md.
L9 envelope schema — records embed an SSTP version (currently 0.0.6); a bump may need a record rewrite.
Project config — .mycelium/config.toml. (mycelium config apply regenerates .env; related but distinct.)
What "generic" should mean
A store schema version persisted somewhere durable (e.g. .mycelium/.store-version or a [store] version in config.toml), separate from per-file version fields.
An ordered set of migration steps, each declaring from → to, idempotent, resumable, and format-aware: helpers for "rewrite every JSONL line under glob", "transform frontmatter across every *.md under glob", "rename/move a file", "drop a fenced wrapper", etc.
Dry-run + diff before apply; backup/rollback (or at least a copy-aside) since these mutate user files in place.
Per-room and whole-store scoping (rooms are independent folders; a migration may run room-by-room).
Fail-soft + logged: a partial/failed migration must never orphan data; report exactly what was and wasn't migrated (no silent truncation).
CLI commands (mycelium-cli)
mycelium migrate status — current store version, pending migrations, per-surface counts.
mycelium migrate up [--to VERSION] [--dry-run] [--room NAME] — apply pending (all or scoped), with a diff/preview in dry-run.
mycelium migrate plan — show what each pending step would touch (globs, file counts) without running.
mycelium migrate rollback [--to VERSION] — revert to a prior version where a step defines a reverse.
Fold the existing mycelium reindex in as a migration primitive (rebuild-from-source is the canonical "reindex" step).
Interactions through the app
Backend on startup — detect a store older than the code's expected version; refuse to serve (or auto-migrate behind a flag) rather than read a stale format. Wire into the provision/room-load path (room_channels.py, persister.py init) and /health (surface store_version + pending_migrations).
mycelium doctor — warn on a pending migration, point at mycelium migrate status.
Frontend — surface a "store needs migration" banner when /health reports pending; don't silently render an empty/half-read room.
CLI guardrails — commands that read/write the store check version compatibility and prompt to migrate on mismatch.
Sharing (git) — a pulled room from a peer on a newer store version must be detected and migrated (or rejected) on first local touch.
Non-goals / notes
Not needed for the current release (no users). This is groundwork for hosted/multi-user, which per CLAUDE.md is gated behind real identity (JWT/SPIRE) anyway.
Generic store-migration system (file-based, JSONL + markdown) with CLI + app-wide wiring
Mycelium has no database — state is markdown-with-frontmatter, JSONL, JSON, and TOML files under
.mycelium/. Today a format/schema change (e.g. #497/#499 moving the transcript from fencedtranscript.md→ plaintranscript.jsonl) has no migration path: we hand-migrate the local dev service and move on. That's fine pre-release (no users), but we'll need a real system before anything hosted/multi-user, and before the next breaking format change lands on a store someone cares about.This issue tracks a generic, versioned migration framework for the file store — Alembic/Django-migrations in spirit, but for files, spanning both JSONL and markdown surfaces — with CLI commands and interactions wired through the whole app.
Storage surfaces a migration system must cover
.mycelium/rooms/{room}/log/transcript.jsonl(JSONL; just changed format in fix(persister): append-only JSONL transcript + transcript-backed read path (#497) #499).TRANSCRIPT_FILENAME,fastapi-backend/app/services/persister.py..mycelium/rooms/{room}/{namespace}/{key}.md, YAML frontmatter carries aversionfield already.serialize_memory/parse_memory,fastapi-backend/app/services/filesystem.py..mycelium/rooms/{room}/.search-index.jsonl(JSONL). Already has a rebuild path (mycelium reindex→services/reindex,services/indexer,search_index.py); a migration system should reuse/subsume it, not duplicate..mycelium/rooms/{room}/log/.delivery-cursors.json(JSON)._cursors_path,persister.py..mycelium/rooms/{room}/log/episodes/{id}.md(markdown).l9_episode.py..mycelium/rooms/{room}/plan/*.md.version(currently0.0.6); a bump may need a record rewrite..mycelium/config.toml. (mycelium config applyregenerates.env; related but distinct.)What "generic" should mean
.mycelium/.store-versionor a[store] versioninconfig.toml), separate from per-fileversionfields.from → to, idempotent, resumable, and format-aware: helpers for "rewrite every JSONL line under glob", "transform frontmatter across every*.mdunder glob", "rename/move a file", "drop a fenced wrapper", etc.CLI commands (mycelium-cli)
mycelium migrate status— current store version, pending migrations, per-surface counts.mycelium migrate up [--to VERSION] [--dry-run] [--room NAME]— apply pending (all or scoped), with a diff/preview in dry-run.mycelium migrate plan— show what each pending step would touch (globs, file counts) without running.mycelium migrate rollback [--to VERSION]— revert to a prior version where a step defines a reverse.mycelium reindexin as a migration primitive (rebuild-from-source is the canonical "reindex" step).Interactions through the app
room_channels.py,persister.pyinit) and/health(surfacestore_version+pending_migrations).mycelium doctor— warn on a pending migration, point atmycelium migrate status./healthreports pending; don't silently render an empty/half-read room.migrateon mismatch.Non-goals / notes
.md→.jsonl), where we deliberately ripped out one-off migration code rather than carry it — a generic system is the right home for that logic.