Files
didactyl/didactyl_browser/browser_didactyl.md
T

30 KiB

Browser Didactyl — A Pure-Browser Agent Runtime

Vision

Port Didactyl's agent loop to JavaScript as a standalone web page. The browser becomes the runtime substrate — no shell, no C binary. You open the page, sign in as the agent using the agent's Nostr private key (via nostr-login-lite), and your agent boots: it reads its soul, skills, adoption list, and memory from relays, listens for triggers, reasons with an LLM, and acts through a browser-safe tool set.

This is not ai.html (a chat app for conversations) and not sovereign_browser (a full OS browser in C/WebKitGTK). It is Didactyl's agent semantics — triggers, two-layer context, skill adoption, tool-call loop — running natively in a browser tab.


Why This Works

Didactyl is Nostr-first. Identity, memory, skills, soul, and communication all live as Nostr events. The only things tying it to a shell are:

  1. The C binary process — a browser tab replaces this
  2. Shell-specific tools — local_shell_exec, local_file_read, local_file_write, local_http_fetch — these are dropped or replaced with browser equivalents

The agent loop itself is portable:

Boot → load config from Nostr → subscribe to triggers →
  on trigger: build context → call LLM → execute tools → loop →
  deliver response

A browser can do every step:

Capability Browser Implementation
Nostr relay connection WebSocket (NDK worker or nostr-tools)
Event signing NIP-07 window.nostr or n_signer
NIP-04/44 encrypt/decrypt window.nostr.nip04 / window.nostr.nip44
LLM calls fetch() to OpenAI-compatible endpoint
Skill/soul/memory storage Kind 31123/31124/30078/10123 events
Blossom blob storage HTTP fetch() to Blossom servers
Tool execution JS tool dispatch (browser-safe subset)

Architecture

flowchart TB
    subgraph BrowserTab
        UI[Chat UI Page]
        CORE[Agent Core JS Module]
        TOOLS[Tool Dispatch JS Module]
        CTX[Context Builder JS Module]
    end

    subgraph NostrLayer
        RELAYS[Nostr Relays via WebSocket]
        EVENTS[Kind 31123 Skills<br/>Kind 10123 Adoption<br/>Kind 30078 Memory/Config<br/>Kind 4/13 DMs]
    end

    subgraph External
        LLM[OpenAI-compatible LLM API]
        SIGNER[NIP-07 Signer Extension<br/>or n_signer]
        BLOSSOM[Blossom Servers]
    end

    UI --> CORE
    CORE --> CTX
    CORE --> TOOLS
    CORE -->|WebSocket| RELAYS
    RELAYS --> EVENTS
    CORE -->|fetch| LLM
    CORE -->|window.nostr| SIGNER
    TOOLS -->|fetch| BLOSSOM
    TOOLS -->|publish events| RELAYS

Boot Sequence

sequenceDiagram
    participant User
    participant Page as Browser Didactyl
    participant NLL as nostr-login-lite
    participant Signer as window.nostr
    participant Relays as Nostr Relays

    User->>Page: Open page
    Page->>NLL: NOSTR_LOGIN_LITE.init methods: local, extension, connect, nsigner
    NLL->>NLL: Restore persisted auth or show login modal
    User->>NLL: Enter agent nsec or connect signer
    NLL->>NLL: Install window.nostr facade
    Page->>Signer: window.nostr.getPublicKey()
    Signer-->>Page: agent pubkey hex
    Page->>Relays: Connect to configured relays
    Page->>Relays: Subscribe: kind 10002 own pubkey
    Page->>Relays: Subscribe: kind 10123 own pubkey
    Page->>Relays: Subscribe: kinds 31123,31124 own pubkey
    Page->>Relays: Subscribe: kind 30078 own pubkey d=memory
    Page->>Relays: Subscribe: kind 30078 own pubkey d=llm_config
    Page->>Relays: Subscribe: kind 4 own pubkey p-tag
    Relays-->>Page: EOSE + cached events
    Page->>Page: Build adoption list + skill cache
    Page->>Page: Load LLM config from 30078
    Page->>Page: Register trigger subscriptions
    Page-->>User: Agent ready — listening for DMs and triggers

