Skip to content

Commit d685e49

Browse files
stonks-gitclaude
andcommitted
Add agent consciousness dashboard (aiohttp, SSE, memory browser)
New dashboard at port 8080 for real-time introspection via Tailscale. AgentState shared dataclass bridges cognitive loop and dashboard. 4 SSE events (cycle_start, llm_response, escalation, gate_flush). Read-only memory browser via direct pool.fetch() (no side effects). Dark theme, vanilla JS, all content via textContent (XSS-safe). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 82dcb63 commit d685e49

File tree

13 files changed

+1174
-31
lines changed

13 files changed

+1174
-31
lines changed

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ WORKDIR /app
2222
# Agent state is mounted, not baked in
2323
VOLUME ["/home/agent/.agent"]
2424

25+
# Dashboard port
26+
EXPOSE 8080
27+
2528
CMD ["python", "-m", "src.main"]

KB/KB_01_architecture.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
1. **Cognitive Loop** (`src/loop.py`) — processes one input at a time through attentional thread. All sources (user, DMN, consolidation, gut) compete for attention.
66
2. **Consolidation** (`src/consolidation.py`) — two-tier: constant background (decay, contradiction scan, pattern detection) + periodic deep cycles (merge, insight, promotion, reconsolidation).
77
3. **Idle Loop / DMN** (`src/idle.py`) — spontaneous thought during downtime, filtered through goals/values, queued for cognitive loop.
8+
4. **Dashboard** (`src/dashboard.py`) — localhost web UI for real-time introspection via SSE + REST API (port 8080).
89

910
## Module Map
1011

@@ -30,6 +31,7 @@
3031
| `tokens.py` | Token counting |
3132
| `stdin_peripheral.py` | Stdin I/O as a peripheral |
3233
| `telegram_peripheral.py` | Telegram Bot API peripheral (raw httpx) |
34+
| `dashboard.py` | Localhost web dashboard (aiohttp, SSE, memory browser) |
3335
| `main.py` | Entry point, initialization, peripheral wiring |
3436

3537
## Tech Stack

KB/KB_03_cognitive_loop.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ Losers decay 0.9x per cycle. User messages naturally suppress DMN.
5050
- **Gut linking:** `gut.link_outcome(outcome_id)` called after each gate decision, forward-linking gut deltas to outcomes
5151
- Linked outcomes available via `get_linked_outcomes()` for PCA analysis in deep consolidation
5252

53+
## Dashboard Integration
54+
55+
- `cognitive_loop()` accepts optional `agent_state=None` parameter
56+
- After creating internal objects (attention, gut, safety, outcome_tracker, bootstrap), assigns them to `agent_state`
57+
- Conversation list shared by reference: `conversation = agent_state.conversation if agent_state else []`
58+
- Publishes 4 SSE event types: `cycle_start`, `llm_response`, `escalation`, `gate_flush`
59+
- All agent_state operations guarded by `if agent_state:` (no-op without dashboard)
60+
- See KB_05_dashboard.md for full details
61+
5362
## Escalation Threshold
5463

5564
Adaptive: 0.3 (bootstrap) -> 0.8 (mature).

