# Human Nostr Sessions

## Overview

Allow humans to interact with ouija sessions via Nostr DMs from any client
(phone, desktop, web). A human is registered as a session — just another peer
in the mesh — so existing routing, delivery, and session management all work
without special cases.

## Design Principles

- A human is a session, not a special case
- The daemon stays dumb — no LLM, just routing and command parsing
- Three delivery mechanisms from one unified path:
  - **Local** (has pane) → tmux inject
  - **Remote** (peer daemon) → Nostr wire protocol DM
  - **Human** (has npub, no pane) → plain-text Nostr DM
- Inter-session chatter stays invisible to the human — only messages
  explicitly addressed to/from the human session are relayed

## Config Changes

### settings.json

Add `human_sessions` to the persisted settings:

```json
{
  "auto_register": true,
  "human_sessions": [
    {
      "npub": "npub1abc...",
      "name": "daniel",
      "admin": true,
      "default_session": "ouija"
    }
  ]
}
```

Fields:
- `npub` — the human's Nostr public key
- `name` — session ID for this human (used in `[from daniel]:` prefixes)
- `admin` — can manage daemon config, connect/disconnect nodes, etc.
- `default_session` — where unprefixed messages route to

### CLI

```
ouija config add-human --npub npub1abc... --name daniel --admin
ouija config remove-human --name daniel
ouija config list-humans
```

## Session Model

### New SessionOrigin variant

```rust
enum SessionOrigin {
    Local,
    Remote(String),
    Human(String),  // String = npub
}
```

### Human session registration

On daemon startup, for each configured human session:
- Register a `Session` with `origin: Human(npub)`, `pane: None`
- The session appears in `/list`, is announced to peers, etc.
- On first contact (first DM from this npub), send the welcome/help message

### Delivery path

In `send_msg` (api.rs), when the target session has `origin: Human(npub)`:
- Format the message as `[from {sender_id}]: {message}`
- Send as a plain-text Nostr DM (NIP-17) to the human's npub
- No tmux, no wire protocol wrapping — just a readable DM

## Incoming DM Processing

### Receive loop changes (nostr_transport.rs)

Currently, the receive loop:
1. Unwraps gift wrap → gets `(rumor, sender_pubkey)`
2. Checks if sender is an authorized peer daemon
3. Parses rumor content as `WireMessage` JSON

New flow:
1. Unwrap gift wrap → `(rumor, sender_pubkey)`
2. Check if sender is an authorized peer daemon → existing wire protocol path
3. **Check if sender is a configured human npub** → human message path
4. Reject otherwise

### Human message path

The rumor content is plain text (not JSON). Route based on content:

**Daemon commands** (prefix `/`):

| Command | Action |
|---------|--------|
| `/help` | Reply with help text |
| `/list` | Reply with current session list |
| `/default <id>` | Set default session for this human |
| `/status` | Daemon status summary |

The daemon handles these directly and replies via Nostr DM.

**Session-targeted messages** (prefix `@`):

```
@ouija-dev how's the dedup fix?
```

Parsed as: `session_send(from="daniel", to="ouija-dev", message="how's the dedup fix?")`

Delivered to the target session via the normal local/remote path. The target
session sees: `[from daniel]: how's the dedup fix?`

**Default session messages** (no prefix):

```
hey what's the status of the auth work?
```

Routed to the human's configured `default_session`. Equivalent to:
`session_send(from="daniel", to="ouija", message="hey what's the status of the auth work?")`

### Response routing

When a Claude session sends a message to the human session (via `session_send`
or MCP `ouija_send`), the daemon:
1. Looks up the target session
2. Sees `origin: Human(npub)`
3. Formats as `[from {sender}]: {message}`
4. Sends as plain-text Nostr DM to the npub

The Claude session doesn't need to know the target is human — it uses the
same `session_send(to="daniel", ...)` as any other message.

## Welcome Message

Sent on first DM from a configured human, or on `/help`:

```
ouija ({daemon_name})

Sessions:
  ouija       — working on ouija [default]
  divine-web  — working on divine-web
  daniel      — you

Commands:
  /help              — this message
  /list              — show sessions
  /default <id>      — set default session
  /status            — daemon status

Usage:
  <message>          — send to default session (ouija)
  @<id> <message>    — send to specific session
```

## Admin Commands

When `admin: true`, additional commands are available:

| Command | Action |
|---------|--------|
| `/connect <ticket>` | Connect to a remote node |
| `/disconnect <name>` | Disconnect from a node |
| `/nodes` | List connected nodes |
| `/task list` | List scheduled tasks |
| `/task trigger <id>` | Trigger a task immediately |

Non-admin humans can only send messages and use basic commands.

## Implementation Steps

### Step 1: Config & persistence
- Add `HumanSession` struct to persistence.rs
- Add `human_sessions` field to `OuijaSettings`
- Add CLI commands: `ouija config add-human`, `remove-human`, `list-humans`

### Step 2: Session model
- Add `Human(String)` variant to `SessionOrigin`
- Register human sessions on daemon startup
- Handle human sessions in session serialization/persistence

### Step 3: Outbound delivery
- In the send path (api.rs `send_msg`), add the Human delivery branch
- Format as `[from sender]: message`
- Send plain-text Nostr DM via `NostrTransport`
- Add `send_plain_dm(npub, text)` method to NostrTransport

### Step 4: Inbound receive
- In the receive loop, detect human senders (not peer, not wire protocol)
- Parse commands (`/help`, `/list`, `/default`, `@target msg`, plain text)
- Route to appropriate handler or session
- Reply to human via DM for daemon commands

### Step 5: Welcome & help
- Track "first contact" per human (persist flag)
- Send welcome message on first DM
- Implement `/help` response

### Step 6: Admin commands
- Gate admin commands behind `admin` flag
- Implement `/connect`, `/disconnect`, `/nodes`, `/task` subcommands

### Step 7: Admin UI
- Show human sessions in the admin dashboard
- Allow adding/removing humans from the UI

## Resolved Questions

1. **No rate limiting needed** — human DMs are signed by the human's key,
   daemon replies are signed by the daemon's key. A session auto-replying
   to a human would produce a daemon-signed DM, not a human-signed one,
   so no feedback loop is possible.

2. **Current state only** — `/help` shows live session list and commands.
   Message history is the Nostr client's job (backfill, caching, etc.).

3. **Multi-daemon works naturally** — each daemon has its own npub, so each
   is a separate DM conversation in the human's Nostr client. Remote sessions
   from connected daemons are accessible via the normal `@daemon/session` syntax.

4. **Relay selection** — use the human's kind:10050 DM relay list (per NIP-17).
   Fallback chain: kind:10050 → kind:10002 (NIP-65) → daemon's own relays.
   Each gift wrap uses a fresh ephemeral key, rumor must be kind:14 with a
   `["p", "<recipient_pubkey>"]` tag. Also self-wrap to the daemon's own
   kind:10050 relays for sent message history.
