ai-conversation-nostr-design.md (5714B)
1 # AI Conversation Nostr Notes — Design Spec 2 3 ## Overview 4 5 Represent claude-code session JSONL lines as nostr events, enabling: 6 1. **Session presentation resume** — reload a previous session's UI from local nostr DB without re-parsing JSONL 7 2. **Round-trip fidelity** — reconstruct the original JSONL from nostr events for claude-code `--resume` 8 3. **Future sharing** — structure is ready for publishing sessions to relays (with privacy considerations deferred) 9 10 ## Architecture 11 12 ``` 13 claude-code JSONL ──→ nostr events ──→ ndb.process_event (local) 14 │ 15 ├──→ UI rendering (presentation from nostr query) 16 └──→ JSONL reconstruction (for --resume) 17 ``` 18 19 ## Event Structure 20 21 **Kind**: Regular event (1000-9999 range, specific number TBD). Immutable — no replaceable events. 22 23 Each JSONL line becomes one nostr event. Every message type (user, assistant, tool_call, tool_result, progress, etc.) gets its own note for 1:1 JSONL line reconstruction. 24 25 ### Note Format 26 27 ```json 28 { 29 "kind": "<TBD>", 30 "content": "<human-readable presentation text>", 31 "tags": [ 32 // Session identity 33 ["d", "<session-id>"], 34 ["session-slug", "<human-readable-name>"], 35 36 // Threading (NIP-10) 37 ["e", "<root-note-id>", "", "root"], 38 ["e", "<parent-note-id>", "", "reply"], 39 40 // Message metadata 41 ["source", "claude-code"], 42 ["source-version", "2.1.42"], 43 ["role", "<user|assistant|system|tool_call|tool_result>"], 44 ["model", "claude-opus-4-6"], 45 ["turn-type", "<JSONL type field: user|assistant|progress|queue-operation|file-history-snapshot>"], 46 47 // Discoverability 48 ["t", "ai-conversation"], 49 50 // Lossless reconstruction (Option 3) 51 ["source-data", "<JSON-escaped JSONL line with paths normalized>"] 52 ] 53 } 54 ``` 55 56 ### Content Field 57 58 Human-readable text suitable for rendering in any nostr client: 59 - **user**: The user's message text 60 - **assistant**: The assistant's rendered markdown text (text blocks only) 61 - **tool_call**: Summary like `Glob: {"pattern": "**/*.rs"}` or tool name + input preview 62 - **tool_result**: The tool output text (possibly truncated for presentation) 63 - **progress**: Description of the progress event 64 - **queue-operation / file-history-snapshot**: Minimal description 65 66 ### source-data Tag 67 68 Contains the **full JSONL line** as a JSON string with these transformations applied: 69 - **Path normalization**: All absolute paths converted to relative (using `cwd` as base) 70 - **Sensitive data stripping** (TODO — deferred to later task): 71 - Token usage / cache statistics 72 - API request IDs 73 - Permission mode details 74 75 On reconstruction, relative paths are re-expanded using the local machine's working directory. 76 77 ## JSONL Line Type → Nostr Event Mapping 78 79 | JSONL `type` | `role` tag | `content` | Notes | 80 |---|---|---|---| 81 | `user` (text) | `user` | User's message text | Simple text content | 82 | `user` (tool_result) | `tool_result` | Tool output text | Separated from user text | 83 | `assistant` (text) | `assistant` | Rendered markdown | Text blocks from content array | 84 | `assistant` (tool_use) | `tool_call` | Tool name + input summary | Each tool_use block = separate note | 85 | `progress` | `progress` | Hook progress description | Mapped for round-trip fidelity | 86 | `queue-operation` | `queue-operation` | Operation type | Mapped for round-trip fidelity | 87 | `file-history-snapshot` | `file-history-snapshot` | Snapshot summary | Mapped for round-trip fidelity | 88 89 **Important**: Assistant messages with mixed content (text + tool_use blocks) are split into multiple nostr events — one per content block. Each gets its own note, threaded in sequence via `e` tags. 90 91 ## Conversation Threading 92 93 Uses **NIP-10** reply threading: 94 - First note in a session: no `e` tags (it is the root) 95 - All subsequent notes: `["e", "<root-id>", "", "root"]` + `["e", "<prev-id>", "", "reply"]` 96 - The `e` tags always reference **nostr note IDs** (not JSONL UUIDs) 97 - UUID-to-note-ID mapping is maintained during conversion 98 99 ## Path Normalization 100 101 When converting JSONL → nostr events: 102 1. Extract `cwd` from the JSONL line 103 2. All absolute paths that start with `cwd` are converted to relative paths 104 3. `cwd` itself is stored as a relative path (or stripped, with project root as implicit base) 105 106 When reconstructing nostr events → JSONL: 107 1. Determine local working directory 108 2. Re-expand all relative paths to absolute using local `cwd` 109 3. Update `cwd`, `gitBranch`, and machine-specific fields 110 111 ## Data Flow (Phase 1 — Local Only) 112 113 ### Publishing (JSONL → nostr events) 114 1. On session activity, dave reads new JSONL lines 115 2. Each line is converted to a nostr event (normalize paths, extract presentation content) 116 3. Events are inserted via `ndb.process_event()` (local relay only) 117 4. UUID-to-note-ID mapping is cached for threading 118 119 ### Consuming (nostr events → UI) 120 1. Query ndb for events with the session's `d` tag 121 2. Order by `e` tag threading (NIP-10 reply chain) 122 3. Render `content` field directly in the conversation UI 123 4. `role` tag determines message styling (user bubble, assistant bubble, tool collapse, etc.) 124 125 ### Reconstruction (nostr events → JSONL, for future resume) 126 1. Query ndb for all events in a session (by `d` tag) 127 2. Order by reply chain 128 3. Extract `source-data` tag from each event 129 4. De-normalize paths (relative → absolute for local machine) 130 5. Write as JSONL file 131 6. Resume via `claude --resume <session-id>` 132 133 ## Non-Goals (Phase 1) 134 135 - Publishing to external relays (privacy concerns) 136 - Resuming shared sessions from other users 137 - Sensitive data stripping (noted as TODO) 138 - NIP proposal (informal notedeck convention for now)