Skip to content

Migration and compatibility boundaries

MindRoom keeps substantive historical schemas and representations in named legacy modules. Historical-format production owners use legacy_<subject>.py beside their current owner, including one-time upgrades and recurring old-data readers. The current storage or lifecycle owner keeps its transaction, locking, validation, authorization, retry, and current-format processing. Small field defaults stay with their current model when extraction would add indirection without isolating a meaningful migration. Dependency-owned migrations and authoritative SaaS data remain under their existing owners.

This page maps every item from the migration audit to its current owner and disposition. It describes the current source rather than promising support for every earlier release.

Each legacy compatibility comment starts with # LEGACY_COMPAT: <short description of the legacy format>, followed by its release provenance, handling, and coverage. The marker also applies to documented defaults and compatibility notes kept beside current owners. Find all marked Python rules, including the SaaS backend, from the repository root:

rg -n -F 'LEGACY_COMPAT:' --glob '*.py'

Status terms

Status Meaning
Isolated Historical input remains supported through a named boundary.
Removed/superseded The earlier reader is gone or a newer cutoff replaces it.
Dependency-owned An installed library owns the migration.
Current behavior The code serves a current format, protocol, safety rule, or authoring interface.
Tiny retained default A small default stays with its current owner because extraction would add complexity.

Named boundaries

The first fourteen rows were created or renamed by the migration-boundary isolation work. The remaining rows include existing focused boundaries and later audit additions.

