scene_service.scene_graph.store

Live scene graph and persistent caption/relation caches.

Two single-threaded asyncio writers own disjoint slices:

  • the geometric loop updates nodes and deterministic geometric edges; and

  • the model builder updates semantic edges that geometry cannot decide.

get_snapshot() combines both slices for MCP and web consumers.

Cache persistence uses simple JSON files so the system can survive restarts without re-calling the LLM for every object pair. Only the caption + relation caches are persisted; the live node/edge slices are rebuilt each tick. Cache I/O failures are swallowed — the worst case is a redundant LLM call.

Classes

SceneGraphStore([cache_dir, map_id])

In-memory live graph (two writer slices) + on-disk JSON caches.

class scene_service.scene_graph.store.SceneGraphStore(cache_dir: str = '/data/robonix/scene_graph/cache', map_id: str | None = None)[source]

Bases: object

In-memory live graph (two writer slices) + on-disk JSON caches.

flush_caches() None[source]
get_cached_caption(node: SceneGraphNode) str | None[source]
get_cached_relation(a: SceneGraphNode, b: SceneGraphNode, hint: GeometryHint, semantic_only: bool = False) SceneGraphEdge | None[source]
get_geometric_edges() list[SceneGraphEdge][source]

Current geometric (contact/containment) edges, owned by the fast loop. The builder reads these to skip pairs geometry already decided, spending LLM budget only on the residual.

get_semantic_edges() list[SceneGraphEdge][source]

Current semantic edges — the builder reads these to carry edges forward across rebuilds (hysteresis).

get_snapshot() SceneGraphSnapshot | None[source]

Compose the live graph for read-only consumers (MCP, web).

Nodes come from the geometric loop; edges are the geometric slice followed by the semantic slice. The builder skips pairs geometry already decided, so the slices are disjoint by construction; the dedup on (source, target, relation) — geometric wins — only guards the ~1 s startup window before the geometric debounce emits, and keeps distinct relations on the same pair (e.g. geometric on_top_of + semantic attached_to). Returns None before the first geometric tick has populated anything, preserving the prior “no snapshot yet” contract for callers.

prune_relations(live_object_ids: set[str]) int[source]

Drop cached edges with an endpoint no longer in the registry. Returns how many were evicted. Complements put_cached_relation’s keep-latest-per-pair eviction: that bounds growth across an object’s moves, this bounds it across object departures. Called by the builder each rebuild before flushing to disk.

put_cached_caption(node: SceneGraphNode) None[source]
put_cached_relation(a: SceneGraphNode, b: SceneGraphNode, hint: GeometryHint, edge: SceneGraphEdge, semantic_only: bool = False) None[source]
set_geometric(nodes: list[SceneGraphNode], edges: list[SceneGraphEdge]) None[source]

Replace the geometric slice (nodes + contact/containment edges). The dict/list references are swapped in one step, so a concurrent get_snapshot() never sees a half-updated geometric layer. Called by the fast geometric loop each tick.

set_semantic_edges(edges: list[SceneGraphEdge]) None[source]

Replace the semantic (LLM-inferred) edge slice. Called by the builder once per rebuild, after residual inference + hysteresis.