Browser-Safe Tool Set

The C agent has ~60 tools. The browser agent starts with a focused subset and can grow. Tools are grouped by what the browser can actually do.

Phase 1 — Core Nostr Tools

These are the agent's hands in a browser. All use window.nostr for signing and the WebSocket relay pool for publishing.

Tool C Equivalent Browser Implementation
nostr_post nostr_post Build event → window.nostr.signEvent → publish via relay pool
nostr_query nostr_query Send filter over WebSocket, collect until EOSE
nostr_dm_send nostr_dm_send NIP-04 encrypt via window.nostr.nip04.encrypt → publish kind 4
nostr_dm_send_nip17 nostr_dm_send_nip17 NIP-44 encrypt → gift-wrap kind 13/14 (if signer supports nip44)
nostr_delete nostr_delete Sign kind 5 → publish
nostr_react nostr_react Sign kind 7 → publish
nostr_profile_get nostr_profile_get Query kind 0 → parse metadata
nostr_encrypt nostr_encrypt window.nostr.nip44.encrypt
nostr_decrypt nostr_decrypt window.nostr.nip44.decrypt

Phase 1 — Identity & Context Tools

Tool Browser Implementation
nostr_pubkey / my_pubkey Return cached agent pubkey hex
nostr_npub / my_npub bech32 encode via nostr-tools
agent_identity Build identity context block
nostr_relay_status Query relay pool connection states
tool_list Return browser tool schemas

Phase 1 — Memory & Task Tools

Tool C Equivalent Browser Implementation
memory_save memory_save NIP-44 encrypt → publish kind 30078 d=memory
memory_recall memory_recall Fetch kind 30078 d=memory → decrypt
task_manage task_manage Publish/fetch kind 30078 d=tasks
task_list task_list Fetch kind 30078 d=tasks → format

Phase 1 — Skill Tools

Tool Browser Implementation
skill_create Build kind 31123/31124 → sign → publish
skill_edit Fetch existing → modify → republish
skill_list Query adopted skills from cache
skill_adopt Update kind 10123 → sign → publish
skill_remove Update kind 10123 → sign → publish
skill_search Query kind 31123 by tag/author

Phase 1 — Config & Model Tools

Tool Browser Implementation
model_get Return active LLM config from 30078 or localStorage
model_set Publish kind 30078 d=llm_config
config_store Encrypt + publish kind 30078
config_recall Fetch + decrypt kind 30078
agent_version Return hardcoded browser version string

Phase 2 — Blossom Tools

Tool Browser Implementation
blossom_upload fetch() PUT to Blossom server with signed auth
blossom_download fetch() GET from Blossom server
blossom_head fetch() HEAD
blossom_list fetch() GET list endpoint
blossom_delete fetch() DELETE with signed auth

Phase 2 — Browser-Native Tools

These have no C equivalent — they are browser-only capabilities:

Tool Description
browser_fetch fetch() any URL — replaces local_http_fetch
browser_tab_open Open a URL in a new tab
browser_clipboard Read/write clipboard
browser_local_storage Read/write localStorage key-value pairs

Explicitly Excluded

These tools cannot run in a browser and are intentionally absent:

Tool Reason
local_shell_exec No shell access
local_file_read No filesystem (except via File System Access API, Phase 3)
local_file_write Same
cashu_wallet_* Could be ported later (Cashu is HTTP-based) — Phase 3

Context Assembly

The browser agent replicates Didactyl's two-layer context model from docs/CONTEXT.md and docs/SKILLS.md.

Two-Layer Model