Boundary Trigger and current caller Retained guarantees
src/mindroom/mcp_gateway/legacy_schema.py GatewayOAuthStore calls migrate_schema when opening SQLite. The store retains its writer transaction, base DDL, and live processing; ordered expiry, accounting, lifecycle, account, and token-cutoff upgrades stay together.
src/mindroom/legacy_usage_storage.py Startup or direct storage opening finds a session database without an independent usage table. Import only retained facts, publishing the table and seed atomically; current reporting never reads old run representations.
src/mindroom/legacy_session_storage.py Run deletion or initial usage migration encounters an Agno 2 runs blob. Current rows win by run_id; descendant deletion, transaction ownership, and diagnostics remain with current owners.
src/mindroom/legacy_openai_tool_replay.py OpenAI-family adapters encounter missing tool arguments, sparse placeholders, or Agno-only Responses spans without reusable ordered output. Request-only repairs preserve call/result links while supplying empty arguments, removing placeholder pairs, and dropping unverifiable reasoning tails and provider item IDs; canonical content rendering stays in openai_response_replay.py.
src/mindroom/legacy_handled_turns.py HandledTurnLedger finds tracking/<agent>_responded.json. Insert-only adoption protects newer rows, fills absent indexes, retries interrupted work, and renames only after adoption.
src/mindroom/event_journal/legacy_turn_records.py The handled-turn importer adopts missing journal indexes. The journal transaction is retained and migration writes never use current upsert deletion semantics.
src/mindroom/event_journal/legacy_response_attempts.py Backend startup finds released approval/outbox tables without response_attempts. One schema transaction adopts stable source identity, preserves pending approvals and frozen wire payloads, and aborts corrupt required live ownership; a second open does not repeat adoption.
src/mindroom/legacy_delivery_payloads.py Outbox reads or Matrix writes encounter inline FINAL results and the bounded marker. Old inline outcomes keep rolling-writer precedence, current local results remain authoritative otherwise, and full recovery data stays off the wire.
src/mindroom/legacy_approval_payloads.py Approval claim or resume encounters missing historical context or the older card ID. Current approval ownership, authorization, exact-call checks, transaction settlement, and failure handling remain with current owners.
src/mindroom/event_journal/legacy_approval_recovery.py Approval settlement encounters an INITIAL retired by historical deleted-response cleanup. The helper proves exact source and response tombstones with no FINAL; current owners retain card expiration, failure fencing, retries, locking, and transactional settlement.
src/mindroom/matrix/legacy_sync_continuity.py SyncContinuityStore loads a valid v2 or v3 record. The helper validates the old shape, discards its checkpoint, increments revision once, and lets the store rewrite v4 under lock.
src/mindroom/script_runs/legacy_schema.py ScriptRunStore finds missing resource snapshot columns. Schema creation and the transaction stay in the store; old rows receive the established null or empty-map values.
src/mindroom/knowledge/legacy_metadata.py Knowledge parsing sees absent or empty optional filter fields. Current parsing still rejects unknown fields; missing corpus settings retain empty historical sentinels and rebuild only when the corresponding current corpus-compatibility value differs.
src/mindroom/matrix/legacy_state.py Matrix state has accounts without a domain or a noncanonical serialized shape. Runtime-domain resolution, parsing, caching, and atomic persistence stay in matrix/state.py; rewrites happen only when data differs.
src/mindroom/config/legacy_fields.py Agent or defaults validation sees a retired field. Pydantic remains the strict validation boundary and the helper provides directed replacement errors.
src/mindroom/legacy_streaming.py Streaming replay encounters body-only [cancelled] or [error] suffixes (each preceded by one space). Current markers stay in streaming.py; execution_preparation.py gives recognized structured status precedence and delegates body fallback to the streaming reader.
src/mindroom/legacy_revision_replay.py Turn-record merges and redaction cleanup encounter reconstructed revision provenance from pre-v2026.9.43 summaries. Current revision facts win, storage mutation stays in turn_store.py, and source-only summary ownership applies only to labeled historical replay.
src/mindroom/event_journal/legacy_schema.py A journal lacks Nio-owned matrix_sync_consumers, or a current approval generation has executable calls without toolkit origins. Schema setup preserves history and frozen deliveries while retiring obsolete work; unresumable approvals enter normal failure recovery before another card decision.
src/mindroom/config/legacy_access.py Config loading or mindroom config migrate finds retired access fields. Complete-source validation, concrete grants, a backup, and atomic membership-schema publication are retained.
src/mindroom/legacy_private_storage.py, legacy_private_storage_aliases.py, and private_storage_paths.py Startup finds a verified private scope with the historical requester spelling. Intent records, owner and inode checks, worker quiescence, ordered renames, and verified aliases protect recovery and current callers.
src/mindroom/session_storage_preflight.py An owned session table lacks required Agno columns. The recovery lock, SQLite rollback recovery, and whole-directory archive complete before current storage creation.
src/mindroom/oauth/legacy_credentials.py The OAuth SQLite store normalizes a retired field or verifies a lossless requester binding. The store retains schema, scope, revision, reset-receipt, transaction, and rollback ownership; old OAuth JSON is not adopted.
src/mindroom/matrix/legacy_crypto_upgrade.py Nio first takes durable ownership of a pre-durable crypto store. Nio's file lease and account/device checks protect keys and trust while only retired recovery rows are cleared.
src/mindroom/script_runs/legacy_recovery.py and src/mindroom/workers/backends/kubernetes.py Script-runtime startup encounters an unversioned recovery signature or a v2 digest from before the optional seccomp or RuntimeClass fields. Only an exact recomputation permits migration; pre-seccomp workers require an unset current seccomp policy, an unset RuntimeClass retains the pre-RuntimeClass digest bytes, and the current store owns atomic signature replacement and rejects concurrent revocation or signature changes.
src/mindroom/desktop/legacy_command_journal.py The desktop SQLite journal finds JSON v1 receipts during its one-time import. Historical validation stays isolated; the journal retains file permissions, atomic import, replay tombstones, response delivery state, sequence maxima, and admission capacity.

Python provenance and regression coverage

Each historical Python boundary carries a source block naming its legacy format, last native old writer and replacement, current handling, and meaningful regression coverage. Those source blocks are authoritative for exact release details and test node IDs; this index groups boundaries that share a behavioral test surface. Last legacy release means the final stable tagged release whose native writer or typed model emitted the old representation, not the final reader that accepted it. Continued acceptance or reader removal belongs in Handling, separate from the writer cutoff and replacement release. When no stable tag contained an old native writer, the block uses an honest unreleased, unversioned external-input, or no-tagged-model classification; schema-based recovery likewise states that it has no single release cutoff.

