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
- 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.
- 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.
- 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.
graph_statsreports an empty palace graph: missing wings, and explicit tunnels are never countedVersion: mempalace 3.5.0 · chromadb 1.5.9 · Python 3.12.7 · Ubuntu 22.04.5 LTS
Affected:
mempalace.palace_graph.graph_stats()and themempalace_graph_statsMCP toolSummary
On a palace with 5 wings and 9 rooms,
graph_stats()reports 6 rooms, omits two wingsentirely, and returns
tunnel_rooms: 0, total_edges: 0— while 44 explicit tunnels existin
tunnels.jsonand the roomgeneraldemonstrably appears in three different wings.The practical effect is that a healthy, well-connected palace looks empty through this
API. We were evaluating
graph_statsas the summary endpoint for a cross-projectdashboard 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):5 wings, 9 rooms. Note
generalappears in three wings.Reproduction
Expected
rooms_per_wingcovering all 5 wingstotal_rooms= 9tunnel_rooms>= 1 (generalspans 3 wings)"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: 44matlab-driveanddesercion-pascualare absent entirely.desertionreports 2 rooms; it has 3.tunnel_rooms/total_edgesare 0 despitegeneralspanning three wings.Two independent defects
1.
build_graphsilently sees a strict subset of the collectiongraph_statsderives everything frombuild_graph(), which paginates ChromaDB metadata:We ran that identical pagination by hand against the same collection object, in the
same process, immediately before calling
build_graph(col):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 and6 rooms. The wings it drops (
matlab-drive38 +desercion-pascual706 = 744 records)account exactly for the shortfall: 3552 − 744 = 2808, the number of records reflected in
its output.
This is a defect in
build_graphitself, not in the store, not a cache (a cold call in afresh process is identical), and not concurrency (the raw read was complete under the same
conditions).
Because the wings containing the room
generalare among those dropped, the passive-tunneltest (
len(node["wings"]) >= 2) can never fire — which is why both tunnel metrics are 0rather than merely undercounted.
2. Explicit tunnels are structurally invisible to
graph_statspalace_graph.pydocuments the distinction clearly:graph_statsonly ever computes the passive kind. It never readstunnels.json, so the44 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
build_graphcover every recordcol.get()will page through — the reproductionabove shows a hand-rolled loop over the same collection recovering all 5 wings while
build_graphrecovers 3. At minimum, have it report skipped/unparsable records sosilent omission is impossible.
graph_stats, or add explicit counts(
explicit_tunnels,passive_tunnel_rooms) so the two kinds are distinguishable andneither is silently dropped.
mempalace statusandgraph_stats— theycurrently 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:
list_tunnels()also returns correct, complete data. The issue appears confined tobuild_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 ofgraph_stats. Flagging it becausethe 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.