notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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)