Module migrate

Module migrate 

Source
Expand description

One-shot SQLite → file-registry migration for named instances.

Runs at most once per <registry_root>/.migrated_from_sqlite marker; idempotent and read-only on every SQLite it touches.

P0.3 re-roots the registry to the GLOBAL ~/.agentmux/shared/agents/ registry/, so this scan is generalized from “the current channel’s per-version DBs” to every channel and every dev branch on the machine:

  <home>/channels/<ch>/versions/<v>/data/db/objects.db   (installed/portable)
  <home>/dev/<branch>[/<sub>]/data/db/objects.db         (dev)

Workspace anchoring. Agent workspaces live GLOBALLY at <home>/agents/<name> (verified on disk: real working_directory values are ~/.agentmux/agents/<name>, e.g. …/agents/mazs-0527n), independent of channel/version. row_to_record therefore strips each row’s working_directory against the global <home>/agents root FIRST, then falls back to this row’s own source-channel agents dir (channels/<ch>/agents, or <instance_dir>/agents for dev) for any legacy row that genuinely lived in-channel — each source DB still carries that per-source dir. The two subtrees are disjoint, so the fallback never mis-maps a global workspace. The chosen base is stored absolute in the record, so a reader in ANY channel round-trips source_agents_base.join(working_dir) back to the real workspace. See docs/specs/SPEC_CROSS_CHANNEL_AGENT_PERSISTENCE_2026-06-13.md §11.5.

Structs§

MigrateStats
Outcome stats — surfaced in the marker file + the srv log.
RowSnapshot 🔒
SourceBackfillStats
Outcome of backfill_source_bases_once.
SqliteSource 🔒
A per-(channel,version) / per-dev-branch SQLite source, paired with this source’s own agents dir — the per-channel fallback anchor row_to_record uses when a row’s (normally global) working_directory isn’t under the primary <home>/agents root.

Constants§

MARKER 🔒
Marker filename. Lives in the registry root so the registry’s existence implies the migration question has been asked at least once.
MIGRATION_VERSION 🔒
Bumped when the migration’s mapping logic changes in a way that must re-run on registries an older build already finalized. v2 fixes the workspace anchor: agent workspaces live globally at <home>/agents/<name>, but v1 stripped working_directory against the per-channel channels/<ch>/agents dir, so every global workspace came back “unmappable” (row_to_record returned None) and “My Agents” stayed empty in every channel. A legacy marker (no migration_version: line) reads as 0 and re-runs exactly once; exists_anywhere() keeps the re-run from duplicating already-written records.
SOURCE_BACKFILL_MARKER 🔒
Marker for the one-shot source_agents_base backfill. Separate from MARKER so it runs exactly once even on registries the main migration already finalized before schema v3 existed.

Functions§

backfill_source_bases_once
One-shot backfill of source_agents_base onto registry records written before schema v3 — i.e. by P0.3b’s global migration (#1389) or any pre-P0.4 live mirror.
collect_dev_sources 🔒
Locate dev instance dirs (those with data/db/objects.db) and anchor each on its sibling agents/ dir. The dev layout is at most two levels under dev/: dev/<branch>/data/... (older single-level layout) or dev/<branch>/<sub-hash>/data/.... We check exactly those depths and never descend into an instance’s own subdirs — so an agent workspace (<instance>/agents/<slug>/) that holds a nested AgentMux objects.db is never mistaken for a source (reagent P2), and a dev branch whose slug happens to equal an internal dir name like data/agents is still scanned (no name-based skip-list — codex P2).
empty_to_none 🔒
enumerate_sources 🔒
Enumerate every per-(channel,version) and per-dev-branch objects.db under home, pairing each with its own agents dir (the per-source fallback anchor; the primary anchor in row_to_record is the global <home>/agents).
is_missing_column_or_table 🔒
True iff the error is SQLite reporting “this column/table doesn’t exist in this DB’s schema.” Distinguishes a pre-v8 DB (skip silently — those agents weren’t named, so wouldn’t appear in the dropdown anyway) from corruption (caller logs + continues + defers the marker).
marker_migration_version 🔒
Read the migration_version: line from an existing marker. Returns 0 when the marker is absent, unreadable, or predates versioning (a legacy stats-only marker has no such line) — so a logic bump, or any pre-versioning marker, re-runs the migration exactly once.
migrate_from_sqlite_once
Scan every channel + dev objects.db under home and populate the shared registry. Skipped if the marker file exists. Never overwrites an existing registry record (idempotency + respect for newer-written data). The SQLite files are opened read-only — never modified.
push_if_instance 🔒
Push dir as a source iff it holds data/db/objects.db. Returns whether it did (so the caller can treat an instance dir as a leaf).
read_dir_tracking 🔒
read_dir that distinguishes “absent” (fine — nothing to scan) from “present but unreadable” (sets incomplete so the marker defers). Returns None in both error cases; the iterator otherwise.
read_named_rows 🔒
row_to_record 🔒
write_marker 🔒