flowchart TD
    TRIGGER[Trigger fires: DM, cron, subscription]
    TRIGGER --> WALK[Walk adoption list 10123]
    WALK --> MATCH{Skill trigger matches?}
    MATCH -- yes --> L1[Add to Layer 1]
    MATCH -- no --> SKIP[Skip]
    L1 --> RESOLVE[Resolve template variables]
    RESOLVE --> VARTYPE{Variable type?}
    VARTYPE -- tool name --> TOOLEXEC[Execute tool, insert result as markdown]
    VARTYPE -- skill d-tag --> SKILLLOOKUP[Insert adopted skill content - Layer 2]
    VARTYPE -- unknown --> EMPTY[Resolve to empty]
    TOOLEXEC --> FORMAT
    SKILLLOOKUP --> FORMAT
    EMPTY --> FORMAT
    FORMAT[Format document: add title, bump headings, join with ---]
    FORMAT --> ROLES[Split into system/user roles]
    ROLES --> LLM[Send to LLM with tool schemas]

Template Variable Resolution

The C agent resolves {{variable}} placeholders in skill templates. The browser agent does the same:

  • {{skill_d_tag}} → look up adopted skill, insert content (layer 2)
  • {{tool_name}} → execute tool, convert JSON result to markdown, insert
  • {{message}} → the incoming trigger payload (DM text, etc.)
  • Unknown → empty string

Heading Bump

