Skip to content

Commit 5d820c4

Browse files
codexclaude
andcommitted
fix(ui): the thought stream had no reader
Seventy-two modules emit into `ThoughtEmitter`. It broadcasts to listeners that call `register`, and nothing in the codebase calls it. The neural feed is fed from the event bus, which the interface bridge subscribes to with a wildcard, so every one of those emits has been going nowhere — including a pursuit narrating each choice as it made it. Bridging inside the emitter rather than at the call sites makes all of them visible at once, and anything written later reaches the interface without having to know any of this. A bus that is missing or broken degrades and the work continues; a thought that cannot be shown is not worth failing the work that produced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5df3b22 commit 5d820c4

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

‎core/thought_stream.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,40 @@ async def unregister(self, queue):
5252
if queue in self.listeners:
5353
self.listeners.discard(queue)
5454

55+
@staticmethod
56+
def _also_tell_the_interface(message: dict) -> None:
57+
"""Put the thought where the interface is actually looking.
58+
59+
This emitter broadcasts to listeners that register with it, and nothing
60+
in the codebase registers — seventy-two modules have been emitting into
61+
a channel with no reader. The neural feed is fed from the event bus,
62+
which the interface bridge subscribes to with a wildcard.
63+
64+
Bridging here rather than at the call sites means every existing
65+
emitter becomes visible at once, and anything written later reaches the
66+
interface without having to know this.
67+
"""
68+
69+
try:
70+
from core.event_bus import get_event_bus
71+
72+
bus = get_event_bus()
73+
if bus is None:
74+
return
75+
payload = {
76+
"content": str(message.get("content") or ""),
77+
"phase": str(message.get("category") or "cognition"),
78+
"title": str(message.get("title") or ""),
79+
"urgency": "NORMAL" if message.get("level") != "warning" else "HIGH",
80+
}
81+
publish = getattr(bus, "publish_threadsafe", None)
82+
if callable(publish):
83+
publish("thoughts", payload)
84+
except Exception as exc:
85+
# A thought that cannot be shown is not worth failing the work that
86+
# produced it.
87+
record_degradation("thought_stream", exc, action="thought not bridged to the interface")
88+
5589
def emit(self, title: str, content: str, level: str = "info", category: str = "General", **kwargs):
5690
"""Broadcast a thought/event to all listeners.
5791
Thread-safe: Can be called from sync threads (Orchestrator).
@@ -65,6 +99,8 @@ def emit(self, title: str, content: str, level: str = "info", category: str = "G
6599
}
66100
message.update(kwargs)
67101

102+
self._also_tell_the_interface(message)
103+
68104
with self._lock:
69105
loop = self._loop
70106
if loop is None:
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Seventy-two modules emitted into a channel with no reader.
2+
3+
`ThoughtEmitter` broadcasts to listeners that call `register`, and nothing in
4+
the codebase calls it. The interface's neural feed is fed from the event bus
5+
instead, so every one of those emits was invisible — including a pursuit
6+
narrating its choices while it worked.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import core.thought_stream as thought_stream
12+
from core.thought_stream import ThoughtEmitter
13+
14+
15+
class _Bus:
16+
def __init__(self):
17+
self.published = []
18+
19+
def publish_threadsafe(self, topic, data, priority=None):
20+
self.published.append((topic, data))
21+
22+
23+
def test_an_emitted_thought_is_published_where_the_interface_reads(monkeypatch):
24+
bus = _Bus()
25+
monkeypatch.setattr("core.event_bus.get_event_bus", lambda: bus)
26+
27+
ThoughtEmitter().emit("Browsing", "Question 4 -> I agree", category="ToolExecution")
28+
29+
assert bus.published, "the thought never reached the bus the interface subscribes to"
30+
topic, payload = bus.published[-1]
31+
assert topic == "thoughts", "the bridge forwards the topic the UI renders as a thought"
32+
assert payload["content"] == "Question 4 -> I agree"
33+
assert payload["title"] == "Browsing"
34+
35+
36+
def test_a_broken_bus_never_breaks_the_work(monkeypatch):
37+
def explode():
38+
raise RuntimeError("bus down")
39+
40+
monkeypatch.setattr("core.event_bus.get_event_bus", explode)
41+
recorded = []
42+
monkeypatch.setattr(
43+
thought_stream, "record_degradation", lambda *a, **k: recorded.append(a)
44+
)
45+
46+
ThoughtEmitter().emit("Browsing", "still working")
47+
48+
assert recorded, "a thought that cannot be shown must degrade, not vanish silently"

0 commit comments

Comments
 (0)