Boundary owners Behavioral evidence
mcp_gateway/legacy_schema.py Gateway OAuth, capacity, lifecycle, and account tests exercise the staged schema upgrades and released token cutoff.
legacy_usage_storage.py tests/test_legacy_usage_storage.py covers mixed schemas, current-row precedence, unknown dates, interruption rollback and retry, dormant stores, symlink isolation, and both startup entry points.
legacy_session_storage.py Run-storage tests use a frozen Agno 2 fixture for merge, deletion, descendant, malformed-data, and transaction behavior; usage tests cover import precedence and available usage.
legacy_openai_tool_replay.py OpenAI model tests cover missing arguments and placeholder pairs; Responses replay tests and history tests cover reasoning tails, filtered call/result links, bounded SQLite replay, and unchanged canonical state.
legacy_handled_turns.py and event_journal/legacy_turn_records.py Handled-turn tests cover released JSON shapes, the deliberate unversioned cutoff, interrupted adoption, occupied indexes, reopen behavior, and reconstructed replay facts.
event_journal/legacy_schema.py Journal upgrade tests cover released DDL, missing and already-added toolkit columns, rejection of stale approval clicks, preserved current generations and frozen deliveries, and stable repeat opening on both database backends.
legacy_delivery_payloads.py and legacy_approval_payloads.py Journal store, response runner, and approval tests cover visible-result precedence, wire sanitation, frozen visibility, origin recovery, and sparse card identity.
event_journal/legacy_approval_recovery.py Journal store tests preserve incomplete deletion proof and FINAL debt; response runner tests recover every approval state only after card expiration, without editing deleted responses or resuming tools.
matrix/legacy_sync_continuity.py and matrix/legacy_crypto_upgrade.py Sync-continuity tests cover complete v2/v3 conversion and retry, while crypto upgrade tests verify that identity, keys, and trust survive retirement of transport recovery.
script_runs/legacy_schema.py Script-run tests rebuild the literal old table, preserve every old value, add empty resource snapshots, and verify a second open.
script_runs/legacy_recovery.py and workers/backends/kubernetes.py Script lifecycle tests cover exact legacy adoption and rejection after authority changes; Kubernetes worker tests cover byte-compatible unset RuntimeClass authority and rejection after configuration changes; script-run tests verify atomic signature replacement.
knowledge/legacy_metadata.py Knowledge indexing tests use independently written metadata from each field boundary and check preservation, nonmutation, repeated normalization, and corpus/query compatibility.
matrix/legacy_state.py and matrix/users.py Matrix identity and agent manager tests preserve durable account state, verify stable reloads, and exercise the missing-request fallback without network registration.
config/legacy_access.py and config/legacy_fields.py Access migration tests cover validated conversion, exact backup bytes, stable publication, and rejection paths; configuration tests cover every directed retired-field error.
legacy_private_storage.py and legacy_private_storage_aliases.py Private-storage tests cover verified owner relocation, content preservation, historical aliases, and tamper rejection.
oauth/legacy_credentials.py and oauth/credential_store.py OAuth store tests cover literal SQLite bindings, publication normalization, the removed JSON reader, reconnect disposition, and inert old files.
memory/auto_flush.py, report_publishing/store.py, scheduling.py, external_triggers/replay_store.py, and cli/owner.py Memory, report, scheduling, trigger replay, and pairing tests drive the retained defaults through their public read or mutation paths.
legacy_streaming.py and execution_preparation.py Partial-reply and streaming tests cover bounded historical suffixes, exact stripping order, current structured-status precedence, and interruption classification.
legacy_revision_replay.py Revision replay, turn-store, and handled-turn tests cover reconstruction, monotonic preservation, historical and modern selection, and cold-reopen cleanup.
session_storage_preflight.py Session recovery tests cover schema-based archive, locks, rollback recovery, unrelated tables, current corruption, and byte preservation without inventing one release cutoff.
desktop/legacy_command_journal.py Desktop journal tests cover bodyless started receipts, retained sequence high-watermarks, deferred response replay, repeated opens, and independent current admission capacity.
SSO cookie routes SSO endpoint tests assert exact shared-domain and host-only expiry cookies on both endpoints and retain current host-only behavior for localhost, IP addresses, and single-label hosts.

