|
| 1 | +# MultiverseNote v2 |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +MultiverseNote is an open-source terminal-based application that transforms AI chatbot interactions into structured knowledge management workflows. It introduces a **branch management system** where users can discuss topics on a main branch and create new branches for in-depth exploration, enabling systematic and continuous knowledge development. |
| 6 | + |
| 7 | +Each conversation is organized into **branches** with numbered references (e.g. `#1`, `#2`), allowing you to fork discussions, load context from other branches, and tag conversation pairs for easy retrieval. |
| 8 | + |
| 9 | +### Core Architecture |
| 10 | + |
| 11 | +Every component (UI, LLM, context management) runs as an **isolated process node**, communicating exclusively through **Redis Pub/Sub**. This ensures modularity and allows components to be developed, tested, and scaled independently. |
| 12 | + |
| 13 | +```mermaid |
| 14 | +flowchart LR |
| 15 | + UI["Terminal UI\n(Textual)"] |
| 16 | + CTX["ContextNode\n(Branch & Context)"] |
| 17 | + LLM["LLMNode\n(litellm)"] |
| 18 | + R[(Redis)] |
| 19 | +
|
| 20 | + UI -- "ch:user_input" --> R |
| 21 | + R -- "ch:user_input" --> CTX |
| 22 | + CTX -- "store user node\n+ assemble context" --> R |
| 23 | + CTX -- "ch:llm_request" --> R |
| 24 | + R -- "ch:llm_request" --> LLM |
| 25 | + LLM -- "ch:llm_response" --> R |
| 26 | + R -- "ch:llm_response" --> UI |
| 27 | +
|
| 28 | + style R fill:#dc382c,color:#fff |
| 29 | + style UI fill:#1a8cff,color:#fff |
| 30 | + style CTX fill:#2b8a3e,color:#fff |
| 31 | + style LLM fill:#7c3aed,color:#fff |
| 32 | +``` |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## Features |
| 37 | + |
| 38 | +```mermaid |
| 39 | +gitGraph |
| 40 | + commit id: "user: What is Rust?" |
| 41 | + commit id: "assistant: Rust is a..." |
| 42 | + branch deep-dive |
| 43 | + commit id: "user: Explain ownership" |
| 44 | + commit id: "assistant: Ownership in Rust..." |
| 45 | + branch ownership-examples |
| 46 | + commit id: "user: Show me borrowing" |
| 47 | + commit id: "assistant: Here is an example..." |
| 48 | + checkout deep-dive |
| 49 | + commit id: "user: What about lifetimes?" |
| 50 | + commit id: "assistant: Lifetimes ensure..." |
| 51 | + checkout main |
| 52 | + commit id: "user: Compare Rust vs Go" |
| 53 | + commit id: "assistant: Both are systems..." |
| 54 | +``` |
| 55 | + |
| 56 | +- **Branch Management** — Create, switch, and navigate conversation branches with parent-child relationships |
| 57 | +- **Context References** — New branches can reference existing branches, loading their conversation history as context for the LLM |
| 58 | +- **Tag System** — Tag conversation pairs with colored labels for organization and retrieval |
| 59 | +- **Multi-Provider LLM** — Supports OpenAI, Anthropic, and local models via litellm |
| 60 | +- **Terminal UI** — Full keyboard-navigable TUI built with Textual |
| 61 | +- **Isolated Nodes** — Each component runs as a separate process communicating via Redis |
| 62 | +- **File-based Logging** — All logs written to rotating files, no terminal output clutter |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## Prerequisites |
| 67 | + |
| 68 | +- **Python** >= 3.11 |
| 69 | +- **Redis** server running locally (default: `localhost:6379`) |
| 70 | +- **uv** package manager |
| 71 | +- An **LLM API key** (e.g. `OPENAI_API_KEY` for the default OpenAI provider) |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Installation |
| 76 | + |
| 77 | +```bash |
| 78 | +# Clone the repository |
| 79 | +git clone https://github.com/AhmadCodes/MultiverseNoteV2.git |
| 80 | +cd MultiverseNoteV2 |
| 81 | + |
| 82 | +# Install dependencies |
| 83 | +uv sync |
| 84 | +``` |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +## Usage |
| 89 | + |
| 90 | +### Start Redis |
| 91 | + |
| 92 | +```bash |
| 93 | +redis-server |
| 94 | +``` |
| 95 | + |
| 96 | +### Set your API key |
| 97 | + |
| 98 | +```bash |
| 99 | +export OPENAI_API_KEY="sk-..." |
| 100 | +``` |
| 101 | + |
| 102 | +### Launch the application |
| 103 | + |
| 104 | +```bash |
| 105 | +uv run multiverse |
| 106 | +``` |
| 107 | + |
| 108 | +### Keyboard Shortcuts |
| 109 | + |
| 110 | +| Key | Action | |
| 111 | +|-----|--------| |
| 112 | +| `Enter` | Send message | |
| 113 | +| `Ctrl+B` | Open branch tree | |
| 114 | +| `Ctrl+Q` | Quit | |
| 115 | +| `Escape` | Close modal / Go back | |
| 116 | +| `N` | New branch (in branch tree) | |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +## Configuration |
| 121 | + |
| 122 | +Configuration is managed via [Hydra](https://hydra.cc/) with composable YAML files in `src/multiverse_note/conf/`. |
| 123 | + |
| 124 | +### LLM Provider |
| 125 | + |
| 126 | +Default is OpenAI. To use Anthropic: |
| 127 | + |
| 128 | +```bash |
| 129 | +uv run multiverse llm=anthropic |
| 130 | +``` |
| 131 | + |
| 132 | +Or override individual settings: |
| 133 | + |
| 134 | +```bash |
| 135 | +uv run multiverse llm.model=openai/gpt-4o-mini llm.temperature=0.5 |
| 136 | +``` |
| 137 | + |
| 138 | +### Redis |
| 139 | + |
| 140 | +```bash |
| 141 | +uv run multiverse redis.host=myhost redis.port=6380 redis.db=1 |
| 142 | +``` |
| 143 | + |
| 144 | +### Config Files |
| 145 | + |
| 146 | +``` |
| 147 | +src/multiverse_note/conf/ |
| 148 | +├── config.yaml # Main config (composes all groups) |
| 149 | +├── redis/default.yaml # host, port, db, password |
| 150 | +├── llm/ |
| 151 | +│ ├── default.yaml # Default provider (OpenAI) |
| 152 | +│ ├── openai.yaml |
| 153 | +│ └── anthropic.yaml |
| 154 | +├── ui/default.yaml # Theme, layout preferences |
| 155 | +└── logging/default.yaml # Log level, file path, rotation |
| 156 | +``` |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +## Process Model |
| 161 | + |
| 162 | +```mermaid |
| 163 | +flowchart TB |
| 164 | + MAIN["main.py\n@hydra.main"] |
| 165 | +
|
| 166 | + MAIN -->|"multiprocessing.Process\n(daemon)"| CTX_PROC["ContextNode Process"] |
| 167 | + MAIN -->|"multiprocessing.Process\n(daemon)"| LLM_PROC["LLMNode Process"] |
| 168 | + MAIN -->|"runs in main process\n(needs terminal)"| UI_PROC["MultiverseApp (Textual)"] |
| 169 | +
|
| 170 | + CTX_PROC -. "Redis Pub/Sub" .- REDIS[(Redis)] |
| 171 | + LLM_PROC -. "Redis Pub/Sub" .- REDIS |
| 172 | + UI_PROC -. "Redis Pub/Sub" .- REDIS |
| 173 | +
|
| 174 | + MAIN -->|"SIGINT / Ctrl+Q"| SHUTDOWN["Graceful Shutdown"] |
| 175 | + SHUTDOWN -->|"ch:system → shutdown"| REDIS |
| 176 | +
|
| 177 | + style REDIS fill:#dc382c,color:#fff |
| 178 | + style MAIN fill:#333,color:#fff |
| 179 | + style SHUTDOWN fill:#e67700,color:#fff |
| 180 | +``` |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +## Project Structure |
| 185 | + |
| 186 | +``` |
| 187 | +MultiverseNoteV2/ |
| 188 | +├── pyproject.toml # UV-managed dependencies |
| 189 | +├── src/ |
| 190 | +│ └── multiverse_note/ |
| 191 | +│ ├── conf/ # Hydra configuration (bundled in package) |
| 192 | +│ ├── main.py # Entry point: launches all nodes |
| 193 | +│ ├── models/ # Data models (dataclasses) |
| 194 | +│ │ ├── branch.py # Branch model |
| 195 | +│ │ ├── node.py # ConversationNode model |
| 196 | +│ │ └── tag.py # Tag model |
| 197 | +│ ├── store/ # Redis storage layer |
| 198 | +│ │ ├── redis_client.py # Redis connection wrapper |
| 199 | +│ │ ├── branch_store.py # Branch CRUD operations |
| 200 | +│ │ ├── node_store.py # ConversationNode CRUD |
| 201 | +│ │ └── tag_store.py # Tag CRUD operations |
| 202 | +│ ├── nodes/ # Isolated process nodes |
| 203 | +│ │ ├── base.py # Abstract BaseNode class |
| 204 | +│ │ ├── llm_node.py # LLM processing node |
| 205 | +│ │ └── context_node.py # Context & branch management node |
| 206 | +│ ├── ui/ # Terminal UI (Textual) |
| 207 | +│ │ ├── app.py # Main Textual App |
| 208 | +│ │ ├── screens/ |
| 209 | +│ │ │ ├── conversation.py # Active conversation screen |
| 210 | +│ │ │ ├── branch_tree.py # Branch navigation/overview |
| 211 | +│ │ │ └── tag_select.py # Tag selection modal |
| 212 | +│ │ └── widgets/ |
| 213 | +│ │ ├── message_view.py # Renders conversation messages |
| 214 | +│ │ ├── context_bar.py # Shows loaded context refs |
| 215 | +│ │ └── tag_badge.py # Colored tag display |
| 216 | +│ └── utils/ |
| 217 | +│ └── logging.py # File-based logging utility |
| 218 | +├── tests/ # Test suite (51 tests) |
| 219 | +│ ├── conftest.py # fakeredis fixtures |
| 220 | +│ ├── test_models/ |
| 221 | +│ ├── test_store/ |
| 222 | +│ └── test_nodes/ |
| 223 | +└── logs/ # Git-ignored log output |
| 224 | +``` |
| 225 | + |
| 226 | +--- |
| 227 | + |
| 228 | +## Redis Schema |
| 229 | + |
| 230 | +```mermaid |
| 231 | +erDiagram |
| 232 | + BRANCH { |
| 233 | + int id PK |
| 234 | + string name |
| 235 | + string created_at |
| 236 | + int parent_branch_id FK |
| 237 | + string status |
| 238 | + } |
| 239 | + BRANCH ||--o{ NODE : "branch:id:nodes" |
| 240 | + BRANCH ||--o{ BRANCH : "branch:id:context_refs" |
| 241 | +
|
| 242 | + NODE { |
| 243 | + int id PK |
| 244 | + int branch_id FK |
| 245 | + string role |
| 246 | + string content |
| 247 | + string title |
| 248 | + string summary |
| 249 | + string timestamp |
| 250 | + } |
| 251 | + NODE ||--o{ TAG : "node:id:tags" |
| 252 | +
|
| 253 | + TAG { |
| 254 | + int id PK |
| 255 | + string name |
| 256 | + string color |
| 257 | + string created_at |
| 258 | + } |
| 259 | +``` |
| 260 | + |
| 261 | +### Key Patterns |
| 262 | + |
| 263 | +| Key | Type | Description | |
| 264 | +|-----|------|-------------| |
| 265 | +| `counter:branch` | int | Auto-increment ID for branches | |
| 266 | +| `counter:node` | int | Auto-increment ID for nodes | |
| 267 | +| `counter:tag` | int | Auto-increment ID for tags | |
| 268 | +| `branch:{id}` | Hash | Branch metadata | |
| 269 | +| `branch:{id}:nodes` | List | Ordered node IDs in a branch | |
| 270 | +| `branch:{id}:context_refs` | Set | Referenced branch IDs as context | |
| 271 | +| `node:{id}` | Hash | Conversation node data | |
| 272 | +| `node:{id}:tags` | Set | Tag IDs assigned to a node | |
| 273 | +| `tag:{id}` | Hash | Tag metadata | |
| 274 | +| `tags:all` | Set | All tag IDs | |
| 275 | + |
| 276 | +### Pub/Sub Channels |
| 277 | + |
| 278 | +```mermaid |
| 279 | +flowchart TB |
| 280 | + subgraph Channels |
| 281 | + direction LR |
| 282 | + ui_in["ch:user_input"] |
| 283 | + llm_req["ch:llm_request"] |
| 284 | + llm_res["ch:llm_response"] |
| 285 | + br_ev["ch:branch_event"] |
| 286 | + tag_ev["ch:tag_event"] |
| 287 | + sys["ch:system"] |
| 288 | + end |
| 289 | +
|
| 290 | + UI(["Terminal UI"]) -->|user text| ui_in |
| 291 | + ui_in -->|consumed by| CTX(["ContextNode"]) |
| 292 | + CTX -->|assembled context| llm_req |
| 293 | + llm_req -->|consumed by| LLM(["LLMNode"]) |
| 294 | + LLM -->|response| llm_res |
| 295 | + llm_res -->|displayed by| UI |
| 296 | +
|
| 297 | + UI -->|create/switch| br_ev |
| 298 | + br_ev -->|handled by| CTX |
| 299 | + UI -->|create/assign| tag_ev |
| 300 | + tag_ev -->|handled by| CTX |
| 301 | +
|
| 302 | + MAIN(["main.py"]) -->|shutdown| sys |
| 303 | + sys -->|stops| CTX |
| 304 | + sys -->|stops| LLM |
| 305 | + sys -->|stops| UI |
| 306 | +``` |
| 307 | + |
| 308 | +--- |
| 309 | + |
| 310 | +## Testing |
| 311 | + |
| 312 | +Tests use [fakeredis](https://github.com/cunla/fakeredis) to simulate Redis without a running server. |
| 313 | + |
| 314 | +```bash |
| 315 | +# Run all 51 tests |
| 316 | +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 uv run pytest tests/ -v |
| 317 | +``` |
| 318 | + |
| 319 | +Test coverage includes: |
| 320 | +- **Model tests** — Serialization roundtrips for Branch, ConversationNode, Tag |
| 321 | +- **Store tests** — Full CRUD operations with fakeredis |
| 322 | +- **Node tests** — BaseNode lifecycle, ContextNode branch/context/tag management, LLMNode with mocked litellm |
| 323 | + |
| 324 | +--- |
| 325 | + |
| 326 | +## Key Libraries |
| 327 | + |
| 328 | +| Purpose | Library | |
| 329 | +|---------|---------| |
| 330 | +| Terminal UI | [textual](https://textual.textualize.io/) | |
| 331 | +| Redis | [redis-py](https://github.com/redis/redis-py) with hiredis | |
| 332 | +| LLM Integration | [litellm](https://github.com/BerriAI/litellm) | |
| 333 | +| Configuration | [hydra-core](https://hydra.cc/) | |
| 334 | +| Package Management | [uv](https://github.com/astral-sh/uv) | |
| 335 | +| Testing | [pytest](https://pytest.org/) + [fakeredis](https://github.com/cunla/fakeredis) | |
| 336 | + |
| 337 | +--- |
| 338 | + |
| 339 | +## License |
| 340 | + |
| 341 | +This project is released under the Apache License 2.0. See the [LICENSE](LICENSE) file for details. |
0 commit comments