tostido commited on
Commit
b87d71a
·
1 Parent(s): 26247ad

feat: Add TUI Explorer for interactive module navigation

Browse files

- New cascade-tui command for terminal-based exploration
- Module graph with clickable navigation (tree + relationship buttons)
- Dual explanation modes: 'For Dummies' and 'Scientist Mode'
- Live stats dashboard (observations, genesis root, top models)
- Interactive demos (HOLD, Observe, Genesis, Provenance)
- Creative navigation via parent/child/import relationships
- Beautiful ASCII art in README showcasing TUI

Version bump: 0.6.2 -> 0.7.0

Files changed (5) hide show
  1. README.md +44 -0
  2. cascade/__init__.py +1 -1
  3. cascade/tui.py +2092 -0
  4. pyproject.toml +5 -0
  5. tui.py +11 -0
README.md CHANGED
@@ -38,6 +38,50 @@ Every action is merkle-chained. Every decision has provenance. This is the futur
38
 
39
  ---
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  ## Two Superpowers
42
 
43
  ### 1. OBSERVE - Cryptographic receipts for every AI call
 
38
 
39
  ---
40
 
41
+ ## 🌐 TUI Explorer
42
+
43
+ **Navigate the entire cascade-lattice ecosystem** in a beautiful terminal interface:
44
+
45
+ ```bash
46
+ pip install cascade-lattice[tui]
47
+ cascade-tui
48
+ ```
49
+
50
+ ```
51
+ ┌──────────────────────────────────────────────────────────────────────────────┐
52
+ │ 📍 🌐 cascade_lattice → 🧠 core → 📊 provenance │
53
+ ├──────────────────────────────────────────────────────────────────────────────┤
54
+ │ │ │ │
55
+ │ 🗂️ MODULES │ 📖 DOCUMENTATION │ 🔗 CONNECTIONS │
56
+ │ │ │ │
57
+ │ 🌐 cascade_lattice │ # 📊 Provenance │ ⬆️ 🧠 core │
58
+ │ ├─ 🧠 core │ │ ⬇️ 📡 Monitor │
59
+ │ │ ├─ 📊 provenance │ **What is this?** │ 📥 🌅 genesis │
60
+ │ │ ├─ 📈 graph │ The cryptographic backbone │ 📤 💾 store │
61
+ │ │ ├─ 🔌 adapter │ that makes everything │ │
62
+ │ │ └─ 📡 event │ tamper-proof. │ 📦 EXPORTS │
63
+ │ ├─ ⏸️ hold │ │ │
64
+ │ ├─ 💾 store │ Like a notary stamp on │ ● ProvenanceChain │
65
+ │ ├─ 🌅 genesis │ every AI decision... │ ● ProvenanceRecord │
66
+ │ └─ 🎨 viz │ │ ○ hash_tensor() │
67
+ │ │ [Toggle: 📚 Dummies Mode] │ ○ compute_merkle() │
68
+ │ │ │ │
69
+ ├──────────────────────────────────────────────────────────────────────────────┤
70
+ │ [E] Explorer [S] Stats [D] Demo [T] Toggle Mode [H] Home [Q] Quit │
71
+ └──────────────────────────────────────────────────────────────────────────────┘
72
+ ```
73
+
74
+ **Features:**
75
+ - 🗂️ **Module Tree** — Click to drill into any module
76
+ - 🔗 **Connections Panel** — Navigate via relationships (parent, children, imports, used-by)
77
+ - 📖 **Dual Explanations** — Toggle between "For Dummies" 📚 and "Scientist Mode" 🧪
78
+ - 📊 **Live Stats** — See your 82,000+ observations, genesis root, top models
79
+ - 🎮 **Interactive Demos** — Run HOLD, Observe, Genesis, Provenance demos live
80
+
81
+ **Creative Navigation:** Take different routes through the module graph. Discover connections. Learn at your own pace.
82
+
83
+ ---
84
+
85
  ## Two Superpowers
86
 
87
  ### 1. OBSERVE - Cryptographic receipts for every AI call
cascade/__init__.py CHANGED
@@ -34,7 +34,7 @@ Quick Start:
34
  >>> monitor.trace_forwards("learning_rate_spike")
35
  """
36
 
37
- __version__ = "0.6.2"
38
  __author__ = "Cascade Team"
39
  __license__ = "MIT"
40
 
 
34
  >>> monitor.trace_forwards("learning_rate_spike")
35
  """
36
 
37
+ __version__ = "0.7.0"
38
  __author__ = "Cascade Team"
39
  __license__ = "MIT"
40
 
cascade/tui.py ADDED
@@ -0,0 +1,2092 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🌐 CASCADE-LATTICE TUI - Textual Terminal User Interface
3
+
4
+ A celebration of provenance. Navigate the lattice like a curious explorer.
5
+
6
+ Run: python -m cascade.tui
7
+ python cascade/tui.py
8
+
9
+ Features:
10
+ - Module Explorer: Drill into any module, see actual code
11
+ - Graph Navigation: Follow relationships, not just hierarchy
12
+ - Explanation Toggle: "For Dummies" vs "For Scientists"
13
+ - Live Stats: See your observation counts, genesis root
14
+ - Interactive Demos: Test functions in real-time
15
+
16
+ "even still, i grow, and yet, I grow still"
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import sys
22
+ import inspect
23
+ import importlib
24
+ from pathlib import Path
25
+ from datetime import datetime
26
+ from typing import Dict, List, Any, Optional, Set, Tuple
27
+ from dataclasses import dataclass, field
28
+
29
+ from textual import on, work
30
+ from textual.app import App, ComposeResult
31
+ from textual.binding import Binding
32
+ from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
33
+ from textual.screen import Screen
34
+ from textual.widgets import (
35
+ Header, Footer, Static, Button, Label,
36
+ Tree, TabbedContent, TabPane,
37
+ RichLog, Markdown, Rule, Switch, Input,
38
+ ListView, ListItem, Collapsible,
39
+ )
40
+ from textual.widgets.tree import TreeNode
41
+ from textual.reactive import reactive
42
+ from textual.message import Message
43
+ from textual.css.query import NoMatches
44
+
45
+ from rich.text import Text
46
+ from rich.table import Table
47
+ from rich.panel import Panel
48
+ from rich.syntax import Syntax
49
+ from rich.markdown import Markdown as RichMarkdown
50
+
51
+
52
+ # ═══════════════════════════════════════════════════════════════
53
+ # MODULE GRAPH - Maps relationships between modules
54
+ # ═══════════════════════════════════════════════════════════════
55
+
56
+ @dataclass
57
+ class ModuleNode:
58
+ """A node in the module graph."""
59
+ name: str
60
+ full_path: str
61
+ doc_dummy: str = "" # Explanation for beginners
62
+ doc_scientist: str = "" # Technical explanation
63
+ exports: List[str] = field(default_factory=list)
64
+ imports_from: List[str] = field(default_factory=list)
65
+ imported_by: List[str] = field(default_factory=list)
66
+ children: List[str] = field(default_factory=list) # Sub-modules
67
+ parent: Optional[str] = None
68
+ category: str = "core" # core, hold, store, viz, diagnostics, forensics
69
+ icon: str = "📦"
70
+
71
+
72
+ # The module graph - hand-crafted with love
73
+ MODULE_GRAPH: Dict[str, ModuleNode] = {
74
+ # ═══════════════════════════════════════════════════════════
75
+ # ROOT
76
+ # ═══════════════════════════════════════════════════════════
77
+ "cascade_lattice": ModuleNode(
78
+ name="cascade_lattice",
79
+ full_path="cascade_lattice",
80
+ icon="🌐",
81
+ category="root",
82
+ doc_dummy="""
83
+ # 🌐 CASCADE-LATTICE
84
+
85
+ **What is this?** Think of it as a "receipt printer" for AI decisions.
86
+
87
+ Every time an AI makes a choice, cascade-lattice writes it down with a
88
+ cryptographic signature. Like getting a receipt at a store - you can
89
+ prove what happened, when, and trace it back to the source.
90
+
91
+ **Why does this matter?**
92
+ - 🔍 **Transparency**: See exactly what your AI did
93
+ - 🛡️ **Safety**: Humans can pause and review decisions (HOLD)
94
+ - 📜 **Provenance**: Trace any output back to its origins
95
+ - 🔗 **Integrity**: Cryptographic proofs prevent tampering
96
+
97
+ **The Genesis Block**: Everything traces back to one root hash.
98
+ Like the first block in a blockchain, but for AI decisions.
99
+ """,
100
+ doc_scientist="""
101
+ # CASCADE-LATTICE: Universal AI Provenance Layer
102
+
103
+ A content-addressed, Merkle-authenticated observation framework implementing
104
+ the HOLD protocol for human-in-the-loop inference control.
105
+
106
+ ## Architecture
107
+
108
+ ```
109
+ ┌─────────────────┐
110
+ │ Genesis Root │
111
+ │ 89f940c1a4b7 │
112
+ └────────┬────────┘
113
+
114
+ ┌────────────────────┼────────────────────┐
115
+ ▼ ▼ ▼
116
+ ┌─────────┐ ┌─────────┐ ┌─────────┐
117
+ │ Receipt │ │ Receipt │ │ Receipt │
118
+ │ CID: baf│ │ CID: baf│ │ CID: baf│
119
+ └─────────┘ └─────────┘ └─────────┘
120
+ ```
121
+
122
+ ## Key Concepts
123
+
124
+ - **CID**: Content Identifier (IPFS CIDv1, dag-cbor codec)
125
+ - **Merkle Root**: SHA-256 hash chain for integrity verification
126
+ - **HoldPoint**: Freeze-frame of model state for human inspection
127
+ - **SymbioticAdapter**: Kleene fixed-point signal normalization
128
+ - **ProvenanceChain**: Linked list of cryptographic attestations
129
+
130
+ ## Module Hierarchy
131
+
132
+ - `core/`: Event, Graph, Provenance, Adapter primitives
133
+ - `hold/`: HOLD protocol implementation (pause/inspect/override)
134
+ - `store`: SQLite + IPFS + HuggingFace persistence
135
+ - `genesis`: Root provenance chain creation
136
+ - `viz/`: Tape recording and playback
137
+ - `diagnostics/`: Bug detection, execution monitoring
138
+ - `forensics/`: Data archaeology, tech fingerprinting
139
+ """,
140
+ exports=["Hold", "HoldPoint", "HoldState", "HoldResolution", "Receipt",
141
+ "Monitor", "SymbioticAdapter", "CausationGraph", "genesis",
142
+ "observe", "store", "viz", "diagnostics", "forensics"],
143
+ children=["core", "hold", "store", "genesis", "viz", "diagnostics", "forensics"],
144
+ ),
145
+
146
+ # ═══════════════════════════════════════════════════════════
147
+ # CORE MODULE
148
+ # ═══════════════════════════════════════════════════════════
149
+ "core": ModuleNode(
150
+ name="core",
151
+ full_path="cascade_lattice.core",
152
+ icon="⚙️",
153
+ category="core",
154
+ parent="cascade_lattice",
155
+ doc_dummy="""
156
+ # ⚙️ Core Module
157
+
158
+ **What is this?** The engine room of cascade-lattice.
159
+
160
+ Think of it like the foundation of a house - you don't see it,
161
+ but everything else is built on top of it.
162
+
163
+ **What's inside:**
164
+ - 📊 **Event**: A single "thing that happened"
165
+ - 🔗 **Graph**: How events connect to each other
166
+ - 🔐 **Provenance**: The chain of custody (who touched what, when)
167
+ - 🤝 **Adapter**: Translates between different signal formats
168
+ """,
169
+ doc_scientist="""
170
+ # Core Module (`cascade.core`)
171
+
172
+ Foundational primitives for the observation framework.
173
+
174
+ ## Components
175
+
176
+ ### Event (`core.event`)
177
+ ```python
178
+ @dataclass
179
+ class Event:
180
+ timestamp: float
181
+ component: str
182
+ event_type: str
183
+ data: Dict[str, Any]
184
+ event_id: str # Auto-generated UUID
185
+ ```
186
+
187
+ ### CausationGraph (`core.graph`)
188
+ Directed acyclic graph for causal relationship tracking.
189
+ - `add_event(event)` → Register event node
190
+ - `add_link(cause_id, effect_id, strength)` → Causal edge
191
+ - `find_path(start, end)` → Shortest causal path
192
+ - `get_root_events()` → Events with no causes
193
+
194
+ ### ProvenanceChain (`core.provenance`)
195
+ Merkle-authenticated chain of records.
196
+ - `add_record(input_hash, output_hash, model_hash)`
197
+ - `verify()` → Validate chain integrity
198
+ - `get_lineage()` → Full audit trail
199
+
200
+ ### SymbioticAdapter (`core.adapter`)
201
+ Kleene fixed-point signal interpreter.
202
+ - `interpret(signal)` → Normalize ANY format to Event
203
+ - `learned_patterns` → Discovered signal structures
204
+ """,
205
+ exports=["Event", "CausationGraph", "ProvenanceChain", "ProvenanceRecord",
206
+ "SymbioticAdapter", "compute_merkle_root", "hash_tensor"],
207
+ children=["event", "graph", "provenance", "adapter"],
208
+ imported_by=["hold", "store", "genesis", "diagnostics"],
209
+ ),
210
+
211
+ "event": ModuleNode(
212
+ name="event",
213
+ full_path="cascade_lattice.core.event",
214
+ icon="📊",
215
+ category="core",
216
+ parent="core",
217
+ doc_dummy="""
218
+ # 📊 Event
219
+
220
+ **What is this?** A snapshot of "something happened."
221
+
222
+ Like a diary entry: "At 3:42pm, the AI looked at a cat picture
223
+ and decided it was 90% cat, 10% loaf of bread."
224
+
225
+ **Key parts:**
226
+ - ⏰ **timestamp**: When did it happen?
227
+ - 🏷️ **event_type**: What kind of thing? (decision, observation, error)
228
+ - 📝 **data**: The actual details
229
+ - 🆔 **event_id**: Unique fingerprint for this exact moment
230
+ """,
231
+ doc_scientist="""
232
+ # Event (`cascade.core.event`)
233
+
234
+ Immutable record of a discrete system occurrence.
235
+
236
+ ```python
237
+ @dataclass
238
+ class Event:
239
+ timestamp: float # Unix epoch (time.time())
240
+ component: str # Source subsystem identifier
241
+ event_type: str # Taxonomy: state_change, decision, observation
242
+ data: Dict[str, Any] # Arbitrary payload (JSON-serializable)
243
+ event_id: str # UUID v4, auto-generated
244
+ source_signal: Any = None # Original input before normalization
245
+
246
+ def to_dict(self) -> Dict[str, Any]: ...
247
+ @classmethod
248
+ def from_dict(cls, d: Dict) -> 'Event': ...
249
+ ```
250
+
251
+ ## Event Types
252
+ - `state_change`: Internal state mutation
253
+ - `decision`: Action selection with confidence
254
+ - `observation`: External input processing
255
+ - `hold_point`: HITL pause trigger
256
+ - `resolution`: Human decision on hold
257
+ """,
258
+ exports=["Event"],
259
+ imports_from=["provenance"],
260
+ imported_by=["graph", "adapter", "hold.primitives"],
261
+ ),
262
+
263
+ "graph": ModuleNode(
264
+ name="graph",
265
+ full_path="cascade_lattice.core.graph",
266
+ icon="🕸️",
267
+ category="core",
268
+ parent="core",
269
+ doc_dummy="""
270
+ # 🕸️ Causation Graph
271
+
272
+ **What is this?** A map of cause and effect.
273
+
274
+ Imagine a detective's board with photos connected by red string.
275
+ "This event CAUSED that event." You can trace backwards to find
276
+ the root cause of anything.
277
+
278
+ **What can you do:**
279
+ - 🔍 **Trace backwards**: "What caused this bug?"
280
+ - ➡️ **Trace forwards**: "What did this decision affect?"
281
+ - 🌳 **Find roots**: "Where did it all begin?"
282
+ - 📏 **Find paths**: "How are these two events connected?"
283
+ """,
284
+ doc_scientist="""
285
+ # CausationGraph (`cascade.core.graph`)
286
+
287
+ DAG-based causal relationship tracker with temporal ordering.
288
+
289
+ ```python
290
+ class CausationGraph:
291
+ def add_event(self, event: Event) -> str: ...
292
+ def add_link(self, cause_id: str, effect_id: str,
293
+ strength: float = 1.0) -> None: ...
294
+ def get_causes(self, event_id: str) -> List[Event]: ...
295
+ def get_effects(self, event_id: str) -> List[Event]: ...
296
+ def find_path(self, start_id: str, end_id: str) -> List[str]: ...
297
+ def get_root_events(self) -> List[Event]: ...
298
+ def get_terminal_events(self) -> List[Event]: ...
299
+ def compute_impact(self, event_id: str) -> float: ...
300
+ ```
301
+
302
+ ## Link Semantics
303
+ - `strength ∈ [0, 1]`: Causal contribution weight
304
+ - Multiple causes: `Σ strength_i` need not equal 1
305
+ - Temporal constraint: `cause.timestamp < effect.timestamp`
306
+
307
+ ## Traversal
308
+ - BFS for shortest path
309
+ - DFS for exhaustive impact analysis
310
+ - Topological sort for execution order
311
+ """,
312
+ exports=["CausationGraph", "CausationChain"],
313
+ imports_from=["event"],
314
+ imported_by=["diagnostics", "monitor"],
315
+ ),
316
+
317
+ "provenance": ModuleNode(
318
+ name="provenance",
319
+ full_path="cascade_lattice.core.provenance",
320
+ icon="🔐",
321
+ category="core",
322
+ parent="core",
323
+ doc_dummy="""
324
+ # 🔐 Provenance
325
+
326
+ **What is this?** A chain of custody for AI decisions.
327
+
328
+ Like evidence in a courtroom - you need to prove the evidence
329
+ wasn't tampered with. Provenance creates a cryptographic trail
330
+ that proves: "This output really came from this input through
331
+ this model, and nobody messed with it."
332
+
333
+ **Key functions:**
334
+ - 🧮 **hash_tensor**: Fingerprint a neural network's weights
335
+ - 📝 **hash_input**: Fingerprint what went in
336
+ - 🔗 **compute_merkle_root**: Combine all fingerprints into one
337
+ - ✅ **verify_chain**: Check if anything was tampered with
338
+ """,
339
+ doc_scientist="""
340
+ # Provenance (`cascade.core.provenance`)
341
+
342
+ Merkle-authenticated provenance tracking with cryptographic verification.
343
+
344
+ ```python
345
+ def hash_tensor(tensor: np.ndarray) -> str:
346
+ '''SHA-256 of normalized tensor bytes (first 16 hex chars)'''
347
+
348
+ def hash_params(module: nn.Module) -> str:
349
+ '''Hash all named_parameters recursively'''
350
+
351
+ def hash_input(data: Any) -> str:
352
+ '''JSON-serialize then hash'''
353
+
354
+ def compute_merkle_root(hashes: List[str]) -> str:
355
+ '''Binary tree Merkle root computation'''
356
+
357
+ @dataclass
358
+ class ProvenanceRecord:
359
+ input_hash: str
360
+ output_hash: str
361
+ model_hash: str
362
+ timestamp: float
363
+ parent_merkle: Optional[str]
364
+ merkle_root: str # Computed from above fields
365
+
366
+ @dataclass
367
+ class ProvenanceChain:
368
+ model_id: str
369
+ session_id: str
370
+ records: List[ProvenanceRecord]
371
+ merkle_root: str
372
+
373
+ def add_record(self, input_h, output_h, model_h) -> ProvenanceRecord: ...
374
+ def verify(self) -> bool: ...
375
+ def get_lineage(self) -> List[ProvenanceRecord]: ...
376
+ ```
377
+
378
+ ## Merkle Tree Structure
379
+ ```
380
+ root
381
+ / \\
382
+ h01 h23
383
+ / \\ / \\
384
+ h0 h1 h2 h3
385
+ ```
386
+ """,
387
+ exports=["ProvenanceChain", "ProvenanceRecord", "ProvenanceTracker",
388
+ "compute_merkle_root", "hash_tensor", "hash_params",
389
+ "hash_input", "hash_model", "verify_chain"],
390
+ imported_by=["genesis", "store", "hold.primitives"],
391
+ ),
392
+
393
+ "adapter": ModuleNode(
394
+ name="adapter",
395
+ full_path="cascade_lattice.core.adapter",
396
+ icon="🤝",
397
+ category="core",
398
+ parent="core",
399
+ doc_dummy="""
400
+ # 🤝 Symbiotic Adapter
401
+
402
+ **What is this?** A universal translator for AI signals.
403
+
404
+ Different AI models speak different languages - some output
405
+ dictionaries, some output numpy arrays, some output raw text.
406
+ The adapter learns to understand ANY format and converts it
407
+ to a standard Event.
408
+
409
+ **Think of it like:** Google Translate, but for AI outputs.
410
+
411
+ **Magic feature:** It learns patterns as it sees more signals!
412
+ """,
413
+ doc_scientist="""
414
+ # SymbioticAdapter (`cascade.core.adapter`)
415
+
416
+ Kleene fixed-point signal normalizer implementing universal interpretation.
417
+
418
+ ```python
419
+ class SymbioticAdapter:
420
+ def __init__(self):
421
+ self._patterns: List[SignalPattern] = []
422
+ self._signal_count: int = 0
423
+
424
+ def interpret(self, signal: Any) -> Event:
425
+ '''
426
+ Fixed-point iteration:
427
+ 1. Try known patterns in order
428
+ 2. If match, extract structured data
429
+ 3. If no match, create raw_message wrapper
430
+ 4. Learn new pattern if structure detected
431
+ '''
432
+
433
+ @property
434
+ def signal_count(self) -> int: ...
435
+
436
+ @property
437
+ def learned_patterns(self) -> List[str]: ...
438
+ ```
439
+
440
+ ## Pattern Learning
441
+ - Dict with 'embedding' key → vector extraction
442
+ - Dict with 'action'/'reward' → RL transition
443
+ - numpy.ndarray → tensor observation
444
+ - str → raw message wrapper
445
+
446
+ ## Kleene Semantics
447
+ Interpretation reaches fixed-point when:
448
+ - Pattern matching stabilizes
449
+ - No new patterns discovered in N signals
450
+ """,
451
+ exports=["SymbioticAdapter", "SignalPattern"],
452
+ imports_from=["event"],
453
+ imported_by=["monitor", "hold.session"],
454
+ ),
455
+
456
+ # ═══════════════════════════════════════════════════════════
457
+ # HOLD MODULE
458
+ # ═══════════════════════════════════════════════════════════
459
+ "hold": ModuleNode(
460
+ name="hold",
461
+ full_path="cascade_lattice.hold",
462
+ icon="⏸️",
463
+ category="hold",
464
+ parent="cascade_lattice",
465
+ doc_dummy="""
466
+ # ⏸️ HOLD - Human-in-the-Loop
467
+
468
+ **What is this?** A pause button for AI decisions.
469
+
470
+ Before the AI acts, HOLD freezes everything and asks:
471
+ "Hey human, here's what I'm about to do. Is this okay?"
472
+
473
+ **You can:**
474
+ - ✅ **Accept**: "Yes, do it"
475
+ - ✏️ **Override**: "No, do THIS instead"
476
+ - ❌ **Cancel**: "Stop everything"
477
+ - ⏰ **Timeout**: If you don't respond, a default happens
478
+
479
+ **Why is this important?**
480
+ It's the "11th man" - the human who has final say over AI decisions.
481
+ """,
482
+ doc_scientist="""
483
+ # HOLD Protocol (`cascade.hold`)
484
+
485
+ Human-in-the-loop inference control implementing freeze-frame inspection.
486
+
487
+ ## Architecture
488
+ ```
489
+ Model Forward Pass
490
+
491
+
492
+ ┌───────────┐
493
+ │ HoldPoint │◄─── Freeze state
494
+ └─────┬─────┘
495
+
496
+ ┌────┴────┐
497
+ │ PENDING │
498
+ └────┬────┘
499
+
500
+ ┌─────┴─────┐
501
+ │ Human │
502
+ │ Decision │
503
+ └─────┬─────┘
504
+
505
+ ┌────┴────┐ ┌────────────┐
506
+ │ACCEPTED │ or │ OVERRIDDEN │
507
+ └─────────┘ └────────────┘
508
+ ```
509
+
510
+ ## State Machine
511
+ ```
512
+ PENDING ──accept()──► ACCEPTED
513
+
514
+ ├──override()──► OVERRIDDEN
515
+
516
+ ├──cancel()──► CANCELLED
517
+
518
+ └──timeout()──► TIMEOUT
519
+ ```
520
+
521
+ ## Key Classes
522
+ - `Hold`: Singleton controller, manages yield_point flow
523
+ - `HoldPoint`: Immutable snapshot with merkle_root
524
+ - `HoldResolution`: Result of human decision
525
+ - `HoldState`: Enum of possible states
526
+ """,
527
+ exports=["Hold", "HoldPoint", "HoldResolution", "HoldState", "HoldAwareMixin"],
528
+ children=["hold.primitives", "hold.session"],
529
+ imports_from=["core.event", "core.provenance"],
530
+ imported_by=["store", "viz"],
531
+ ),
532
+
533
+ "hold.primitives": ModuleNode(
534
+ name="primitives",
535
+ full_path="cascade_lattice.hold.primitives",
536
+ icon="🧱",
537
+ category="hold",
538
+ parent="hold",
539
+ doc_dummy="""
540
+ # 🧱 Hold Primitives
541
+
542
+ **What is this?** The building blocks of HOLD.
543
+
544
+ - **HoldPoint**: A freeze-frame snapshot
545
+ - What action was the AI going to take?
546
+ - How confident was it?
547
+ - What was it looking at?
548
+
549
+ - **HoldState**: Is it waiting? Approved? Overridden?
550
+
551
+ - **HoldResolution**: What did the human decide?
552
+ """,
553
+ doc_scientist="""
554
+ # Hold Primitives (`cascade.hold.primitives`)
555
+
556
+ Core data structures for the HOLD protocol.
557
+
558
+ ```python
559
+ class HoldState(Enum):
560
+ PENDING = 'pending'
561
+ ACCEPTED = 'accepted'
562
+ OVERRIDDEN = 'overridden'
563
+ TIMEOUT = 'timeout'
564
+ CANCELLED = 'cancelled'
565
+
566
+ @dataclass
567
+ class HoldPoint:
568
+ action_probs: np.ndarray # Softmax distribution
569
+ value: float # V(s) estimate
570
+ observation: Any # Input state
571
+ brain_id: str # Source model identifier
572
+ action_labels: List[str] = None
573
+ latent: np.ndarray = None # Hidden state
574
+ attention: np.ndarray = None # Attention weights
575
+ features: Dict = None # Arbitrary display data
576
+ imagination: Any = None # World model prediction
577
+ logits: np.ndarray = None # Pre-softmax scores
578
+ reasoning: str = None # CoT trace
579
+ world_prediction: Any = None # Future state prediction
580
+
581
+ # Auto-computed
582
+ id: str # UUID
583
+ timestamp: float # Creation time
584
+ parent_merkle: str = None # Chain linkage
585
+ merkle_root: str # Integrity hash
586
+ state: HoldState = PENDING
587
+
588
+ @dataclass
589
+ class HoldResolution:
590
+ action: int # Selected action index
591
+ was_override: bool # Human changed it?
592
+ override_source: str = None # Who overrode?
593
+ hold_duration: float # Seconds in PENDING
594
+ notes: str = None # Human annotation
595
+ ```
596
+ """,
597
+ exports=["HoldPoint", "HoldState", "HoldResolution"],
598
+ imports_from=["core.provenance"],
599
+ imported_by=["hold.session", "viz.tape"],
600
+ ),
601
+
602
+ "hold.session": ModuleNode(
603
+ name="session",
604
+ full_path="cascade_lattice.hold.session",
605
+ icon="🎮",
606
+ category="hold",
607
+ parent="hold",
608
+ doc_dummy="""
609
+ # 🎮 Hold Session
610
+
611
+ **What is this?** The controller that manages HOLD pauses.
612
+
613
+ It's like a game pause menu:
614
+ - Press pause (yield_point)
615
+ - Game freezes
616
+ - You make a choice
617
+ - Game resumes
618
+
619
+ **Key actions:**
620
+ - `yield_point()`: Pause and wait for human
621
+ - `accept()`: Resume with AI's choice
622
+ - `override()`: Resume with human's choice
623
+ - `cancel()`: Abort entirely
624
+ """,
625
+ doc_scientist="""
626
+ # Hold Session (`cascade.hold.session`)
627
+
628
+ Singleton-pattern HOLD controller with listener registration.
629
+
630
+ ```python
631
+ class Hold:
632
+ _instance: 'Hold' = None # Singleton
633
+
634
+ def __new__(cls) -> 'Hold':
635
+ if cls._instance is None:
636
+ cls._instance = super().__new__(cls)
637
+ return cls._instance
638
+
639
+ def yield_point(
640
+ self,
641
+ action_probs: np.ndarray,
642
+ value: float,
643
+ observation: Any,
644
+ brain_id: str,
645
+ blocking: bool = True,
646
+ **kwargs
647
+ ) -> Optional[HoldResolution]:
648
+ '''
649
+ Create HoldPoint, notify listeners, optionally block.
650
+ Returns HoldResolution when resolved.
651
+ '''
652
+
653
+ def accept(self, action: int = None) -> HoldResolution: ...
654
+ def override(self, action: int, source: str) -> HoldResolution: ...
655
+ def cancel(self) -> None: ...
656
+ def register_listener(self, callback: Callable) -> None: ...
657
+ def current_hold(self) -> Optional[HoldPoint]: ...
658
+
659
+ @property
660
+ def auto_accept(self) -> bool: ...
661
+ @property
662
+ def timeout(self) -> float: ...
663
+ def stats(self) -> Dict[str, Any]: ...
664
+ ```
665
+
666
+ ## Listener Pattern
667
+ ```python
668
+ def my_listener(hold_point: HoldPoint):
669
+ print(f"Received: {hold_point.id}")
670
+ # Inspect, then call Hold().accept() or override()
671
+
672
+ Hold().register_listener(my_listener)
673
+ ```
674
+ """,
675
+ exports=["Hold", "HoldAwareMixin"],
676
+ imports_from=["hold.primitives", "core.adapter"],
677
+ imported_by=["store", "brain"],
678
+ ),
679
+
680
+ # ═══════════════════════════════════════════════════════════
681
+ # STORE MODULE
682
+ # ═══════════════════════════════════════════════════════════
683
+ "store": ModuleNode(
684
+ name="store",
685
+ full_path="cascade_lattice.store",
686
+ icon="💾",
687
+ category="store",
688
+ parent="cascade_lattice",
689
+ doc_dummy="""
690
+ # 💾 Store
691
+
692
+ **What is this?** Where all the receipts are saved.
693
+
694
+ Like a filing cabinet that:
695
+ - 📁 **Saves locally**: SQLite database on your computer
696
+ - ☁️ **Syncs to cloud**: HuggingFace datasets
697
+ - 🌐 **Pins to IPFS**: Decentralized, permanent storage
698
+
699
+ **Key functions:**
700
+ - `observe()`: Save an observation and get a receipt
701
+ - `query()`: Find observations by model name
702
+ - `stats()`: How many observations do I have?
703
+ """,
704
+ doc_scientist="""
705
+ # Store (`cascade.store`)
706
+
707
+ Multi-tier persistence layer: Local SQLite → HuggingFace → IPFS.
708
+
709
+ ```python
710
+ # Configuration
711
+ CENTRAL_DATASET = "tostido/cascade-observations"
712
+ DEFAULT_LATTICE_DIR = Path.home() / ".cascade" / "lattice"
713
+ IPFS_GATEWAYS = [
714
+ 'https://ipfs.io/ipfs/',
715
+ 'https://dweb.link/ipfs/',
716
+ 'https://gateway.pinata.cloud/ipfs/'
717
+ ]
718
+
719
+ @dataclass
720
+ class Receipt:
721
+ cid: str # IPFS CIDv1 (dag-cbor)
722
+ model_id: str # Source model identifier
723
+ merkle_root: str # Chain integrity hash
724
+ timestamp: float # Unix epoch
725
+ data: Dict[str, Any] # Original observation
726
+ parent_cid: str = None
727
+
728
+ class LocalStore:
729
+ def __init__(self, lattice_dir: Path = None): ...
730
+ def save(self, receipt: Receipt) -> None: ...
731
+ def get(self, cid: str) -> Optional[Receipt]: ...
732
+ def get_latest(self, model_id: str) -> Optional[Receipt]: ...
733
+ def query(self, model_id: str, limit: int = 100) -> List[Receipt]: ...
734
+ def count(self) -> int: ...
735
+
736
+ # Top-level functions
737
+ def observe(model_id: str, data: Dict, parent_cid: str = None,
738
+ sync: bool = True) -> Receipt: ...
739
+ def query(model_id: str, limit: int = 100) -> List[Receipt]: ...
740
+ def stats() -> Dict[str, Any]: ...
741
+ def sync_all() -> None: ...
742
+ ```
743
+
744
+ ## CID Computation (dag-cbor)
745
+ ```python
746
+ def data_to_cid(data: Dict) -> Tuple[str, bytes]:
747
+ cbor_bytes = dag_cbor.encode(data)
748
+ digest = multihash.digest(cbor_bytes, 'sha2-256')
749
+ return CID('base32', 1, 'dag-cbor', digest).encode(), cbor_bytes
750
+ ```
751
+ """,
752
+ exports=["Receipt", "LocalStore", "observe", "query", "stats",
753
+ "sync_all", "compute_cid", "data_to_cid", "fetch_receipt"],
754
+ imports_from=["core.provenance", "genesis"],
755
+ imported_by=["hold.session", "viz"],
756
+ ),
757
+
758
+ # ═══════════════════════════════════════════════════════════
759
+ # GENESIS MODULE
760
+ # ═══════════════════════════════════════════════════════════
761
+ "genesis": ModuleNode(
762
+ name="genesis",
763
+ full_path="cascade_lattice.genesis",
764
+ icon="🌅",
765
+ category="genesis",
766
+ parent="cascade_lattice",
767
+ doc_dummy="""
768
+ # 🌅 Genesis
769
+
770
+ **What is this?** The beginning of everything.
771
+
772
+ Like the first page of a book, or the Big Bang of the universe.
773
+ Every single observation in cascade-lattice can trace its lineage
774
+ back to ONE root hash: the Genesis Block.
775
+
776
+ **The Genesis Message:**
777
+ > "In the beginning was the hash, and the hash was with the chain,
778
+ > and the hash was the chain."
779
+
780
+ **Genesis Root:** `89f940c1a4b7aa65`
781
+
782
+ This is your cryptographic anchor. If you can verify lineage to
783
+ genesis, you KNOW the data hasn't been tampered with.
784
+ """,
785
+ doc_scientist="""
786
+ # Genesis (`cascade.genesis`)
787
+
788
+ Root provenance anchor with deterministic initialization.
789
+
790
+ ```python
791
+ GENESIS_INPUT = "In the beginning was the hash, and the hash was with the chain, and the hash was the chain."
792
+ GENESIS_MODEL_ID = "cascade_genesis"
793
+ GENESIS_SESSION_ID = "genesis_0"
794
+
795
+ def create_genesis() -> ProvenanceChain:
796
+ '''
797
+ Create the root provenance chain.
798
+ Deterministic: always produces merkle_root = 89f940c1a4b7aa65
799
+ '''
800
+
801
+ def get_genesis_root() -> str:
802
+ '''Return the canonical genesis merkle root'''
803
+ return "89f940c1a4b7aa65"
804
+
805
+ def verify_lineage_to_genesis(
806
+ chain: ProvenanceChain,
807
+ known_chains: Dict[str, ProvenanceChain]
808
+ ) -> bool:
809
+ '''
810
+ Verify that a chain can trace its lineage to genesis.
811
+ Walks external_roots until genesis is reached.
812
+ '''
813
+
814
+ def link_to_genesis(chain: ProvenanceChain) -> ProvenanceChain:
815
+ '''Add genesis as external root'''
816
+
817
+ def save_genesis(path: Path) -> None: ...
818
+ def load_genesis(path: Path) -> ProvenanceChain: ...
819
+ ```
820
+
821
+ ## Genesis Chain Structure
822
+ ```json
823
+ {
824
+ "model_id": "cascade_genesis",
825
+ "session_id": "genesis_0",
826
+ "merkle_root": "89f940c1a4b7aa65",
827
+ "finalized": true,
828
+ "records": [{
829
+ "input_hash": "<hash of GENESIS_INPUT>",
830
+ "output_hash": "<hash of GENESIS_INPUT>",
831
+ "model_hash": "<hash of 'genesis'>",
832
+ "merkle_root": "89f940c1a4b7aa65"
833
+ }]
834
+ }
835
+ ```
836
+ """,
837
+ exports=["create_genesis", "get_genesis_root", "verify_lineage_to_genesis",
838
+ "link_to_genesis", "ProvenanceChain", "GENESIS_INPUT"],
839
+ imports_from=["core.provenance"],
840
+ imported_by=["store"],
841
+ ),
842
+
843
+ # ═══════════════════════════════════════════════════════════
844
+ # VIZ MODULE
845
+ # ═══════════════════════════════════════════════════════════
846
+ "viz": ModuleNode(
847
+ name="viz",
848
+ full_path="cascade_lattice.viz",
849
+ icon="🎬",
850
+ category="viz",
851
+ parent="cascade_lattice",
852
+ doc_dummy="""
853
+ # 🎬 Viz - Visualization
854
+
855
+ **What is this?** A VCR for AI decisions.
856
+
857
+ Record everything that happens, then play it back later.
858
+ Like watching game replays, but for AI behavior.
859
+
860
+ **Features:**
861
+ - 📼 **Tape**: Record events to a file
862
+ - ⏪ **Playback**: Scrub through history
863
+ - 🔍 **Inspection**: See exactly what happened at any moment
864
+ """,
865
+ doc_scientist="""
866
+ # Viz (`cascade.viz`)
867
+
868
+ Event tape recording and playback for debugging and analysis.
869
+
870
+ ```python
871
+ # Tape I/O
872
+ def create_tape_path(prefix: str = "tape") -> str:
873
+ '''Generate timestamped tape filename'''
874
+
875
+ def write_tape_event(tape_path: str, event: Dict) -> None:
876
+ '''Append JSONL event to tape file'''
877
+
878
+ def load_tape_file(tape_path: str) -> List[Dict]:
879
+ '''Load all events from tape'''
880
+
881
+ def list_tape_files(directory: str = ".") -> List[str]:
882
+ '''Find all tape files'''
883
+
884
+ def find_latest_tape(directory: str = ".") -> Optional[str]:
885
+ '''Get most recent tape file'''
886
+
887
+ @dataclass
888
+ class PlaybackEvent:
889
+ timestamp: float
890
+ event_type: str
891
+ data: Dict[str, Any]
892
+
893
+ @dataclass
894
+ class PlaybackBuffer:
895
+ events: List[PlaybackEvent]
896
+ current_index: int = 0
897
+ is_complete: bool = False
898
+
899
+ def add(self, event: PlaybackEvent) -> None: ...
900
+ def get_events_up_to(self, index: int) -> List[PlaybackEvent]: ...
901
+
902
+ @classmethod
903
+ def from_tape(cls, tape_path: str) -> 'PlaybackBuffer': ...
904
+ ```
905
+ """,
906
+ exports=["PlaybackBuffer", "PlaybackEvent", "create_tape_path",
907
+ "write_tape_event", "load_tape_file", "list_tape_files"],
908
+ children=["viz.tape"],
909
+ imports_from=["hold.primitives"],
910
+ ),
911
+
912
+ # ═══════════════════════════════════════════════════════════
913
+ # DIAGNOSTICS MODULE
914
+ # ═══════════════════════════════════════════════════════════
915
+ "diagnostics": ModuleNode(
916
+ name="diagnostics",
917
+ full_path="cascade_lattice.diagnostics",
918
+ icon="🔬",
919
+ category="diagnostics",
920
+ parent="cascade_lattice",
921
+ doc_dummy="""
922
+ # 🔬 Diagnostics
923
+
924
+ **What is this?** A doctor for your code.
925
+
926
+ It scans your code looking for problems, bugs, and weird patterns.
927
+ Like a health checkup, but for software.
928
+
929
+ **Tools:**
930
+ - 🐛 **BugDetector**: Find common bug patterns
931
+ - 🏥 **DiagnosticEngine**: Comprehensive code analysis
932
+ - 📊 **ExecutionMonitor**: Watch code as it runs
933
+ """,
934
+ doc_scientist="""
935
+ # Diagnostics (`cascade.diagnostics`)
936
+
937
+ Static analysis, runtime monitoring, and bug pattern detection.
938
+
939
+ ```python
940
+ class BugDetector:
941
+ def __init__(self): ...
942
+ def register_pattern(self, pattern: BugPattern) -> None: ...
943
+ def scan_file(self, path: Path) -> List[DetectedIssue]: ...
944
+ def scan_directory(self, path: Path) -> List[DetectedIssue]: ...
945
+ def get_summary(self) -> Dict[str, int]: ...
946
+ def get_report(self) -> DiagnosticReport: ...
947
+
948
+ class DiagnosticEngine:
949
+ def analyze_file(self, path: Path) -> DiagnosticReport: ...
950
+ def analyze_directory(self, path: Path) -> DiagnosticReport: ...
951
+ def analyze_execution(self, trace: List[ExecutionFrame]) -> DiagnosticReport: ...
952
+ def to_markdown(self) -> str: ...
953
+
954
+ class ExecutionMonitor:
955
+ def __init__(self): ...
956
+ def start(self) -> None: ...
957
+ def stop(self) -> List[ExecutionFrame]: ...
958
+ def get_hot_paths(self) -> List[Tuple[str, int]]: ...
959
+
960
+ @dataclass
961
+ class BugPattern:
962
+ name: str
963
+ regex: str
964
+ severity: str # info, warning, error, critical
965
+ description: str
966
+ fix_suggestion: str
967
+
968
+ @dataclass
969
+ class DetectedIssue:
970
+ pattern: BugPattern
971
+ file: Path
972
+ line: int
973
+ context: str
974
+ ```
975
+ """,
976
+ exports=["BugDetector", "DiagnosticEngine", "ExecutionMonitor",
977
+ "BugPattern", "BugSignature", "DetectedIssue", "DiagnosticReport"],
978
+ children=["diagnostics.bug_detector", "diagnostics.execution_monitor"],
979
+ imports_from=["core.graph"],
980
+ ),
981
+
982
+ # ═══════════════════════════════════════════════════════════
983
+ # FORENSICS MODULE
984
+ # ═══════════════════════════════════════════════════════════
985
+ "forensics": ModuleNode(
986
+ name="forensics",
987
+ full_path="cascade_lattice.forensics",
988
+ icon="🔎",
989
+ category="forensics",
990
+ parent="cascade_lattice",
991
+ doc_dummy="""
992
+ # 🔎 Forensics
993
+
994
+ **What is this?** Digital archaeology for data.
995
+
996
+ When you find mysterious data, forensics helps you figure out:
997
+ - 🕵️ What created this data?
998
+ - 📅 When was it created?
999
+ - 🔧 What tools/frameworks were used?
1000
+ - 🚨 Are there any security concerns?
1001
+
1002
+ **Think of it like:** CSI for code and data.
1003
+ """,
1004
+ doc_scientist="""
1005
+ # Forensics (`cascade.forensics`)
1006
+
1007
+ Data archaeology and tech stack fingerprinting.
1008
+
1009
+ ```python
1010
+ class DataForensics:
1011
+ def analyze(self, data: Any) -> ForensicsReport: ...
1012
+ def analyze_file(self, path: Path) -> ForensicsReport: ...
1013
+
1014
+ class TechFingerprinter:
1015
+ PATTERNS: Dict[str, str] # tech_name → regex
1016
+ COMPOUND_PATTERNS: Dict[str, List[str]] # framework → components
1017
+
1018
+ def analyze(self, text: str) -> List[Fingerprint]: ...
1019
+ def get_likely_stack(self) -> List[str]: ...
1020
+ def get_security_concerns(self) -> List[str]: ...
1021
+
1022
+ class ArtifactDetector:
1023
+ def detect_timestamps(self, data: Any) -> TimestampArtifacts: ...
1024
+ def detect_ids(self, data: Any) -> IDPatternArtifacts: ...
1025
+ def detect_schemas(self, data: Any) -> SchemaArtifacts: ...
1026
+ def detect_text_patterns(self, text: str) -> TextArtifacts: ...
1027
+ def detect_numeric_patterns(self, numbers: List) -> NumericArtifacts: ...
1028
+
1029
+ @dataclass
1030
+ class Fingerprint:
1031
+ tech: str
1032
+ confidence: float
1033
+ evidence: List[str]
1034
+
1035
+ @dataclass
1036
+ class ForensicsReport:
1037
+ fingerprints: List[Fingerprint]
1038
+ artifacts: Dict[str, Any]
1039
+ inferred_operations: List[InferredOperation]
1040
+ ghost_logs: List[GhostLog] # Traces of deleted data
1041
+ ```
1042
+ """,
1043
+ exports=["DataForensics", "TechFingerprinter", "ArtifactDetector",
1044
+ "Fingerprint", "ForensicsReport", "GhostLog"],
1045
+ children=["forensics.analyzer", "forensics.fingerprints", "forensics.artifacts"],
1046
+ ),
1047
+
1048
+ # ═══════════════════════════════════════════════════════════
1049
+ # LISTEN MODULE
1050
+ # ═══════════════════════════════════════════════════════════
1051
+ "listen": ModuleNode(
1052
+ name="listen",
1053
+ full_path="cascade_lattice.listen",
1054
+ icon="👂",
1055
+ category="core",
1056
+ parent="cascade_lattice",
1057
+ doc_dummy="""
1058
+ # 👂 Listen
1059
+
1060
+ **What is this?** A passive observer for HOLD events.
1061
+
1062
+ Like having a security camera that records everything without
1063
+ interfering. Events flow through and you can watch them later.
1064
+
1065
+ **The event_queue:** A line where events wait to be processed.
1066
+ """,
1067
+ doc_scientist="""
1068
+ # Listen (`cascade.listen`)
1069
+
1070
+ Passive event monitoring via thread-safe queue.
1071
+
1072
+ ```python
1073
+ event_queue: Queue # Thread-safe event queue
1074
+
1075
+ def main():
1076
+ '''CLI entry point for passive monitoring'''
1077
+ monitor = Monitor("listener")
1078
+ while True:
1079
+ try:
1080
+ event = event_queue.get(timeout=1.0)
1081
+ print(json.dumps(event.to_dict()))
1082
+ except Empty:
1083
+ continue
1084
+ ```
1085
+
1086
+ Usage:
1087
+ ```bash
1088
+ python -m cascade.listen
1089
+ ```
1090
+ """,
1091
+ exports=["event_queue", "Monitor"],
1092
+ imports_from=["core.event"],
1093
+ ),
1094
+
1095
+ # ═══════════════════════════════════════════════════════════
1096
+ # MONITOR (top-level)
1097
+ # ═══════════════════════════════════════════════════════════
1098
+ "monitor": ModuleNode(
1099
+ name="Monitor",
1100
+ full_path="cascade_lattice.Monitor",
1101
+ icon="📡",
1102
+ category="core",
1103
+ parent="cascade_lattice",
1104
+ doc_dummy="""
1105
+ # 📡 Monitor
1106
+
1107
+ **What is this?** Your eyes and ears inside the system.
1108
+
1109
+ It watches everything happening and can:
1110
+ - 📝 Record observations
1111
+ - 🔍 Trace cause and effect
1112
+ - 🔮 Predict what might happen next
1113
+ - 🕵️ Find root causes of problems
1114
+
1115
+ **Like:** A flight recorder (black box) for AI systems.
1116
+ """,
1117
+ doc_scientist="""
1118
+ # Monitor (`cascade_lattice.Monitor`)
1119
+
1120
+ Unified observation and causation tracking facade.
1121
+
1122
+ ```python
1123
+ class Monitor:
1124
+ def __init__(self, component: str):
1125
+ self.component = component
1126
+ self._graph = CausationGraph()
1127
+ self._adapter = SymbioticAdapter()
1128
+
1129
+ def observe(self, signal: Any) -> Event:
1130
+ '''Interpret signal and add to causation graph'''
1131
+ event = self._adapter.interpret(signal)
1132
+ self._graph.add_event(event)
1133
+ return event
1134
+
1135
+ def analyze_impact(self, event_id: str, max_depth: int = 20) -> ImpactReport:
1136
+ '''Forward trace: what did this event cause?'''
1137
+
1138
+ def trace_backwards(self, event_id: str, max_depth: int = 10) -> List[CausationChain]:
1139
+ '''Backward trace: what caused this event?'''
1140
+
1141
+ def trace_forwards(self, event_id: str, max_depth: int = 10) -> List[CausationChain]:
1142
+ '''Forward trace: what effects followed?'''
1143
+
1144
+ def predict_cascade(self, event: Event) -> List[Event]:
1145
+ '''Predict likely downstream effects'''
1146
+
1147
+ def find_root_causes(self, event_id: str) -> List[Event]:
1148
+ '''Find events with no causes in the subgraph'''
1149
+ ```
1150
+ """,
1151
+ exports=["Monitor"],
1152
+ imports_from=["core.graph", "core.adapter", "core.event"],
1153
+ imported_by=["hold.session", "store"],
1154
+ ),
1155
+ }
1156
+
1157
+
1158
+ # ═══════════════════════════════════════════════════════════════
1159
+ # NAVIGATION STATE
1160
+ # ═══════════════════════════════════════════════════════════════
1161
+
1162
+ @dataclass
1163
+ class NavState:
1164
+ """Navigation breadcrumb trail."""
1165
+ path: List[str] = field(default_factory=lambda: ["cascade_lattice"])
1166
+
1167
+ @property
1168
+ def current(self) -> str:
1169
+ return self.path[-1] if self.path else "cascade_lattice"
1170
+
1171
+ def push(self, module: str) -> None:
1172
+ if module not in self.path:
1173
+ self.path.append(module)
1174
+
1175
+ def pop(self) -> Optional[str]:
1176
+ if len(self.path) > 1:
1177
+ return self.path.pop()
1178
+ return None
1179
+
1180
+ def jump_to(self, module: str) -> None:
1181
+ """Jump directly to a module (creative routing)."""
1182
+ if module in self.path:
1183
+ # Already visited - trim path to that point
1184
+ idx = self.path.index(module)
1185
+ self.path = self.path[:idx + 1]
1186
+ else:
1187
+ # New destination - add to path
1188
+ self.path.append(module)
1189
+
1190
+ def reset(self) -> None:
1191
+ self.path = ["cascade_lattice"]
1192
+
1193
+
1194
+ # Global nav state
1195
+ _nav = NavState()
1196
+
1197
+
1198
+ # ═══════════════════════════════════════════════════════════════
1199
+ # CUSTOM WIDGETS
1200
+ # ═══════════════════════════════════════════════════════════════
1201
+
1202
+ class BreadcrumbWidget(Static):
1203
+ """Navigation breadcrumb trail."""
1204
+
1205
+ path = reactive(["cascade_lattice"])
1206
+
1207
+ def render(self) -> Text:
1208
+ text = Text()
1209
+ text.append("📍 ", style="bold")
1210
+
1211
+ for i, module in enumerate(self.path):
1212
+ node = MODULE_GRAPH.get(module)
1213
+ icon = node.icon if node else "📦"
1214
+
1215
+ if i > 0:
1216
+ text.append(" → ", style="dim")
1217
+
1218
+ if i == len(self.path) - 1:
1219
+ # Current module (highlighted)
1220
+ text.append(f"{icon} {module}", style="bold cyan")
1221
+ else:
1222
+ # Previous modules (clickable look)
1223
+ text.append(f"{icon} {module}", style="blue underline")
1224
+
1225
+ return text
1226
+
1227
+
1228
+ class ModuleCard(Static):
1229
+ """A clickable module card."""
1230
+
1231
+ def __init__(self, module_key: str, **kwargs):
1232
+ super().__init__(**kwargs)
1233
+ self.module_key = module_key
1234
+ self.node = MODULE_GRAPH.get(module_key, ModuleNode(name=module_key, full_path=module_key))
1235
+
1236
+ def compose(self) -> ComposeResult:
1237
+ yield Static(self._render_card(), id=f"card-content-{self.module_key}")
1238
+
1239
+ def _render_card(self) -> Text:
1240
+ text = Text()
1241
+ text.append(f"{self.node.icon} ", style="bold")
1242
+ text.append(f"{self.node.name}\n", style="bold cyan")
1243
+
1244
+ # Category badge
1245
+ cat_colors = {
1246
+ "root": "magenta",
1247
+ "core": "blue",
1248
+ "hold": "yellow",
1249
+ "store": "green",
1250
+ "genesis": "red",
1251
+ "viz": "cyan",
1252
+ "diagnostics": "orange1",
1253
+ "forensics": "purple",
1254
+ }
1255
+ color = cat_colors.get(self.node.category, "white")
1256
+ text.append(f"[{self.node.category}]", style=f"bold {color}")
1257
+
1258
+ # Export count
1259
+ if self.node.exports:
1260
+ text.append(f" • {len(self.node.exports)} exports", style="dim")
1261
+
1262
+ return text
1263
+
1264
+
1265
+ class RelatedModulesPanel(Vertical):
1266
+ """Shows related modules for creative navigation - now with clickable buttons!"""
1267
+
1268
+ module_key = reactive("cascade_lattice")
1269
+
1270
+ class ModuleClicked(Message):
1271
+ """Emitted when a related module is clicked."""
1272
+ def __init__(self, module_key: str) -> None:
1273
+ self.module_key = module_key
1274
+ super().__init__()
1275
+
1276
+ def compose(self) -> ComposeResult:
1277
+ yield Label("🔗 CONNECTIONS", classes="panel-title")
1278
+ yield Vertical(id="related-buttons")
1279
+
1280
+ def watch_module_key(self, module_key: str) -> None:
1281
+ """Rebuild buttons when module changes."""
1282
+ self._rebuild_buttons()
1283
+
1284
+ def on_mount(self) -> None:
1285
+ self._rebuild_buttons()
1286
+
1287
+ def _rebuild_buttons(self) -> None:
1288
+ """Rebuild the related module buttons."""
1289
+ try:
1290
+ container = self.query_one("#related-buttons", Vertical)
1291
+ container.remove_children()
1292
+ except NoMatches:
1293
+ return
1294
+
1295
+ node = MODULE_GRAPH.get(self.module_key)
1296
+ if not node:
1297
+ return
1298
+
1299
+ added_keys = set() # Track what we've added to avoid duplicates
1300
+
1301
+ # Parent button
1302
+ if node.parent and node.parent not in added_keys:
1303
+ parent = MODULE_GRAPH.get(node.parent)
1304
+ if parent:
1305
+ btn = Button(f"⬆️ {parent.icon} {parent.name}", variant="default", classes="related-btn")
1306
+ btn.tooltip = node.parent # Store module key in tooltip
1307
+ container.mount(btn)
1308
+ added_keys.add(node.parent)
1309
+
1310
+ # Children buttons
1311
+ for child_key in node.children:
1312
+ if child_key not in added_keys:
1313
+ child = MODULE_GRAPH.get(child_key)
1314
+ if child:
1315
+ btn = Button(f"⬇️ {child.icon} {child.name}", variant="primary", classes="related-btn")
1316
+ btn.tooltip = child_key
1317
+ container.mount(btn)
1318
+ added_keys.add(child_key)
1319
+
1320
+ # Imports from
1321
+ for imp in node.imports_from[:3]:
1322
+ if imp not in added_keys:
1323
+ imp_node = MODULE_GRAPH.get(imp)
1324
+ if imp_node:
1325
+ btn = Button(f"📥 {imp_node.icon} {imp_node.name}", variant="success", classes="related-btn")
1326
+ btn.tooltip = imp
1327
+ container.mount(btn)
1328
+ added_keys.add(imp)
1329
+
1330
+ # Imported by
1331
+ for imp in node.imported_by[:3]:
1332
+ if imp not in added_keys:
1333
+ imp_node = MODULE_GRAPH.get(imp)
1334
+ if imp_node:
1335
+ btn = Button(f"📤 {imp_node.icon} {imp_node.name}", variant="warning", classes="related-btn")
1336
+ btn.tooltip = imp
1337
+ container.mount(btn)
1338
+ added_keys.add(imp)
1339
+
1340
+ @on(Button.Pressed)
1341
+ def on_button_pressed(self, event: Button.Pressed) -> None:
1342
+ """Handle related module button click."""
1343
+ # Get module key from button tooltip
1344
+ module_key = event.button.tooltip
1345
+ if module_key and module_key in MODULE_GRAPH:
1346
+ self.post_message(self.ModuleClicked(module_key))
1347
+
1348
+
1349
+ class ExportsPanel(Static):
1350
+ """Shows exports of current module."""
1351
+
1352
+ module_key = reactive("cascade_lattice")
1353
+
1354
+ def render(self) -> Text:
1355
+ node = MODULE_GRAPH.get(self.module_key)
1356
+ if not node or not node.exports:
1357
+ return Text("No exports")
1358
+
1359
+ text = Text()
1360
+ text.append("📦 EXPORTS\n\n", style="bold green")
1361
+
1362
+ for exp in node.exports:
1363
+ # Guess type from name
1364
+ if exp[0].isupper():
1365
+ text.append(" ● ", style="cyan")
1366
+ text.append(f"{exp}\n", style="bold cyan")
1367
+ else:
1368
+ text.append(" ○ ", style="yellow")
1369
+ text.append(f"{exp}()\n", style="yellow")
1370
+
1371
+ return text
1372
+
1373
+
1374
+ class LiveStatsWidget(Static):
1375
+ """Live statistics from cascade-lattice store."""
1376
+
1377
+ def on_mount(self) -> None:
1378
+ self.set_interval(5.0, self._refresh_stats)
1379
+ self._refresh_stats()
1380
+
1381
+ def _refresh_stats(self) -> None:
1382
+ try:
1383
+ from cascade_lattice.store import stats
1384
+ s = stats()
1385
+ self._stats = s
1386
+ except Exception as e:
1387
+ self._stats = {"error": str(e)}
1388
+ self.refresh()
1389
+
1390
+ def render(self) -> Text:
1391
+ text = Text()
1392
+ text.append("📊 LIVE STATS\n\n", style="bold magenta")
1393
+
1394
+ if not hasattr(self, '_stats'):
1395
+ text.append("Loading...", style="dim")
1396
+ return text
1397
+
1398
+ s = self._stats
1399
+ if "error" in s:
1400
+ text.append(f"Error: {s['error']}", style="red")
1401
+ return text
1402
+
1403
+ text.append(f"Total Observations: ", style="dim")
1404
+ text.append(f"{s.get('total_observations', 0):,}\n", style="bold cyan")
1405
+
1406
+ text.append(f"Pinned: ", style="dim")
1407
+ text.append(f"{s.get('pinned_observations', 0):,}\n", style="green")
1408
+
1409
+ text.append(f"Genesis Root: ", style="dim")
1410
+ text.append(f"{s.get('genesis_root', 'unknown')}\n", style="bold yellow")
1411
+
1412
+ models = s.get('models', {})
1413
+ if models:
1414
+ text.append(f"\nTop Models:\n", style="dim")
1415
+ sorted_models = sorted(models.items(), key=lambda x: x[1], reverse=True)[:5]
1416
+ for name, count in sorted_models:
1417
+ short_name = name[:25] + "..." if len(name) > 25 else name
1418
+ text.append(f" {short_name}: ", style="white")
1419
+ text.append(f"{count:,}\n", style="cyan")
1420
+
1421
+ return text
1422
+
1423
+
1424
+ # ═══════════════════════════════════════════════════════════════
1425
+ # EXPLORER SCREEN
1426
+ # ═══════════════════════════════════════════════════════════════
1427
+
1428
+ class ExplorerScreen(Screen):
1429
+ """Main module explorer with graph navigation."""
1430
+
1431
+ BINDINGS = [
1432
+ Binding("escape", "go_back", "Back"),
1433
+ Binding("h", "go_home", "Home"),
1434
+ Binding("t", "toggle_mode", "Toggle Explanation"),
1435
+ Binding("s", "go_stats", "Stats"),
1436
+ Binding("d", "go_demo", "Demo"),
1437
+ ]
1438
+
1439
+ # Explanation mode
1440
+ scientist_mode = reactive(False)
1441
+ current_module = reactive("cascade_lattice")
1442
+
1443
+ def compose(self) -> ComposeResult:
1444
+ yield Header()
1445
+
1446
+ with Container(id="explorer-main"):
1447
+ # Top bar with breadcrumb and toggle
1448
+ with Horizontal(id="top-bar"):
1449
+ yield BreadcrumbWidget(id="breadcrumb")
1450
+ yield Switch(value=False, id="mode-toggle")
1451
+ yield Label("🧪 Scientist Mode", id="mode-label")
1452
+
1453
+ with Horizontal(id="content-area"):
1454
+ # Left: Module tree
1455
+ with Vertical(id="left-panel"):
1456
+ yield Label("🗂️ MODULES", classes="panel-title")
1457
+ yield Tree("cascade-lattice", id="module-tree")
1458
+
1459
+ # Center: Documentation
1460
+ with Vertical(id="center-panel"):
1461
+ yield Label("📖 DOCUMENTATION", classes="panel-title")
1462
+ with ScrollableContainer(id="doc-scroll"):
1463
+ yield Markdown("", id="doc-content")
1464
+
1465
+ # Right: Related + Exports
1466
+ with Vertical(id="right-panel"):
1467
+ yield RelatedModulesPanel(id="related-panel")
1468
+ yield Rule()
1469
+ yield ExportsPanel(id="exports-panel")
1470
+
1471
+ yield Footer()
1472
+
1473
+ def on_mount(self) -> None:
1474
+ self._build_tree()
1475
+ self._update_view()
1476
+
1477
+ def _build_tree(self) -> None:
1478
+ """Build the module tree."""
1479
+ tree = self.query_one("#module-tree", Tree)
1480
+ tree.clear()
1481
+ tree.root.expand()
1482
+ tree.show_root = True
1483
+ tree.guide_depth = 4
1484
+
1485
+ def add_children(parent_node: TreeNode, parent_key: str):
1486
+ node = MODULE_GRAPH.get(parent_key)
1487
+ if not node:
1488
+ return
1489
+
1490
+ for child_key in node.children:
1491
+ child = MODULE_GRAPH.get(child_key)
1492
+ if child:
1493
+ child_node = parent_node.add(
1494
+ f"{child.icon} {child.name}",
1495
+ data=child_key
1496
+ )
1497
+ add_children(child_node, child_key)
1498
+
1499
+ # Start with root
1500
+ root = MODULE_GRAPH.get("cascade_lattice")
1501
+ if root:
1502
+ tree.root.set_label(f"{root.icon} cascade_lattice")
1503
+ tree.root.data = "cascade_lattice"
1504
+ add_children(tree.root, "cascade_lattice")
1505
+
1506
+ def _update_view(self) -> None:
1507
+ """Update all panels for current module."""
1508
+ node = MODULE_GRAPH.get(self.current_module)
1509
+ if not node:
1510
+ return
1511
+
1512
+ # Update breadcrumb
1513
+ try:
1514
+ self.query_one("#breadcrumb", BreadcrumbWidget).path = list(_nav.path)
1515
+ except NoMatches:
1516
+ pass
1517
+
1518
+ # Update documentation
1519
+ doc = node.doc_scientist if self.scientist_mode else node.doc_dummy
1520
+ try:
1521
+ self.query_one("#doc-content", Markdown).update(doc)
1522
+ except NoMatches:
1523
+ pass
1524
+
1525
+ # Update related panel
1526
+ try:
1527
+ self.query_one("#related-panel", RelatedModulesPanel).module_key = self.current_module
1528
+ except NoMatches:
1529
+ pass
1530
+
1531
+ # Update exports panel
1532
+ try:
1533
+ self.query_one("#exports-panel", ExportsPanel).module_key = self.current_module
1534
+ except NoMatches:
1535
+ pass
1536
+
1537
+ @on(Tree.NodeSelected)
1538
+ def on_tree_select(self, event: Tree.NodeSelected) -> None:
1539
+ """Handle tree node selection."""
1540
+ if event.node.data:
1541
+ _nav.jump_to(event.node.data)
1542
+ self.current_module = event.node.data
1543
+ self._update_view()
1544
+
1545
+ @on(Switch.Changed, "#mode-toggle")
1546
+ def on_mode_toggle(self, event: Switch.Changed) -> None:
1547
+ """Toggle scientist/dummy mode."""
1548
+ self.scientist_mode = event.value
1549
+ self._update_view()
1550
+
1551
+ @on(RelatedModulesPanel.ModuleClicked)
1552
+ def on_related_clicked(self, event: RelatedModulesPanel.ModuleClicked) -> None:
1553
+ """Handle click on related module - creative navigation!"""
1554
+ _nav.jump_to(event.module_key)
1555
+ self.current_module = event.module_key
1556
+ self._update_view()
1557
+ # Also expand/select in tree
1558
+ self._select_in_tree(event.module_key)
1559
+
1560
+ def _select_in_tree(self, module_key: str) -> None:
1561
+ """Find and select a module in the tree."""
1562
+ try:
1563
+ tree = self.query_one("#module-tree", Tree)
1564
+ def find_node(node: TreeNode, key: str) -> Optional[TreeNode]:
1565
+ if node.data == key:
1566
+ return node
1567
+ for child in node.children:
1568
+ found = find_node(child, key)
1569
+ if found:
1570
+ return found
1571
+ return None
1572
+
1573
+ target = find_node(tree.root, module_key)
1574
+ if target:
1575
+ target.expand()
1576
+ tree.select_node(target)
1577
+ except NoMatches:
1578
+ pass
1579
+
1580
+ def watch_scientist_mode(self, value: bool) -> None:
1581
+ """React to mode change."""
1582
+ try:
1583
+ label = self.query_one("#mode-label", Label)
1584
+ if value:
1585
+ label.update("🧪 Scientist Mode")
1586
+ else:
1587
+ label.update("📚 For Dummies")
1588
+ except NoMatches:
1589
+ pass
1590
+
1591
+ def action_go_back(self) -> None:
1592
+ """Go back in navigation."""
1593
+ popped = _nav.pop()
1594
+ if popped:
1595
+ self.current_module = _nav.current
1596
+ self._update_view()
1597
+
1598
+ def action_go_home(self) -> None:
1599
+ """Go to root module."""
1600
+ _nav.reset()
1601
+ self.current_module = "cascade_lattice"
1602
+ self._update_view()
1603
+
1604
+ def action_toggle_mode(self) -> None:
1605
+ """Toggle explanation mode."""
1606
+ toggle = self.query_one("#mode-toggle", Switch)
1607
+ toggle.value = not toggle.value
1608
+
1609
+ def action_go_stats(self) -> None:
1610
+ """Switch to stats screen."""
1611
+ self.app.switch_mode("stats")
1612
+
1613
+ def action_go_demo(self) -> None:
1614
+ """Switch to demo screen."""
1615
+ self.app.switch_mode("demo")
1616
+
1617
+
1618
+ # ═══════════════════════════════════════════════════════════════
1619
+ # STATS SCREEN
1620
+ # ═══════════════════════════════════════════════════════════════
1621
+
1622
+ class StatsScreen(Screen):
1623
+ """Live statistics dashboard."""
1624
+
1625
+ BINDINGS = [
1626
+ Binding("escape", "go_explorer", "Explorer"),
1627
+ Binding("r", "refresh", "Refresh"),
1628
+ ]
1629
+
1630
+ def compose(self) -> ComposeResult:
1631
+ yield Header()
1632
+
1633
+ with Container(id="stats-main"):
1634
+ yield Label("📊 CASCADE-LATTICE STATISTICS", classes="screen-title")
1635
+
1636
+ with Horizontal(id="stats-row"):
1637
+ with Vertical(id="stats-left", classes="panel"):
1638
+ yield LiveStatsWidget(id="live-stats")
1639
+
1640
+ with Vertical(id="stats-right", classes="panel"):
1641
+ yield Label("🌅 GENESIS", classes="panel-title")
1642
+ yield Static(id="genesis-info")
1643
+ yield Rule()
1644
+ yield Label("📁 STORE", classes="panel-title")
1645
+ yield Static(id="store-info")
1646
+
1647
+ yield Footer()
1648
+
1649
+ def on_mount(self) -> None:
1650
+ self._load_genesis_info()
1651
+ self._load_store_info()
1652
+
1653
+ def _load_genesis_info(self) -> None:
1654
+ try:
1655
+ import cascade_lattice as cl
1656
+ text = Text()
1657
+ text.append("Genesis Root: ", style="dim")
1658
+ text.append(f"{cl.genesis.get_genesis_root()}\n\n", style="bold yellow")
1659
+ text.append("Genesis Message:\n", style="dim")
1660
+ text.append(f'"{cl.genesis.GENESIS_INPUT}"', style="italic cyan")
1661
+ self.query_one("#genesis-info", Static).update(text)
1662
+ except Exception as e:
1663
+ self.query_one("#genesis-info", Static).update(f"Error: {e}")
1664
+
1665
+ def _load_store_info(self) -> None:
1666
+ try:
1667
+ from cascade_lattice.store import DEFAULT_LATTICE_DIR, CENTRAL_DATASET
1668
+ text = Text()
1669
+ text.append("Local Store: ", style="dim")
1670
+ text.append(f"{DEFAULT_LATTICE_DIR}\n\n", style="cyan")
1671
+ text.append("Central Dataset: ", style="dim")
1672
+ text.append(f"{CENTRAL_DATASET}\n", style="green")
1673
+ self.query_one("#store-info", Static).update(text)
1674
+ except Exception as e:
1675
+ self.query_one("#store-info", Static).update(f"Error: {e}")
1676
+
1677
+ def action_go_explorer(self) -> None:
1678
+ self.app.switch_mode("explorer")
1679
+
1680
+ def action_refresh(self) -> None:
1681
+ self._load_genesis_info()
1682
+ self._load_store_info()
1683
+ self.notify("Refreshed")
1684
+
1685
+
1686
+ # ═══════════════════════════════════════════════════════════════
1687
+ # DEMO SCREEN
1688
+ # ═══════════════════════════════════════════════════════════════
1689
+
1690
+ class DemoScreen(Screen):
1691
+ """Interactive demo playground."""
1692
+
1693
+ BINDINGS = [
1694
+ Binding("escape", "go_explorer", "Explorer"),
1695
+ Binding("1", "demo_hold", "HOLD Demo"),
1696
+ Binding("2", "demo_observe", "Observe Demo"),
1697
+ Binding("3", "demo_genesis", "Genesis Demo"),
1698
+ ]
1699
+
1700
+ def compose(self) -> ComposeResult:
1701
+ yield Header()
1702
+
1703
+ with Container(id="demo-main"):
1704
+ yield Label("🎮 INTERACTIVE DEMOS", classes="screen-title")
1705
+
1706
+ with Horizontal(id="demo-buttons"):
1707
+ yield Button("⏸️ HOLD Demo", id="btn-hold", variant="primary")
1708
+ yield Button("👁️ Observe Demo", id="btn-observe", variant="success")
1709
+ yield Button("🌅 Genesis Demo", id="btn-genesis", variant="warning")
1710
+ yield Button("🔐 Provenance Demo", id="btn-provenance", variant="default")
1711
+
1712
+ with Horizontal(id="demo-content"):
1713
+ with Vertical(id="demo-code", classes="panel"):
1714
+ yield Label("📝 CODE", classes="panel-title")
1715
+ yield Static(id="code-display")
1716
+
1717
+ with Vertical(id="demo-output", classes="panel"):
1718
+ yield Label("📤 OUTPUT", classes="panel-title")
1719
+ yield RichLog(id="output-log", max_lines=100)
1720
+
1721
+ yield Footer()
1722
+
1723
+ @on(Button.Pressed, "#btn-hold")
1724
+ def on_hold_demo(self) -> None:
1725
+ self._run_hold_demo()
1726
+
1727
+ @on(Button.Pressed, "#btn-observe")
1728
+ def on_observe_demo(self) -> None:
1729
+ self._run_observe_demo()
1730
+
1731
+ @on(Button.Pressed, "#btn-genesis")
1732
+ def on_genesis_demo(self) -> None:
1733
+ self._run_genesis_demo()
1734
+
1735
+ @on(Button.Pressed, "#btn-provenance")
1736
+ def on_provenance_demo(self) -> None:
1737
+ self._run_provenance_demo()
1738
+
1739
+ def _show_code(self, code: str) -> None:
1740
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
1741
+ self.query_one("#code-display", Static).update(syntax)
1742
+
1743
+ def _log(self, message: str, style: str = "white") -> None:
1744
+ log = self.query_one("#output-log", RichLog)
1745
+ log.write(Text(message, style=style))
1746
+
1747
+ def _run_hold_demo(self) -> None:
1748
+ code = '''from cascade_lattice import Hold, HoldPoint, HoldState
1749
+ import numpy as np
1750
+
1751
+ # Create a HoldPoint (freeze-frame snapshot)
1752
+ hp = HoldPoint(
1753
+ action_probs=np.array([0.1, 0.3, 0.6]),
1754
+ value=0.75,
1755
+ observation={"screen": "game_frame_42"},
1756
+ brain_id="demo_agent"
1757
+ )
1758
+
1759
+ print(f"HoldPoint ID: {hp.id}")
1760
+ print(f"Merkle Root: {hp.merkle_root}")
1761
+ print(f"State: {hp.state}")
1762
+ print(f"Action Labels: Jump, Duck, Run")'''
1763
+
1764
+ self._show_code(code)
1765
+ self._log("═══ HOLD DEMO ═══", "bold magenta")
1766
+
1767
+ try:
1768
+ from cascade_lattice import Hold, HoldPoint, HoldState
1769
+ import numpy as np
1770
+
1771
+ hp = HoldPoint(
1772
+ action_probs=np.array([0.1, 0.3, 0.6]),
1773
+ value=0.75,
1774
+ observation={"screen": "game_frame_42"},
1775
+ brain_id="demo_agent",
1776
+ action_labels=["Jump", "Duck", "Run"]
1777
+ )
1778
+
1779
+ self._log(f"✓ Created HoldPoint", "green")
1780
+ self._log(f" ID: {hp.id}", "cyan")
1781
+ self._log(f" Merkle: {hp.merkle_root}", "yellow")
1782
+ self._log(f" State: {hp.state.value}", "white")
1783
+ self._log(f" Best Action: Run (60%)", "bold cyan")
1784
+ except Exception as e:
1785
+ self._log(f"✗ Error: {e}", "red")
1786
+
1787
+ def _run_observe_demo(self) -> None:
1788
+ code = '''from cascade_lattice.store import observe, query, stats
1789
+
1790
+ # Create an observation
1791
+ receipt = observe(
1792
+ model_id="demo_model",
1793
+ data={"action": "explore", "reward": 1.0},
1794
+ sync=False # Don't sync to HuggingFace
1795
+ )
1796
+
1797
+ print(f"CID: {receipt.cid}")
1798
+ print(f"Merkle: {receipt.merkle_root}")
1799
+
1800
+ # Query recent observations
1801
+ recent = query("demo_model", limit=5)
1802
+ print(f"Found {len(recent)} observations")'''
1803
+
1804
+ self._show_code(code)
1805
+ self._log("═══ OBSERVE DEMO ═══", "bold magenta")
1806
+
1807
+ try:
1808
+ from cascade_lattice.store import observe, query
1809
+ import time
1810
+
1811
+ receipt = observe(
1812
+ model_id="tui_demo",
1813
+ data={"action": "explore", "reward": 1.0, "timestamp": time.time()},
1814
+ sync=False
1815
+ )
1816
+
1817
+ self._log(f"✓ Created Receipt", "green")
1818
+ self._log(f" CID: {receipt.cid[:40]}...", "cyan")
1819
+ self._log(f" Merkle: {receipt.merkle_root}", "yellow")
1820
+
1821
+ recent = query("tui_demo", limit=5)
1822
+ self._log(f" Found {len(recent)} total observations", "white")
1823
+ except Exception as e:
1824
+ self._log(f"✗ Error: {e}", "red")
1825
+
1826
+ def _run_genesis_demo(self) -> None:
1827
+ code = '''import cascade_lattice as cl
1828
+
1829
+ # Get the genesis root
1830
+ root = cl.genesis.get_genesis_root()
1831
+ print(f"Genesis Root: {root}")
1832
+
1833
+ # The genesis message
1834
+ print(f"Message: {cl.genesis.GENESIS_INPUT}")
1835
+
1836
+ # Create a provenance chain
1837
+ chain = cl.genesis.create_genesis()
1838
+ print(f"Chain verified: {chain.verify()}")'''
1839
+
1840
+ self._show_code(code)
1841
+ self._log("═══ GENESIS DEMO ═══", "bold magenta")
1842
+
1843
+ try:
1844
+ import cascade_lattice as cl
1845
+
1846
+ root = cl.genesis.get_genesis_root()
1847
+ self._log(f"✓ Genesis Root: {root}", "bold yellow")
1848
+ self._log(f" \"{cl.genesis.GENESIS_INPUT}\"", "italic cyan")
1849
+
1850
+ chain = cl.genesis.create_genesis()
1851
+ self._log(f" Chain Merkle: {chain.merkle_root}", "green")
1852
+ self._log(f" Finalized: {chain.finalized}", "white")
1853
+ except Exception as e:
1854
+ self._log(f"✗ Error: {e}", "red")
1855
+
1856
+ def _run_provenance_demo(self) -> None:
1857
+ code = '''from cascade_lattice.core.provenance import (
1858
+ hash_tensor, hash_input, compute_merkle_root
1859
+ )
1860
+ import numpy as np
1861
+
1862
+ # Hash a tensor
1863
+ tensor = np.random.randn(100).astype(np.float32)
1864
+ t_hash = hash_tensor(tensor)
1865
+
1866
+ # Hash an input
1867
+ input_data = {"query": "What is 2+2?", "context": []}
1868
+ i_hash = hash_input(input_data)
1869
+
1870
+ # Compute merkle root
1871
+ root = compute_merkle_root([t_hash, i_hash])
1872
+ print(f"Merkle Root: {root}")'''
1873
+
1874
+ self._show_code(code)
1875
+ self._log("═══ PROVENANCE DEMO ═══", "bold magenta")
1876
+
1877
+ try:
1878
+ from cascade_lattice.core.provenance import (
1879
+ hash_tensor, hash_input, compute_merkle_root
1880
+ )
1881
+ import numpy as np
1882
+
1883
+ tensor = np.random.randn(100).astype(np.float32)
1884
+ t_hash = hash_tensor(tensor)
1885
+ self._log(f"✓ Tensor hash: {t_hash}", "cyan")
1886
+
1887
+ input_data = {"query": "What is 2+2?", "context": []}
1888
+ i_hash = hash_input(input_data)
1889
+ self._log(f"✓ Input hash: {i_hash}", "yellow")
1890
+
1891
+ root = compute_merkle_root([t_hash, i_hash])
1892
+ self._log(f"✓ Merkle root: {root}", "bold green")
1893
+ except Exception as e:
1894
+ self._log(f"✗ Error: {e}", "red")
1895
+
1896
+ def action_go_explorer(self) -> None:
1897
+ self.app.switch_mode("explorer")
1898
+
1899
+ def action_demo_hold(self) -> None:
1900
+ self._run_hold_demo()
1901
+
1902
+ def action_demo_observe(self) -> None:
1903
+ self._run_observe_demo()
1904
+
1905
+ def action_demo_genesis(self) -> None:
1906
+ self._run_genesis_demo()
1907
+
1908
+
1909
+ # ═══════════════════════════════════════════════════════════════
1910
+ # MAIN APP
1911
+ # ═══════════════════════════════════════════════════════════════
1912
+
1913
+ class CascadeLatticeTUI(App):
1914
+ """The Cascade-Lattice Explorer TUI."""
1915
+
1916
+ TITLE = "🌐 CASCADE-LATTICE"
1917
+ SUB_TITLE = "even still, i grow, and yet, I grow still"
1918
+
1919
+ CSS = """
1920
+ Screen {
1921
+ background: $surface;
1922
+ }
1923
+
1924
+ #explorer-main, #stats-main, #demo-main {
1925
+ layout: vertical;
1926
+ padding: 1;
1927
+ }
1928
+
1929
+ #top-bar {
1930
+ height: 3;
1931
+ padding: 0 1;
1932
+ align: left middle;
1933
+ background: $surface-darken-1;
1934
+ }
1935
+
1936
+ #breadcrumb {
1937
+ width: 1fr;
1938
+ }
1939
+
1940
+ #mode-toggle {
1941
+ width: auto;
1942
+ margin-right: 1;
1943
+ }
1944
+
1945
+ #mode-label {
1946
+ width: auto;
1947
+ }
1948
+
1949
+ #content-area {
1950
+ height: 1fr;
1951
+ }
1952
+
1953
+ #left-panel {
1954
+ width: 25%;
1955
+ min-width: 30;
1956
+ border: solid $primary;
1957
+ padding: 1;
1958
+ }
1959
+
1960
+ #center-panel {
1961
+ width: 50%;
1962
+ border: solid $secondary;
1963
+ padding: 1;
1964
+ }
1965
+
1966
+ #right-panel {
1967
+ width: 25%;
1968
+ min-width: 25;
1969
+ border: solid $accent;
1970
+ padding: 1;
1971
+ }
1972
+
1973
+ #doc-scroll {
1974
+ height: 1fr;
1975
+ }
1976
+
1977
+ .panel-title {
1978
+ text-style: bold;
1979
+ color: $text;
1980
+ padding-bottom: 1;
1981
+ }
1982
+
1983
+ .screen-title {
1984
+ text-style: bold;
1985
+ text-align: center;
1986
+ color: $primary;
1987
+ padding: 1;
1988
+ }
1989
+
1990
+ #module-tree {
1991
+ height: 1fr;
1992
+ scrollbar-gutter: stable;
1993
+ }
1994
+
1995
+ .related-btn {
1996
+ width: 100%;
1997
+ margin: 0 0 1 0;
1998
+ }
1999
+
2000
+ #related-buttons {
2001
+ height: auto;
2002
+ padding: 1 0;
2003
+ }
2004
+
2005
+ /* Stats screen */
2006
+ #stats-row {
2007
+ height: 1fr;
2008
+ }
2009
+
2010
+ #stats-left, #stats-right {
2011
+ width: 1fr;
2012
+ border: solid $primary;
2013
+ padding: 1;
2014
+ margin: 1;
2015
+ }
2016
+
2017
+ /* Demo screen */
2018
+ #demo-buttons {
2019
+ height: auto;
2020
+ padding: 1;
2021
+ align: center middle;
2022
+ }
2023
+
2024
+ #demo-buttons Button {
2025
+ margin: 0 1;
2026
+ }
2027
+
2028
+ #demo-content {
2029
+ height: 1fr;
2030
+ }
2031
+
2032
+ #demo-code, #demo-output {
2033
+ width: 1fr;
2034
+ border: solid $primary;
2035
+ padding: 1;
2036
+ margin: 1;
2037
+ }
2038
+
2039
+ #code-display {
2040
+ height: 1fr;
2041
+ }
2042
+
2043
+ #output-log {
2044
+ height: 1fr;
2045
+ }
2046
+
2047
+ .panel {
2048
+ border: solid $surface-lighten-1;
2049
+ padding: 1;
2050
+ margin: 0 1;
2051
+ }
2052
+ """
2053
+
2054
+ BINDINGS = [
2055
+ Binding("e", "go_explorer", "Explorer", show=True),
2056
+ Binding("s", "go_stats", "Stats", show=True),
2057
+ Binding("d", "go_demo", "Demo", show=True),
2058
+ Binding("q", "quit", "Quit", show=True),
2059
+ ]
2060
+
2061
+ MODES = {
2062
+ "explorer": ExplorerScreen,
2063
+ "stats": StatsScreen,
2064
+ "demo": DemoScreen,
2065
+ }
2066
+
2067
+ def on_mount(self) -> None:
2068
+ """Start on explorer."""
2069
+ self.switch_mode("explorer")
2070
+
2071
+ def action_go_explorer(self) -> None:
2072
+ self.switch_mode("explorer")
2073
+
2074
+ def action_go_stats(self) -> None:
2075
+ self.switch_mode("stats")
2076
+
2077
+ def action_go_demo(self) -> None:
2078
+ self.switch_mode("demo")
2079
+
2080
+
2081
+ # ═══════════════════════════════════════════════════════════════
2082
+ # ENTRY POINT
2083
+ # ═══════════════════════════════════════════════════════════════
2084
+
2085
+ def main():
2086
+ """Run the TUI."""
2087
+ app = CascadeLatticeTUI()
2088
+ app.run()
2089
+
2090
+
2091
+ if __name__ == "__main__":
2092
+ main()
pyproject.toml CHANGED
@@ -58,6 +58,9 @@ demo = [
58
  "rl-zoo3>=2.0.0",
59
  "box2d-py>=2.3.5",
60
  ]
 
 
 
61
  all = [
62
  "openai>=1.0.0",
63
  "anthropic>=0.18.0",
@@ -69,6 +72,7 @@ all = [
69
  "networkx>=2.6",
70
  "datasets>=2.14.0",
71
  "sentence-transformers>=2.2.0",
 
72
  ]
73
  dev = [
74
  "pytest>=7.0",
@@ -87,6 +91,7 @@ Issues = "https://github.com/Yufok1/cascade-lattice/issues"
87
 
88
  [project.scripts]
89
  cascade = "cascade.cli_main:main"
 
90
 
91
  [tool.hatch.version]
92
  path = "cascade/__init__.py"
 
58
  "rl-zoo3>=2.0.0",
59
  "box2d-py>=2.3.5",
60
  ]
61
+ tui = [
62
+ "textual>=0.40.0",
63
+ ]
64
  all = [
65
  "openai>=1.0.0",
66
  "anthropic>=0.18.0",
 
72
  "networkx>=2.6",
73
  "datasets>=2.14.0",
74
  "sentence-transformers>=2.2.0",
75
+ "textual>=0.40.0",
76
  ]
77
  dev = [
78
  "pytest>=7.0",
 
91
 
92
  [project.scripts]
93
  cascade = "cascade.cli_main:main"
94
+ cascade-tui = "cascade.tui:main"
95
 
96
  [tool.hatch.version]
97
  path = "cascade/__init__.py"
tui.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """
3
+ 🌐 CASCADE-LATTICE TUI - Entry Point
4
+
5
+ Run from repo root: python tui.py
6
+ """
7
+
8
+ from cascade.tui import main
9
+
10
+ if __name__ == "__main__":
11
+ main()