This index intentionally excludes current authoring shorthands, protocol adapters, recovery rules, and caches that tolerate unknown versions because those are active interfaces rather than evidence of a retired native writer. Sparse publication, job, and failure fields in knowledge/index_metadata.py remain a current writer contract: the writer still omits optional values and the reader accepts those sparse in-progress and failed records. Dependency-owned schemas remain attributed to their dependency, and removed readers remain documented as removed rather than recreated only to obtain conversion coverage. The coverage delivered here is limited to Python owners, including the SSO route; inventoried SQL migrations, browser cleanup, and infrastructure setup below remain outside this implementation and carry no new annotation or test claim.

The explicit response attempt schema replaces ownership inference used by the last verified native writer, v2026.9.137. SQLite holds its startup writer transaction and PostgreSQL its schema advisory lock while the migration runs. Literal released SQL and JSON in response attempt migration tests cover both backends, rollback, repeated opens, inline result precedence, ordinary completed turns, and retained delivery debt. The migration assumes no active responses; current callers pass typed ownership independently of terminal recovery snapshots.

Journal, delivery, approvals, and sync

Journal IDs use J to avoid colliding with credential IDs.

ID Status Owner and reason
J1 Isolated legacy_handled_turns.py adopts pre-journal JSON once and renames it.
J2 Isolated handled_turns.py keeps current sparse fields, turn_store.py owns Agno run recovery and cleanup, legacy_handled_turns.py reconstructs absent historical revision facts, and legacy_revision_replay.py owns their preservation and source-only summary decisions.
J3 Removed/superseded event_journal/legacy_schema.py replaces the old additive conversion framework with the Nio ownership cutoff.
J4 Removed/superseded event_journal/legacy_schema.py drops old interactive tables instead of archiving or translating them.
J5 Removed/superseded event_journal/legacy_schema.py retires the old response outbox rather than converting delivery debt.
J6 Removed/superseded event_journal/legacy_schema.py retires all pre-cutoff delivery tables, replacing the earlier unfenced-outbox guard.
J7 Removed/superseded event_journal/legacy_schema.py drops old approval transport and continuations rather than converting or tombstoning them.
J8 Isolated legacy_delivery_payloads.py owns inline FINAL results, precedence, markers, and wire sanitation.
J9 Isolated legacy_approval_payloads.py rebuilds historical optional context; approval_execution.py keeps current exact execution gates.
J10 Isolated legacy_approval_payloads.py owns the old card ID alias; approval_manager.py keeps current authentication, retries, tombstones, and fail-closed behavior.
J11 Isolated matrix/legacy_sync_continuity.py converts valid v2/v3 join fences to current v4 and discards the obsolete checkpoint.
J12 Removed/superseded The upgrade fixture confirms old event-cache and dispatch-obligation files have no runtime reader and remain untouched.
J13 Current behavior event_journal_open.py owns binding, generation, adoption, and database ownership guards.
J14 Current behavior sync_restart_retry.py and visible_response_reconciliation.py keep current replay and visible-response safety.
J15 Isolated event_journal/legacy_approval_recovery.py recognizes approvals stranded by historical INITIAL retirement; current owners retain consent, failure handling, and settlement.

Agent state, history, memory, and knowledge

