Skip to content

graph_stats reports an empty palace graph: build_graph sees a strict subset of the collection, and explicit tunnels are never counted #2260

Description

@drojasd

graph_stats reports an empty palace graph: missing wings, and explicit tunnels are never counted

Version: mempalace 3.5.0 · chromadb 1.5.9 · Python 3.12.7 · Ubuntu 22.04.5 LTS
Affected: mempalace.palace_graph.graph_stats() and the mempalace_graph_stats MCP tool

Summary

On a palace with 5 wings and 9 rooms, graph_stats() reports 6 rooms, omits two wings
entirely, and returns tunnel_rooms: 0, total_edges: 0 — while 44 explicit tunnels exist
in tunnels.json and the room general demonstrably appears in three different wings.

The practical effect is that a healthy, well-connected palace looks empty through this
API. We were evaluating graph_stats as the summary endpoint for a cross-project
dashboard and would have reported "no graph" to users.

This is not a stale cache — a cold call in a fresh Python process returns identical
numbers.

Environment / palace shape

mempalace status (ground truth):

WING: desercion            ROOM: fact                        1 drawers
WING: desercion-pascual    ROOM: general                   706 drawers
WING: desertion            ROOM: general                  2646 drawers
                           ROOM: heatstgnn-model-selection    2 drawers
                           ROOM: diary                       1 drawers
WING: matlab-drive         ROOM: general                    38 drawers
WING: octopus              ROOM: documentation              87 drawers
                           ROOM: plans                      68 drawers
                           ROOM: controller                  3 drawers

5 wings, 9 rooms. Note general appears in three wings.

Reproduction

from mempalace.palace_graph import graph_stats, list_tunnels
print(graph_stats())
print("explicit tunnels:", len(list_tunnels()))

Expected

  • rooms_per_wing covering all 5 wings
  • total_rooms = 9
  • tunnel_rooms >= 1 (general spans 3 wings)
  • the 44 explicit tunnels reflected somewhere in a function documented as
    "Palace graph overview: total rooms, tunnel connections, edges between wings"

Actual

{
  'total_rooms': 6,
  'tunnel_rooms': 0,
  'total_edges': 0,
  'rooms_per_wing': {'octopus': 3, 'desertion': 2, 'desercion': 1},
  'top_tunnels': []
}
explicit tunnels: 44
  • Wings matlab-drive and desercion-pascual are absent entirely.
  • desertion reports 2 rooms; it has 3.
  • tunnel_rooms/total_edges are 0 despite general spanning three wings.

Two independent defects

1. build_graph silently sees a strict subset of the collection

graph_stats derives everything from build_graph(), which paginates ChromaDB metadata:

total = col.count()
offset = 0
while offset < total:
    batch = col.get(limit=1000, offset=offset, include=["metadatas"])

We ran that identical pagination by hand against the same collection object, in the
same process, immediately before calling build_graph(col):

collection.count() = 3552
records actually paged: 3552      (metadata without a wing: 0)
RAW wings in metadata: {'desertion': 2649, 'desercion': 1, 'matlab-drive': 38,
                        'octopus': 158, 'desercion-pascual': 706}
RAW distinct (wing, room) pairs: 9        <- matches `mempalace status` exactly

build_graph nodes: 6   edges: 0
build_graph wings: ['desercion', 'desertion', 'octopus']
rooms spanning >=2 wings per build_graph: none

So the data is complete and every record carries wing metadata — the raw loop recovers all
5 wings and all 9 rooms. build_graph, given the same collection, returns 3 wings and
6 rooms. The wings it drops (matlab-drive 38 + desercion-pascual 706 = 744 records)
account exactly for the shortfall: 3552 − 744 = 2808, the number of records reflected in
its output.

This is a defect in build_graph itself, not in the store, not a cache (a cold call in a
fresh process is identical), and not concurrency (the raw read was complete under the same
conditions).

Because the wings containing the room general are among those dropped, the passive-tunnel
test (len(node["wings"]) >= 2) can never fire — which is why both tunnel metrics are 0
rather than merely undercounted.

2. Explicit tunnels are structurally invisible to graph_stats

palace_graph.py documents the distinction clearly:

Passive tunnels are discovered from shared room names across wings.
Explicit tunnels are created by agents when they notice a connection
between two specific drawers or rooms in different wings/projects.

graph_stats only ever computes the passive kind. It never reads tunnels.json, so the
44 explicit tunnels created by the miner (Entity tunnels: +N cross-wing entity link(s))
do not appear in the graph overview at all.

This may be intentional internally, but from the outside it is surprising: the tool
description promises a palace graph overview, and a caller has no way to know the number
excludes the tunnels the miner just reported creating.

Suggested fix

  1. Make build_graph cover every record col.get() will page through — the reproduction
    above shows a hand-rolled loop over the same collection recovering all 5 wings while
    build_graph recovers 3. At minimum, have it report skipped/unparsable records so
    silent omission is impossible.
  2. Either include explicit tunnels in graph_stats, or add explicit counts
    (explicit_tunnels, passive_tunnel_rooms) so the two kinds are distinguishable and
    neither is silently dropped.
  3. Consider a consistency check between mempalace status and graph_stats — they
    currently disagree about how many wings and rooms exist, which is what made this
    noticeable.

Not affected (checked)

The knowledge-graph layer (mempalace.knowledge_graph.KnowledgeGraph,
knowledge_graph.sqlite3) is a separate store and behaved correctly in isolated testing,
including temporal validity:

kg.add_triple("project:x", "canonical_root", "/old", valid_from="2026-07-01", valid_to="2026-08-14")
kg.add_triple("project:x", "canonical_root", "/new", valid_from="2026-08-14")
kg.query_entity("project:x", as_of="2026-07-15")  # -> /old   correct
kg.query_entity("project:x", as_of="2026-08-20")  # -> /new   correct

list_tunnels() also returns correct, complete data. The issue appears confined to
build_graph/graph_stats.

Impact for us

We are building a machine-wide project graph on top of MemPalace and have routed around
this by using list_tunnels() and the KG API instead of graph_stats. Flagging it because
the failure mode is silent: a healthy palace reports an empty graph, with no error.

Happy to run any diagnostic against this palace — it reproduces reliably.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions