scene_service.persistence

Object persistence — survives scene restarts so the registry warm-starts instead of re-accumulating every stable object through the min_observations filter from scratch.

Backed by an embedded milvus-lite DB owned solely by the scene process (one .db file, single writer — no sharing with system/memory’s memsearch DB). Writes happen at the low-frequency scene-graph builder cadence; reads happen once at boot. Each row carries a caption embedding so a future G3 object-search layer can reuse the same table; when no embedder is wired (e.g. the VLM-fallback perception path that has no CLIP), a fixed placeholder vector is stored so the collection stays well-formed.

Rows are partitioned by map_id: an object’s pose is only meaningful in the map frame of the SLAM map it was observed on, so warm-restore loads exactly the current map’s objects and never mixes two maps. The primary key is the composite “{map_id}::{object_id}” so the same object_id may exist on different maps without colliding on upsert. Until mapping emits a real map identity, map_id is a deploy-controlled string (SCENE_MAP_ID, default “default”) — this stays internal to scene and touches no atlas/MCP contract.

Classes

ObjectStore(db_path, *[, map_id, dim])

milvus-lite-backed store for SceneObject current-state.

class scene_service.persistence.ObjectStore(db_path: str, *, map_id: str = 'default', dim: int = 512)[source]

Bases: object

milvus-lite-backed store for SceneObject current-state.

Upsert is keyed by the composite “{map_id}::{object_id}” primary key, so a row holds the latest state of an object on a given map (no version history in v1 — version mirrors observation_count). All methods are synchronous; callers run them off the asyncio loop or inside an already-held registry lock as appropriate.

close() None[source]

Close the client and release the milvus-lite server so the next boot can re-open the same .db file (mirrors memsearch’s cleanup).

delete_map(map_id: str) int[source]

Delete persisted scene objects belonging to one map partition.

The collection is shared, so the filter is mandatory: deleting a map must never affect objects captured for another spatial map.

load_all(*, limit: int = 16384) list[SceneObject][source]

Return this map’s persisted objects as SceneObject`s for warm restore (rows of other maps are filtered out by `map_id).

Restored objects come back missing=True (known but not currently observed) with their persisted last_seen; re-observation flips them back via data association. The embedding column is intentionally not read back — restore needs only scalar state. A query error is logged at error level and yields an empty list rather than failing boot — this is distinct from a genuinely empty DB, which returns [] silently.

property map_id: str

The sanitized map id this store is scoped to.

persist(pairs: list[tuple[SceneObject, str | None]]) int[source]

Upsert (SceneObject, caption) pairs under this store’s map_id (composite pk “{map_id}::{object_id}”). Caption falls back to the class name when None. Returns the number of rows written; a milvus error is swallowed (logged) so a bad write never kills the builder tick.

rebind(map_id: str) None[source]

Switch subsequent reads/writes to another map partition.

The Milvus collection is shared across maps; rows are filtered by the scalar map_id field and keyed by {map_id}::{object_id}, so a rebind only changes the partition used by persist / load_all.

set_embedder(embed_text: Callable[[list[str]], list[list[float]] | None] | None) None[source]

Inject the caption→vector function (wired from perception’s loaded CLIP text encoder). Safe to pass None — persistence then stores the placeholder vector.