ID Status Owner and reason
S1 Isolated script_runs/legacy_schema.py adds old missing resource columns inside the current store transaction.
S2 Isolated knowledge/legacy_metadata.py normalizes absent or empty optional filters.
S3 Isolated knowledge/legacy_metadata.py retains empty historical sentinels for missing corpus settings, rebuilding only when the corresponding current corpus-compatibility value differs.
S4 Current behavior knowledge/index_metadata.py deliberately writes and reads sparse publication, job, and failure lifecycle fields.
S5 Current behavior knowledge/collections.py protects current default and live collections as well as older published layouts.
S6 Tiny retained default memory/auto_flush.py discards two retired location fields while current worker identity sanitation and queue defaults remain.
S7 Removed/superseded memory/functions.py no longer contains or exports the monolithic memory-prompt wrapper.
S8 Current behavior ai_runtime.py supports string input and deep-copies canonical message sequences for retries.
S9 Isolated legacy_openai_tool_replay.py repairs old stored calls; current sparse-stream filtering stays in the adapters.
S10 Current behavior agent_storage.py and history/replay.py own the current prompt persistence and replay boundary.
S11 Removed/superseded thread_export/storage.py refuses populated markerless roots and marks only empty roots.
S12 Tiny retained default report_publishing/store.py treats missing artifact_kind as html_file.
S13 Tiny retained default scheduling.py treats missing history_limit as the current None default.
S14 Current behavior history/storage.py reads current v2 compaction state and ignores v1.
S15 Current behavior knowledge/candidate_checkpoint.py rebuilds unknown versions and retains current torn-tail recovery.
S16 Current behavior external_triggers/store.py and external_triggers/replay_store.py own current validation and replay deduplication; the only retained historical default supplies an empty threads map when that section is absent.
S17 Current behavior Receipts, todos, attachments, and workflows combine sparse fields with current identity and integrity checks.
S18 Isolated session_storage_preflight.py archives incompatible owned sessions; MindRoom does not invoke Agno's historical migration manager.
S19 Isolated legacy_session_storage.py owns Agno 2 blob scrub and double-JSON decoding; other Agno readers remain dependency-owned.
S20 Dependency-owned memory/config.py leaves Mem0's history rewrite and default history path to Mem0.

Configuration and credentials

ID Status Owner and reason
C1 Isolated config/legacy_access.py converts retired reply-permission lists into membership access.
C2 Current behavior config/main.py accepts YAML null for optional root sections as a supported authoring form.
C3 Current behavior config/main.py normalizes supported string plugin entries to objects.
C4 Current behavior tool_system/plugin_imports.py supports bare packages beside paths and explicit Python specs.
C5 Current behavior config/models.py reads and emits current string, compact, and explicit tool entries.
C6 Current behavior tool_system/metadata.py keeps current text/list override interoperability.
C7 Isolated config/legacy_fields.py rejects retired fields with directed errors.
C8 Current behavior config/memory.py supports memory: none.
C9 Removed/superseded cli/migrate.py now applies access migration; the exact-text starter-memory converter is gone.
C10 Tiny retained default cli/owner.py replaces both old and current owner placeholders during pairing.
C11 Current behavior constants.py owns current config, environment, and path selection without relocating data.
C12 Current behavior cli/local_stack.py retains existing local-chat flags and container names.
A1 Current behavior credentials.py uses JSON, including its encrypted envelope, for generic services.
A2 Current behavior credentials_sync.py treats missing _source as manually owned instead of overwriting it from the environment.
A3 Current behavior credentials.py grants untagged shared credentials only through current allowlists and worker policy.
A4 Current behavior credentials_sync.py supports current inline, named, embedder, and shared OpenAI credential sources.
A5 Current behavior credentials_sync.py supports current provider aliases and NAME or NAME_FILE secrets.

OAuth, Matrix state, and tools