Skill content headings are bumped down one level (# → ##, ## → ###) during assembly, matching context_bump_headings() in the C agent.

Role Split

The assembled markdown is split into system/user roles via the same logic as context_roles_split(). The runtime owns role construction — skill authors write plain markdown.


Trigger System

The browser agent supports a subset of Didactyl's trigger types:

Trigger Type Browser Support Implementation
dm Yes Subscribe to kind 4 events with own pubkey as p-tag
nostr-subscription Yes Register WebSocket subscription with skill's filter
cron Yes setInterval / setTimeout with cron expression parser
chain Yes Internal event — fire when another skill completes
webhook No No HTTP server in browser (could use a relay-based webhook relay)

DM Trigger Flow

sequenceDiagram
    participant Admin
    participant Relays as Nostr Relays
    participant Page as Browser Didactyl
    participant LLM as LLM API

    Admin->>Relays: Publish kind 4 DM to agent
    Relays->>Page: WebSocket event notification
    Page->>Page: NIP-04 decrypt
    Page->>Page: Dedup check event ID cache
    Page->>Page: Classify sender tier admin/wot/stranger
    Page->>Page: Build context from triggered skills
    Page->>LLM: Chat completion with tools
    LLM-->>Page: Response or tool_calls
    loop tool call loop
        Page->>Page: Execute tool
        Page->>LLM: Feed tool result back
        LLM-->>Page: Next response
    end
    Page->>Relays: Publish kind 4 DM reply
    Relays->>Admin: Deliver reply

Cron Trigger

Cron expressions are parsed in JS and scheduled via setTimeout. The browser tab must stay open for cron to fire (a Service Worker could keep it alive in a future enhancement).

Sender Tier Classification

The C agent classifies senders as ADMIN / WoT / STRANGER. The browser agent replicates this:

  • ADMIN — pubkey matches configured admin pubkey
  • WoT — pubkey is in admin's kind 3 contact list
  • STRANGER — everyone else (configurable: ignore or canned reply)

LLM Integration

Provider Config

LLM config is read from kind 30078 d:llm_config under the agent's own pubkey, falling back to the shared d:user-settings global_llm namespace (matching the cross-project settings sync from sovereign_browser/plans/cross-project-agent-sync.md).

The config shape:

{
  "provider": "ppq",
  "api_key": "sk-...",
  "model": "claude-haiku-4.5",
  "base_url": "https://api.ppq.ai",
  "max_tokens": 4096,
  "temperature": 0.7
}

LLM Call

Uses fetch() to the OpenAI-compatible /v1/chat/completions endpoint. The existing ai-chat.mjs and ai-ui.mjs modules provide reusable sendAiChatJson() and config normalization functions.

Tool-Call Loop

async function agentLoop(messages, toolsSchema, maxTurns) {
    for (let turn = 0; turn < maxTurns; turn++) {
        const response = await callLLM({ messages, tools: toolsSchema });
        if (response.tool_calls.length === 0) {
            return response.content;  // final answer
        }
        messages.push({ role: 'assistant', tool_calls: response.tool_calls });
        for (const tc of response.tool_calls) {
            const result = await toolsExecute(tc.name, tc.arguments);
            messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
        }
    }
    return null;  // max turns exhausted
}

LLM Fallback Chain

Skill llm tags use the CSS font-stack style fallback chain (anthropic/claude-sonnet, openai/gpt-4o-mini, cheap). The browser agent resolves this against its configured providers, matching the C agent's docs/SKILLS.md behavior.


Nostr Connectivity

Relay Pool

Two options:

  1. NDK Worker — reuse ndk-worker.js from the client project. Provides a Web Worker with relay pool, subscription management, event dedup, and NIP-07 signer bridge. This is the fastest path — it is already battle-tested in ai.html and other client pages.

  2. nostr-tools — lighter weight, direct in-page. Simpler but less infrastructure.

Recommendation: NDK Worker. It already handles relay connection management, reconnection, event dedup, and signer integration. The browser Didactyl page would import init-ndk.mjs for the auth + worker bootstrap, then use the worker's subscribe and publish message types.

Subscriptions on Boot

Subscription Filter Purpose
Relay list kind 10002, own pubkey Discover agent's configured relays
Adoption list kind 10123, own pubkey Load adopted skills
Self skills kinds 31123, 31124, own pubkey Load skill definitions
Memory kind 30078, own pubkey, d=memory Agent long-term memory
Tasks kind 30078, own pubkey, d=tasks Agent task list
LLM config kind 30078, own pubkey, d=llm_config Provider/model config
DMs kind 4, own pubkey as p-tag Admin command channel
Admin context kinds 0, 3, 10002, 1, admin pubkey WoT + admin awareness

Event Dedup

The C agent uses an event-ID cache + FNV-1a fingerprint debounce window. The browser agent uses a Set<string> of seen event IDs with a periodic cleanup (evict entries older than N minutes). NDK also has built-in dedup.


Identity & Signing

nostr-login-lite — Primary Auth Path

The page uses nostr-login-lite for authentication and signing. This is the same login library used across the client project. It provides a NIP-07 compliant window.nostr facade that works with multiple signing methods:

Method How It Works Use Case
local User enters agent nsec; key is AES-GCM encrypted with a session password and stored in localStorage Primary — sign in as the agent with its private key
extension Delegates to a real NIP-07 browser extension (Alby, nos2x, Amber) If the agent key is in an extension
connect NIP-46 remote signer (bunker) Agent key held by a remote signer service
nsigner USB hardware signer via WebUSB/WebSerial Agent key on n_signer hardware device
seedphrase BIP-39 seed phrase → derive nsec Agent key from a 12-word seed
readonly npub only, no signing Inspection mode — read relay data, no actions

The key insight: you sign in as the agent, with the agent's private key. nostr-login-lite installs window.nostr with the full NIP-07 surface:

  • window.nostr.getPublicKey() — returns agent pubkey hex
  • window.nostr.signEvent(event) — signs any Nostr event with the agent key
  • window.nostr.nip04.encrypt(pubkey, plaintext) — NIP-04 encrypt
  • window.nostr.nip04.decrypt(pubkey, ciphertext) — NIP-04 decrypt
  • window.nostr.nip44.encrypt(pubkey, plaintext) — NIP-44 encrypt (if supported)
  • window.nostr.nip44.decrypt(pubkey, ciphertext) — NIP-44 decrypt (if supported)

All tool implementations that need to sign events, encrypt DMs, or decrypt memory route through window.nostr — exactly like the C agent routes through its signer. The agent's nsec never touches the agent code directly; it stays inside nostr-login-lite's encrypted store or the extension/hardware signer.

Initialization

await window.NOSTR_LOGIN_LITE.init({
    persistence: true,           // Remember login across page reloads
    isolateSession: false,       // Share across tabs (agent is one identity)
    relays: ['wss://relay.damus.io', 'wss://nos.lol'],
    methods: {
        extension: true,         // Browser extension if available
        local: true,             // Manual nsec entry — primary for agent
        readonly: true,          // Inspection mode
        seedphrase: true,        // BIP-39 seed
        connect: true,           // NIP-46 remote signer
        nsigner: true,           // USB hardware signer
    },
    floatingTab: {
        enabled: true,
        appearance: { text: 'Agent Login' }
    }
});

After init, nostr-login-lite either restores a persisted session or shows the login modal. Once authenticated, window.nostr is installed and the boot sequence continues.

Admin Identity

The admin pubkey is stored in the agent's kind 30078 d:llm_config or a dedicated config event. On boot, the page fetches this to determine the admin identity for DM classification.


File Layout

The page lives in the client project's www/ directory alongside the existing pages, reusing the shared CSS, NDK worker, and JS modules.

~/lt/client/www/
├── didactyl-agent.html          # NEW — the browser Didactyl page
├── css/
│   └── client.css               # existing shared styles
├── js/
│   ├── init-ndk.mjs             # existing — NDK worker bootstrap + auth
│   ├── ai-ui.mjs                # existing — LLM config + chat completions URL
│   ├── ai-chat.mjs              # existing — sendAiChatJson()
│   ├── didactyl-agent.mjs       # NEW — agent core: boot, trigger dispatch, loop
│   ├── didactyl-context.mjs     # NEW — two-layer context assembly
│   ├── didactyl-tools.mjs       # NEW — browser-safe tool dispatch + schemas
│   ├── didactyl-skills.mjs      # NEW — skill cache, adoption list, trigger matching
│   ├── didactyl-memory.mjs      # NEW — memory/task persistence via kind 30078
│   └── didactyl-triggers.mjs    # NEW — DM, cron, subscription trigger management
├── ndk-worker.js                # existing — relay pool worker
├── nostr-login-lite/            # existing — login library (vendored or built)
└── template.html                # existing — page template (auth, sidenav, theme)

Module Responsibilities

Module Responsibility
didactyl-agent.mjs Boot sequence, agent loop, message dispatch, UI binding
didactyl-context.mjs Two-layer context assembly, heading bump, role split, template variable resolution
didactyl-tools.mjs Tool schema definitions, tool dispatch, browser-safe tool implementations
didactyl-skills.mjs Skill cache, adoption list management, trigger matching
didactyl-memory.mjs Memory save/recall, task management via kind 30078
didactyl-triggers.mjs DM listener, cron scheduler, subscription trigger registration

UI Design

Layout

+------------------------------------------------------------------+
|  DIDACTYL AGENT                                    [dark/light]  |
+------------------------------------------------------------------+
|  Status: Connected to 3 relays · Listening for DMs and triggers  |
+------------------------------------------------------------------+
|                                    |                              |
|  +------------------------------+  | +---------------------------+|
|  | CONVERSATION / LOG            |  | | AGENT STATE              ||
|  |                               |  | |                           ||
|  | [admin] Fix the typo in my    |  | | Pubkey: npub1...         ||
|  |       latest post             |  | | Admin:  npub1...         ||
|  |                               |  | | Relays: 3/3 connected    ||
|  | [agent] I found your post     |  | | Skills: 12 adopted       ||
|  |         note1abc...           |  | | Triggers: 3 active       ||
|  |         The typo is "recieve" |  | |                           ||
|  |         → fixing now...       |  | | --- Trigger Log ---      ||
|  |                               |  | | [cron] readme-monitor    ||
|  | [tool] nostr_query → 1 event  |  | | [dm] admin message       ||
|  | [tool] nostr_post → published |  | | [sub] new follower       ||
|  |                               |  | |                           ||
|  | [admin] Thanks!               |  | | --- Skills ---           ||
|  |                               |  | | [x] personality          ||
|  | [agent] Done!                 |  | | [x] chat                 ||
|  |                               |  | | [x] readme-monitor       ||
|  |                               |  | | [ ] spellcheck           ||
|  +------------------------------+  | +---------------------------+|
|                                    |                              |
|  +------------------------------+  | +---------------------------+|
|  | [Message input...]    [Send]  |  | | [Settings]               ||
|  +------------------------------+  | +---------------------------+|
+------------------------------------------------------------------+

Key UI Elements

  • Conversation log — DM history with the admin, showing tool calls inline
  • Agent state panel — pubkey, admin, relay status, skill count, trigger status
  • Trigger log — recent trigger fires with type and skill name
  • Skills list — adopted skills with toggle (adopts/removes from 10123)
  • Message input — send a DM to the agent directly (for testing)
  • Settings — LLM provider config, relay list, admin pubkey

Reusable Components

The page builds on template.html from the client project, inheriting:

  • Auth flow (NIP-07 login via init-ndk.mjs)
  • Sidenav navigation
  • Theme toggle (dark/light)
  • Relay status indicator
  • Hamburger morphing menu

Implementation Phases

Phase 1 — Minimum Viable Agent

Goal: A browser page that boots from a Nostr identity, loads its config and skills, listens for admin DMs, reasons with an LLM, and replies via DM.

  • Create didactyl-agent.html page shell using template.html as base
  • Integrate nostr-login-lite for auth: init with local/extension/connect/nsigner/seedphrase methods, sign in as agent with its nsec
  • Implement didactyl-agent.mjs boot sequence: nostr-login-lite auth → window.nostr.getPublicKey() → relay connect → subscribe to config/skills/adoption/DMs
  • Implement didactyl-skills.mjs: skill cache, adoption list parsing, trigger matching for dm trigger type
  • Implement didactyl-context.mjs: two-layer context assembly, heading bump, role split, {{message}} and {{skill_d_tag}} variable resolution
  • Implement didactyl-tools.mjs: core Nostr tools (nostr_post, nostr_query, nostr_dm_send, nostr_profile_get, nostr_pubkey, nostr_npub, tool_list), OpenAI-format tool schemas
  • Implement didactyl-memory.mjs: memory_save, memory_recall, task_manage, task_list via kind 30078
  • Implement agent loop: build context → call LLM → execute tools → loop → reply as DM
  • Implement sender tier classification (admin / WoT / stranger)
  • Wire up conversation log UI + agent state panel
  • LLM config from kind 30078 d:llm_config with localStorage fallback

Phase 2 — Full Tool Set + Triggers

Goal: Complete the browser-safe tool set and add cron + subscription triggers.

  • Add remaining Nostr tools: nostr_delete, nostr_react, nostr_dm_send_nip17, nostr_encrypt, nostr_decrypt, nostr_nip05_lookup, nostr_encode, nostr_decode
  • Add skill tools: skill_create, skill_edit, skill_list, skill_adopt, skill_remove, skill_search
  • Add config/model tools: model_get, model_set, config_store, config_recall, agent_version
  • Add Blossom tools: blossom_upload, blossom_download, blossom_head, blossom_list, blossom_delete
  • Add browser-native tools: browser_fetch, browser_tab_open, browser_clipboard, browser_local_storage
  • Implement didactyl-triggers.mjs: cron trigger scheduler, nostr-subscription trigger registration, chain trigger
  • Add trigger log UI
  • Add skills management UI (adopt/remove, create/edit)

Phase 3 — Advanced

Goal: Push the boundaries of what a browser agent can do.

  • Cashu wallet tools (NIP-60) — all HTTP-based, feasible in browser
  • File System Access API — local_file_read / local_file_write for user-selected directories
  • Service Worker — keep cron triggers alive when tab is backgrounded
  • NIP-17 gift-wrap messaging (requires nip44 signer support)
  • Cross-project settings sync — read/write shared d:user-settings kind 30078
  • Broadcast-debounce admin client — fan-out DMs to multiple agents (browser + C), accept first reply (from DECENTRALIZED_DIDACTYL.md)
  • Mobile responsive layout
  • Offline skill caching via IndexedDB

Compatibility with C Didactyl

Shared State

The browser agent and C agent share all state via Nostr events — no direct communication is needed. Both read and write the same event kinds:

State Event Shared
Skills kind 31123/31124 Yes — both publish and consume
Adoption list kind 10123 Yes
Memory kind 30078 d=memory Yes (encrypted, same key needed)
Tasks kind 30078 d=tasks Yes
LLM config kind 30078 d=llm_config Yes
DMs kind 4 Yes
Profile kind 0 Yes

Same Keys, Different Runtimes

If the browser agent and C agent use the same nsec, they share a Nostr identity. This has the same problems described in DECENTRALIZED_DIDACTYL.md: both subscribe to the same DMs, both process, both reply. The broadcast-debounce model applies.

Recommended: different keys. The browser agent is a distinct agent with its own identity. The admin talks to whichever agent is appropriate. Skills and soul can be shared via adoption lists.

Skill Portability

Skills are portable by design. A skill published by the C agent works in the browser agent and vice versa, as long as:

  1. The skill's tools tag only references browser-available tools
  2. The skill doesn't depend on shell-specific capabilities

The llm fallback chain ensures model portability across runtimes.


Security Considerations

Concern Mitigation
nsec in browser memory nostr-login-lite encrypts the nsec with AES-GCM and a session password before storing in localStorage. For maximum security, use nsigner (hardware) or connect (NIP-46 remote) methods so the key never enters the browser.
LLM API key exposure Key is in kind 30078 encrypted event. Browser decrypts to memory. Never logged.
Tool execution safety Browser sandbox limits damage. No shell, no filesystem (Phase 1/2).
DM sender spoofing Verify event signatures before processing (NDK does this). Classify sender tier.
Relay trust Relays are untrusted. Event signatures verified. Encrypted DMs.
CORS for LLM calls OpenAI-compatible APIs typically support CORS. If not, use a proxy.
Blossom auth Signed auth events via window.nostr.signEvent.

Reusable Infrastructure

Asset Location What It Provides
NDK Worker ndk-worker.js Relay pool, subscriptions, event dedup, publish
NDK Init init-ndk.mjs NIP-07 auth, worker bootstrap, mute list
nostr-login-lite nostr-login-lite NIP-07 facade: local nsec, extension, NIP-46, nsigner, seedphrase, readonly
LLM Config ai-ui.mjs Config normalization, chat completions URL, provider merge
LLM Call ai-chat.mjs sendAiChatJson() — fetch-based LLM call
Page Template template.html Auth, sidenav, theme toggle, relay status
Shared CSS client.css Theme variables, layout, components
Skills Demo skills-demo.mjs Skill discovery, template parsing, execution

Open Questions

  1. Should the browser agent use the same relay list as the C agent (from kind 10002), or its own? Same is simpler and more sovereign. Different allows geographic diversity.

  2. NIP-17 gift-wrap support depends on the signer having nip44. Not all NIP-07 extensions support this. Fallback to NIP-04 or skip NIP-17 tools if unavailable.

  3. Cron triggers die when the tab closes. Is this acceptable, or do we need a Service Worker? For a true "always-on" agent, the C binary is still the right choice. The browser agent is for interactive/attended use.

  4. Should we support the broadcast-debounce admin client pattern from DECENTRALIZED_DIDACTYL.md so the admin can fan-out to both browser and C agents? This is a Phase 3 enhancement.