mempalace.palace_graph_age
Source: mempalace/palace_graph_age.py
Palace structure (Wing → Room → Drawer) as native AGE graph nodes.
Today's palace_graph.build_graph aggregates wing/room/tunnel structure from the drawer table via SQL on every call. This module mirrors that hierarchy into AGE so:
Cypher MATCH walks the palace structure natively — no SQL aggregation per query.
The Entity / MENTIONS layer (from kg_writethrough.py) connects to the palace structure layer via shared Drawer nodes.
The "agent walks into the palace" metaphor becomes a Cypher pattern:
MATCH (w:Wing {name: $wing})-[:CONTAINS]->(r:Room)-[:CONTAINS]-> (d:Drawer)-[:MENTIONS]->(e:Entity) RETURN w, r, d, e
Node labels: Wing — top-level grouping (project / repo / domain) Room — topic within a wing Drawer — individual drawer (one per stored memory) Entity — extracted name (person, project, identifier, ...)
Edge labels: CONTAINS — Wing → Room, Room → Drawer (hierarchical) MENTIONS — Drawer → Entity (from kg_writethrough) SHARED_VIA — Wing ↔ Wing where they share a Room (tunnels)
The populate functions are idempotent: re-running on the same source data MERGEs by name/id rather than blindly creating duplicates. They are restartable.
Read-side helpers (walk_wing, find_drawers_in_room, etc.) are bundled here for convenience, but the canonical query interface is KnowledgeGraphAGE._run_cypher with arbitrary Cypher.
Functions
populate_from_postgres
def populate_from_postgres(kg: KnowledgeGraphAGE, *, dsn: str, table_name: str = 'mempalace_drawers', skip_drawers: bool = False, skip_tunnels: bool = False, batch_log_every: int = 500) -> dictPopulate palace structure into AGE from the drawer table.
Reads the drawer table once, builds Wing/Room/Drawer/SHARED_VIA in AGE. Idempotent — re-runs MERGE on identifier (wing.name, room.name, drawer.id) so existing nodes aren't duplicated.
Args: kg: A KnowledgeGraphAGE instance (already connected, graph initialized). dsn: Postgres DSN to read drawers from (typically the same DSN the KG uses, but kept explicit so cross-database populates remain possible). table_name: Drawer table to read. skip_drawers: If True, only build Wing/Room/SHARED_VIA edges and skip the per-drawer Drawer nodes + CONTAINS edges. Faster for "I just want the high-level palace map" use cases. skip_tunnels: If True, skip SHARED_VIA edges (room→wing adjacency). Useful for first-pass population on huge palaces where you want CONTAINS first.
Returns a counters dict: {wings, rooms, drawers, contains_edges, shared_via_edges}.
walk_wing
def walk_wing(kg: KnowledgeGraphAGE, wing_name: str, depth: int = 2, limit: int = 100) -> listReturn a structured walk of a wing's contents.
Default depth=2 expands Wing → Room → Drawer; depth=3 also pulls in MENTIONS → Entity. Result is a list of dicts: {wing, room, drawer, entity?} — one row per leaf reached at the requested depth.
The "agent walks the palace" primitive — this is what an MCP tool or RLM-orchestrator would call to enumerate what's inside a wing.
list_wings
def list_wings(kg: KnowledgeGraphAGE, limit: int = 100) -> list[str]Return all wing names in the palace.
list_rooms_in_wing
def list_rooms_in_wing(kg: KnowledgeGraphAGE, wing_name: str, limit: int = 100) -> list[str]Return all rooms in the named wing.
list_drawers_in_room
def list_drawers_in_room(kg: KnowledgeGraphAGE, room_name: str, limit: int = 100) -> list[str]Return all drawer ids in the named room (across any wing).
tunnels_from_wing
def tunnels_from_wing(kg: KnowledgeGraphAGE, wing_name: str) -> list[dict]Return all other wings reachable from this one via SHARED_VIA.
