Skip to content

Commit 28e86c3

Browse files
committed
fix(gossip): separate hallway rooms from chatter hall names
Add a rooms field to ChatterNode and match hallway co-occurrence rooms against node rooms rather than hall names. Regression test uses a room (audit_report) that differs from the node hall (security) to prove the namespaces are no longer conflated. Refs MemPalace#2247
1 parent bbc77a4 commit 28e86c3

2 files changed

Lines changed: 36 additions & 17 deletions

File tree

mempalace/gossip.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"hall": "technical",
7676
"role": "technical_gossip",
7777
"specialties": ["contracts", "backend", "defi", "trading"],
78+
"rooms": ["contracts"],
7879
"gossip_radius": ["orkid", "past-performance"],
7980
"chatter_level": "high",
8081
"propagation_speed": "instant",
@@ -86,6 +87,7 @@
8687
"hall": "general",
8788
"role": "marketing_gossip",
8889
"specialties": ["brand", "messaging", "content", "storytelling"],
90+
"rooms": ["launch_blog"],
8991
"gossip_radius": ["brutal-marketing", "orkid"],
9092
"chatter_level": "high",
9193
"propagation_speed": "instant",
@@ -97,6 +99,7 @@
9799
"hall": "analytics",
98100
"role": "performance_gossip",
99101
"specialties": ["metrics", "validation", "optimization", "data"],
102+
"rooms": ["audit_report"],
100103
"gossip_radius": ["past-performance", "orkid"],
101104
"chatter_level": "high",
102105
"propagation_speed": "instant",
@@ -108,6 +111,7 @@
108111
"hall": "strategy",
109112
"role": "strategy_gossip",
110113
"specialties": ["trading", "revenue", "business", "planning"],
114+
"rooms": ["contracts"],
111115
"gossip_radius": ["orkid", "brutal-marketing"],
112116
"chatter_level": "medium",
113117
"propagation_speed": "fast",
@@ -119,6 +123,7 @@
119123
"hall": "security",
120124
"role": "security_gossip",
121125
"specialties": ["audit", "compliance", "risk", "validation"],
126+
"rooms": ["audit_report"],
122127
"gossip_radius": ["orkid", "brutal-marketing"],
123128
"chatter_level": "medium",
124129
"propagation_speed": "fast",
@@ -130,6 +135,7 @@
130135
"hall": "creative",
131136
"role": "creative_gossip",
132137
"specialties": ["design", "visual", "brand", "aesthetic"],
138+
"rooms": ["launch_blog"],
133139
"gossip_radius": ["brutal-marketing", "orkid"],
134140
"chatter_level": "medium",
135141
"propagation_speed": "fast",
@@ -141,6 +147,7 @@
141147
"hall": "creative",
142148
"role": "theory_gossip",
143149
"specialties": ["information theory", "physics", "complexity", "optimization"],
150+
"rooms": ["launch_blog"],
144151
"gossip_radius": ["negentropy", "orkid"],
145152
"chatter_level": "low",
146153
"propagation_speed": "normal",
@@ -278,6 +285,7 @@ class ChatterNode:
278285
hall: str
279286
role: str
280287
specialties: list[str] = field(default_factory=list)
288+
rooms: list[str] = field(default_factory=list)
281289
gossip_radius: list[str] = field(default_factory=list)
282290
chatter_level: str = "medium"
283291
propagation_speed: str = "normal"
@@ -396,12 +404,13 @@ def detect_topic(self, text: str, priority: Optional[str] = None) -> tuple[str,
396404
def _get_hallway_context(
397405
self, message: GossipMessage
398406
) -> tuple[dict[str, float], set[str]]:
399-
"""Return (hall_scores, related_entities) derived from within-wing hallways.
407+
"""Return (room_scores, related_entities) derived from within-wing hallways.
400408
401409
Hallways are entity-pair co-occurrence records built at mine time. When a
402410
gossip message mentions an entity that co-occurs with other entities in
403-
the source wing, we use those co-occurrences to boost chatter nodes that
404-
live in the same rooms/halls or cover the related entities.
411+
the source wing, we use those co-occurrences to boost chatter nodes whose
412+
``rooms`` overlap with the co-occurrence rooms, as well as nodes whose
413+
specialties overlap with the related entities.
405414
"""
406415
if not message.source_wing:
407416
return {}, set()
@@ -414,7 +423,7 @@ def _get_hallway_context(
414423
return {}, set()
415424

416425
entities = {message.subject.lower(), message.obj.lower()}
417-
hall_scores: dict[str, float] = {}
426+
room_scores: dict[str, float] = {}
418427
related: set[str] = set()
419428

420429
for h in hallways:
@@ -427,11 +436,11 @@ def _get_hallway_context(
427436
for room in h.get("rooms") or []:
428437
room_key = room.lower()
429438
# Accumulate a small boost per co-occurrence in this room.
430-
hall_scores[room_key] = hall_scores.get(room_key, 0.0) + min(
439+
room_scores[room_key] = room_scores.get(room_key, 0.0) + min(
431440
0.15, 0.05 + count * 0.01
432441
)
433442

434-
return hall_scores, related
443+
return room_scores, related
435444

436445
def select_chatter_nodes(
437446
self,
@@ -441,12 +450,12 @@ def select_chatter_nodes(
441450
"""Rank and select chatter nodes for a given message.
442451
443452
Selection combines the base specialty/topic score with within-wing
444-
hallway context: chatter nodes in the source wing whose hall/room
445-
appears in co-occurrence records for the subject/object get a boost, as
446-
do nodes whose specialties overlap with related entities.
453+
hallway context: chatter nodes in the source wing whose ``rooms``
454+
overlap with the co-occurrence rooms get a boost, as do nodes whose
455+
specialties overlap with related entities.
447456
"""
448457
fanout = fanout if fanout is not None else self.config.get("fanout", 5)
449-
hall_scores, related_entities = self._get_hallway_context(message)
458+
room_scores, related_entities = self._get_hallway_context(message)
450459

451460
scored = []
452461
for node in self._chatter_nodes:
@@ -456,8 +465,15 @@ def select_chatter_nodes(
456465
if message.source_wing and normalize_wing_name(
457466
message.source_wing
458467
) == normalize_wing_name(node.wing):
459-
if node.hall and node.hall.lower() in hall_scores:
460-
score += hall_scores[node.hall.lower()]
468+
# Boost by room-level affinity. A node's hall and the
469+
# co-occurrence rooms are in different namespaces, so we match
470+
# rooms to the node's ``rooms`` list rather than to ``hall``.
471+
if room_scores and node.rooms:
472+
overlaps = set(room_scores.keys()) & {
473+
r.lower() for r in node.rooms
474+
}
475+
for room in overlaps:
476+
score += room_scores[room]
461477

462478
# Also boost if a related entity matches a specialty.
463479
if related_entities and node.specialties:

tests/test_gossip.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,11 @@ def test_gossip_config_is_json_serializable():
397397

398398

399399
def test_select_chatter_nodes_uses_hallway_room(monkeypatch):
400-
"""A hallway matching the subject and the node's hall/room boosts selection."""
400+
"""A hallway's co-occurrence rooms boost nodes whose rooms match, even when
401+
the room name differs from the node's hall name."""
401402
kg_path = _temp_db()
402403
try:
403-
protocol = GossipProtocol(kg_path=kg_path)
404+
protocol = GossipProtocol(kg_path=kg_path, config=EXAMPLE_GOSSIP_CONFIG)
404405

405406
def _fake_list_hallways(wing=None, config=None):
406407
if wing != "orkid":
@@ -412,7 +413,8 @@ def _fake_list_hallways(wing=None, config=None):
412413
"entity_a": "audit",
413414
"entity_b": "risk",
414415
"co_occurrence_count": 4,
415-
"rooms": ["security"],
416+
# room name is intentionally different from the node's hall.
417+
"rooms": ["audit_report"],
416418
},
417419
{
418420
"id": "hallway_orkid_audit_compliance_abc12345",
@@ -436,7 +438,8 @@ def _fake_list_hallways(wing=None, config=None):
436438
nodes = protocol.select_chatter_nodes(msg, fanout=3)
437439
ids = [n.id for n in nodes]
438440

439-
# chatter_security is in hall "security" and has specialties audit/risk/compliance.
441+
# chatter_security has hall "security" but room "audit_report"; the
442+
# co-occurrence room "audit_report" should boost it to first place.
440443
assert "chatter_security" in ids
441444
assert ids[0] == "chatter_security"
442445
finally:
@@ -447,7 +450,7 @@ def test_select_chatter_nodes_without_hallways(monkeypatch):
447450
"""When hallways are empty, selection falls back to base scoring."""
448451
kg_path = _temp_db()
449452
try:
450-
protocol = GossipProtocol(kg_path=kg_path)
453+
protocol = GossipProtocol(kg_path=kg_path, config=EXAMPLE_GOSSIP_CONFIG)
451454
monkeypatch.setattr(gossip_mod, "list_hallways", lambda *a, **kw: [])
452455

453456
msg = GossipMessage(

0 commit comments

Comments
 (0)