KB/KB_05_dashboard.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# KB 05: Dashboard
2+
3+
## Overview
4+
5+
Localhost web dashboard for real-time agent introspection. Serves at `http://0.0.0.0:8080`, accessible via Tailscale at `http://norisor:8080`.
6+
7+
Built with `aiohttp` — runs as another coroutine in `asyncio.gather` alongside the cognitive loop, consolidation, and peripherals. Dashboard crash does not kill the agent.
8+
9+
## Architecture
10+
11+
```
12+
AgentState (dataclass)
13+
created in main.py
14+
written by cognitive_loop (assigns attention, gut, safety, etc.)
15+
read by dashboard handlers
16+
conversation list is shared (same object reference)
17+
SSE broadcast via asyncio.Queue per subscriber
18+
19+
run_dashboard()
20+
aiohttp web.Application
21+
binds 0.0.0.0:8080
22+
waits on shutdown_event
23+
access_log disabled
24+
```
25+
26+
## Data Sharing
27+
28+
`AgentState` is created in `main.py` and passed to both `cognitive_loop()` and `run_dashboard()`.
29+
30+
- Loop assigns internal objects after creation: `agent_state.attention = attention`, etc.
31+
- Conversation list is shared by reference: `conversation = agent_state.conversation`
32+
- Exchange count synced: `agent_state.exchange_count = exchange_count`
33+
- All reads are safe (single event loop, no thread contention).
34+
35+
## SSE Events
36+
37+
The cognitive loop publishes events at 4 points via `agent_state.publish_event()`:
38+
39+
| Event | When | Data |
40+
|-------|------|------|
41+
| `cycle_start` | After winner selected | source, content preview, salience, queue_size |
42+
| `llm_response` | After LLM response | reply preview, confidence, escalated |
43+
| `escalation` | Before System 2 call | triggers, confidence |
44+
| `gate_flush` | After periodic flush | persisted count, dropped count |
45+
46+
Each browser tab gets its own `asyncio.Queue(maxsize=200)`. Events are fire-and-forget: `QueueFull` silently drops the subscriber. SSE keepalive every 15s.
47+
48+
## API Routes
49+
50+
```
51+
GET / -> HTML dashboard (inline, dark theme)
52+
GET /events -> SSE stream (real-time cognitive events)
53+
GET /api/status -> JSON agent state snapshot
54+
GET /api/memories -> JSON paginated memory list (?limit=20&offset=0)
55+
GET /api/memory/{id} -> JSON single memory detail
56+
GET /api/attention -> JSON attention queue contents
57+
GET /api/gut -> JSON gut feeling state + delta log
58+
GET /api/conversation -> JSON current conversation window
59+
GET /api/energy -> JSON energy tracker breakdown
60+
```
61+
62+
## Memory Browser
63+
64+
Uses direct `asyncpg pool.fetch()` with SELECT queries. Does NOT go through `MemoryStore` methods to avoid side effects:
65+
- No access count updates
66+
- No retrieval mutation
67+
- The agent doesn't "feel" you browsing its memories
68+
69+
## Frontend
70+
71+
Single inline HTML/CSS/JS string (`DASHBOARD_HTML`). Dark theme, vanilla JS with `EventSource`.
72+
73+
4 panels:
74+
1. **Live Feed** - scrolling SSE events (attention wins, LLM responses, gate flushes, escalations)
75+
2. **Agent Status** - refreshes every 5s (phase, model, memory count, bootstrap, gut, energy cost)
76+
3. **Context Window** - refreshes every 3s (current conversation messages)
77+
4. **Memory Store** - paginated table with click-to-expand modal (refreshes every 10s)
78+
79+
All dynamic content uses `textContent` and DOM construction (no `innerHTML` with API data) to prevent XSS.
80+
81+
## Modules
82+
83+
### `src/dashboard.py` (~900 lines)
84+
85+
- `AgentState` dataclass with SSE broadcast
86+
- Route handlers for all API endpoints
87+
- `run_dashboard()` coroutine
88+
- Inline HTML/CSS/JS
89+
90+
### `src/loop.py` (modified)
91+
92+
- Signature: `cognitive_loop(..., agent_state=None)`
93+
- Assigns objects to `agent_state` after creation
94+
- Uses shared conversation: `agent_state.conversation if agent_state else []`
95+
- Publishes 4 SSE events at key points
96+
- All agent_state operations guarded by `if agent_state:` (no-op without dashboard)
97+
98+
### `src/main.py` (modified)
99+
100+
- Creates `AgentState(config=config, layers=layers, memory=memory)`
101+
- Passes `agent_state` to `cognitive_loop()`
102+
- Adds `run_dashboard(agent_state, shutdown_event)` to tasks
103+
104+
## Security
105+
106+
- Binds to 0.0.0.0 but only accessible via Tailscale (not exposed to public internet)
107+
- Read-only: no mutation endpoints, no write operations
108+
- No authentication (Tailscale provides network-level auth)
109+
- XSS prevented: all dynamic content via textContent/DOM construction
110+
111+
## Port
112+
113+
| Service | Port |
114+
|---------|------|
115+
| Dashboard | 8080 (mapped in docker-compose.yml) |
116+
117+
## Resilience
118+
119+
- `run_dashboard()` catches exceptions internally
120+
- Dashboard crash logs error and exits without calling `shutdown_event.set()`
121+
- Agent continues operating via Telegram/stdin without dashboard
122+
- `agent_state=None` default means loop works identically without dashboard

KB/KB_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
| 02 | KB/KB_02_memory.md | Memory system (unified store, Beta weights, retrieval) | 2026-02-12 |
1212
| 03 | KB/KB_03_cognitive_loop.md | Cognitive loop, attention, dual-process escalation | 2026-02-12 |
1313
| 04 | KB/KB_04_peripherals.md | Peripheral architecture, Telegram, stdin, reply_fn | 2026-02-13 |
14+
| 05 | KB/KB_05_dashboard.md | Dashboard (aiohttp, SSE, memory browser, AgentState) | 2026-02-14 |

docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ services:
3232
environment:
3333
- DATABASE_URL=postgresql://agent:${AGENT_DB_PASSWORD:-agent_secret}@postgres:5432/agent_memory
3434

35+
# Dashboard port (accessible via Tailscale)
36+
ports:
37+
- "8080:8080"
38+
3539
# NO PRIVILEGED ACCESS
3640
security_opt:
3741
- no-new-privileges:true
3842
read_only: false
3943
tmpfs:
4044
- /tmp:size=100M
4145

42-
# Interactive mode (stdin for conversation)
43-
stdin_open: true
44-
tty: true
45-
4646
# POSTGRES + PGVECTOR — agent's memory store
4747
postgres:
4848
image: pgvector/pgvector:pg17

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ pgvector>=0.3
1515
# Retrieval
1616
flashrank>=0.2
1717

18+
# Dashboard
19+
aiohttp>=3.9
20+
1821
# Utilities
1922
numpy>=1.26
2023
python-dateutil>=2.8

0 commit comments

Comments
 (0)