ID Status Owner and reason
O1 Removed/superseded oauth/credential_store.py no longer adopts OAuth JSON; JSON-only tokens require reconnection and files remain untouched.
O2 Removed/superseded oauth/credential_store.py no longer performs opaque or deferred old JSON adoption.
O3 Isolated oauth/legacy_credentials.py removes the old publication field; obsolete JSON and sidecar cleanup is gone.
O4 Current behavior oauth/credential_store.py owns schema, private-file, and scope-binding validation.
O5 Current behavior oauth/credential_lifecycle.py owns target resolution, generation checks, and reset receipts.
O6 Current behavior oauth/client.py and credential_lifecycle.py support active provider dialects and configured original authentication.
M1 Isolated matrix/legacy_state.py backfills domains and asks the current state owner to rewrite noncanonical data.
M2 Tiny retained default matrix/users.py falls back from missing requested_username to persisted actual username.
M3 Removed/superseded thread_tags.py reads only one state event per thread-tag pair; the old thread-wide overlay is gone.
T1 Current behavior tools/python.py publishes both installer names over one implementation.
T2 Current behavior tools/agentql.py adapts the currently installed AgentQL and browser-stealth combination.
T3 Current behavior tools/brandfetch.py and custom_tools/coding.py retain public option and tool naming.
T4 Current behavior api/dynamic_workflows.py deliberately returns 404 for the unscoped private-report route.
T5 Current behavior egress/policy.py retains the older allowlist path environment name as a deny-safe alias.
T6 Current behavior Provider adapters serve current request, replay, sampling, and tool-schema APIs.
T7 Current behavior tool_system/skills.py supports OpenClaw metadata eligibility and its active tool preset.

Dependencies, SaaS, and deployment

These rows are checked manually because the original inventory used section headings rather than IDs.

ID Status Owner and reason
D1 Dependency-owned matrix/legacy_crypto_upgrade.py isolates the pre-durable cutoff, while Nio owns its current SQLite schema and preserves crypto and trust records.
D2 Dependency-owned knowledge/indexing_config.py owns corpus compatibility, while Chroma owns storage-engine migrations.
D3 Dependency-owned memory/config.py leaves Mem0's history-table rewrite and possibly external default history path to Mem0.
D4 Current behavior The three SaaS SQL files remain explicit migrations for authoritative account, subscription, instance, payment, usage, audit, and grant data.
D5 Current behavior SSO cookie cleanup, Terraform relocation, and root service-worker cleanup remain deployment-owned; logger aliases and UI preferences are current state.
D6 Current behavior Worker protocol checks and desktop protocol checks protect current execution, identity reuse, metadata recovery, and replay gates.
D7 Current behavior Provider, Matrix protocol, dependency, and cancellation adapters remain necessary after database reset.
D8 Current behavior session_storage_preflight.py is the concrete owned-session archive boundary; reset remains an explicit owner policy, not a generic exception fallback.
D9 Current behavior Usage diagnostics, model overrides, invited rooms, and vocabulary cache deliberately use weak retention without an old conversion chain.

Upgrade and reset limits

An incompatible format is different from a locked database, permission failure, missing key, full disk, or current-schema corruption. Migration owners reject those failures rather than converting them into deletion. The OAuth credential and sync-continuity stores reject unsupported versions; other owners retain their existing version policies. Several sparse readers deliberately ignore unknown fields or drop malformed reconstructible records.

Additional small compatibility branches stay with current readers. execution_preparation.py classifies structured stream status first and uses the old [cancelled] and [error] body suffixes (each preceded by one space) owned by legacy_streaming.py only through the streaming reader fallback. Interrupted visible replies are excluded; eligible in-progress text is cleaned before it is included in model context. external_triggers/replay_store.py supplies an empty threads map for replay stores written before thread keys existed.

A journal replacement must coordinate its generation binding with the next Nio baseline. Agno sessions may still contain current handled-turn recovery facts and historical run blobs, while Matrix keeps visible messages and state independently of local storage. Private storage moves require stopped primaries and absent managed workers, as described in Private Storage Migration. Usage discovery ignores verified historical primary and session aliases because their canonical directories are scanned separately; unverified symlinks still report incomplete coverage. The Nio cutoff abandons pre-durable pending transport work while preserving crypto material, as described in Nio 1.0 Upgrade. Dependency migrations use their dependency's schema and locking contract, and SaaS databases are never treated as reconstructible caches.