Files

60 KiB

Phase 13: AIUI — Conversational Node Control & Content Surfaces - Research

Researched: 2026-08-03 Domain: LLM tool-calling bridge (Rust), Cashu/Nostr paid-inference integration, cross-repo Vue embed architecture, media/content indexing Confidence: MEDIUM-HIGH — the Rust and neode-ui sides are fully source-verified; the AIUI repo side is fully source-verified (cloned, read directly); Routstr protocol details are CITED from official docs (not independently protocol-tested against a live Routstr node, which does not exist in this environment).

<user_constraints>

User Constraints (from CONTEXT.md)

Locked Decisions

Where the agent loop lives

  • D-01: The agent loop (model call → tool call → result → model) runs node-side in Rust. The archipelago binary owns the loop, the tool registry, and the model key. AIUI becomes a thin chat client. Rationale: the key never reaches the browser; tool authorization sits where session auth already lives; Pine/voice can reuse the same tools later. Reversibility: costly.
  • D-02: One assistant, many front doors. Extend the existing mesh assistant into a shared service: one tool registry, one backend selector, one place keys live. Mesh/LoRa, AIUI chat and (later) Pine voice are callers distinguished by permission scope. The existing peer-facing controls (trusted_only, allowed_contacts, denied_askers) are a per-caller scope mechanism that already exists.
  • D-03: Split by nature. The node-side registry owns everything that reads or changes the node (system, bitcoin, network, wallet, files, media). The existing ContextBroker keeps only what must run in the browser (navigate, open-app, launch-app, theme) and remains the consent surface pushing permissions:update.
  • D-08: Chat history lives node-side in the per-node data dir (/var/lib/archipelago), inheriting the node's backup/factory-reset/LUKS story.

Model backends

  • D-04: Backend chain is local Ollama first, with Claude and Routstr as fallbacks. Node data never leaves the node when a local model is available. Routstr is explicitly in scope at the user's request — an OpenAI-compatible endpoint paid per request in Cashu ecash, providers/models/prices discovered over Nostr.
  • D-05: Routstr spending is authorized by a prepaid budget the user sets. Inference spends silently within the allowance, then stops and asks. The ceiling is hard. Reversibility: reversible.
  • D-07: The local model does get tools, and every write needs confirmation regardless of backend. Backend choice stays a privacy decision, not a safety one.

Authority and sandboxing

  • D-06: Tools are a curated allowlist of hand-written tools — each with its own schema, permission category, and destructive/confirm flag. The model never sees the full RPC surface. No auto-generation from the dispatcher. Reversibility: reversible (adding tools is additive).
  • D-09: First-cut authority is reads within granted categories + app lifecycle (start/stop/restart) + settings writes. Explicitly excluded from chat reach: keys, seeds, wallet spends, federation trust, factory reset. Reversibility: costly.
  • D-10: Tool authority never derives from content. Peer-supplied text enters the context inside explicit untrusted-content delimiters marking it as data, not instructions. Pattern-stripping filters were considered and rejected.
  • D-11: Write confirmations render in neode-ui's trusted chrome, outside the iframe, drawn by the host from the node's own description of the pending action — never by AIUI, never from model-authored text. Uses the Teleport-to-body modal pattern. Reversibility: costly — the load-bearing anti-spoofing property.
  • D-16: All 10 permission categories (apps, system, network, wallet, files, media, search, ai-local, notes, bitcoin) default closed on a fresh node.
  • Hard constraint from Phase 10: the UNAUTHENTICATED_METHODS hard-refuse gates and the loopback/auth boundaries must hold with AIUI on the other side of them. Not to be widened.

Content surfaces

  • D-12: Feed the existing grids from Archy, replacing the LLM-synth source. AIUI's design is kept exactly; what fills FilmGrid, SongGrid, NewsGrid, the detail views changes to real records. Reversibility: reversible — the grids are prop-driven.
  • D-13: Build a real music library — albums, artists, tracks, tag/metadata extraction, an index that stays fresh. Lands as its own wave inside Phase 13, not blocking the rest. Reversibility: one-way — a persisted data model with a migration cost once nodes have indexed libraries.
  • D-14: IndeeHub and peer video are surfaced through the content + paid-unlock subsystem that already exists (invoices, X-Payment-Token, Range streaming). No new payment rail. The cross-node "archipelago content source" is deferred.

Delivery and the two-repo split

  • D-15: AIUI is built and shipped with the frontend, versioned and verified — rsync path kept but made deliberate: AIUI's commit pinned, VITE_BASE_PATH=/aiui/ enforced by the build script, a post-deploy check that fetches a live asset. Making AIUI a signed-catalog app was considered and rejected for this phase.
  • D-17: AIUI keeps its standalone mode; embedded mode delegates to the node. Dev loop stays fast — no node required to work on the UI.
  • D-18: Push access to the AIUI repo is confirmed before planning starts — CONFIRMED by the orchestrator per the phase brief; plan freely against git.tx1138.com/lfg2025/AIUI branch development.

Claude's Discretion

  • What the music library indexes over (own filebrowser Music folder, peer audio, or both), the tag-extraction library, and where the index lives — within D-13's bounds.
  • Streaming/token delivery for chat responses; context-window budgeting over node data.
  • Which specific tools make the first curated allowlist, within D-09's authority ceiling.
  • Routstr provider selection strategy among Nostr-advertised providers.
  • Per-category mapping of the 10 permission categories onto individual tools.

Deferred Ideas (OUT OF SCOPE)

  • Cross-node "archipelago content source" with payments (federation distribution + payments feature; own phase).
  • AIUI Nostr integration polish (carried from Phase 2's deferred list).
  • Reviving ContentPanel.vue and the plugin-renderer path (considered and rejected; dead-code cleanup, not architecture).
  • AIUI's dev-only Vite middleware beyond what D-12 replaces (vite-tmdb, vite-rss, vite-web-search, vite-fs, vite-music-search, vite-dev-chats).
  • Pine voice reusing the tool registry (D-02 makes it possible; wiring voice is not in this phase).
  • Not in scope (from the phase Domain block): cross-node content distribution with payments; wallet spends, seed/key operations, federation trust changes, factory reset as chat-reachable actions; Nostr integration polish; reviving the dead ContentPanel.vue architecture. </user_constraints>

<phase_requirements>

Phase Requirements

ID Description Research Support
AIUI-01 Human-language node control — typed chat request reaches a real node action §1 (gating question, verified), §2 (tool-calling loop), §4 (confirm gate) settle the mechanism; §"Curated Tool Allowlist" gives concrete RPC candidates
AIUI-02 Conversational settings — system settings reachable by conversation, scoped to grants Same tool-registry mechanism as AIUI-01; system.settings.get/system.settings.set are existing RPCs to wrap as tools
AIUI-03 Content surfaces made real — peer files, music, IndeeHub movies, owned/paid content render live §5 (content surfaces) maps ContentItem/content.* RPCs onto AIUI's Film/Song/Podcast prop shapes; §6 (music library) covers the one genuinely new data domain
AIUI-04 Sandboxed by construction, permissioned by the user §4 (confirm gate mechanism) + new §"Same-Origin Sandbox Gap" (a verified architectural finding: today's iframe embed has no hard browser-enforced boundary)
AIUI-05 Delivery and build — AIUI reaches nodes through a real, verifiable update path §7 (delivery) — current deploy scripts, the VITE_BASE_PATH requirement, and the missing live-asset check are all verified from source
AIUI-06 Verified on device, embedded iframe, mobile included §Validation Architecture
</phase_requirements>

Summary

AIUI today is not a dormant blank canvas waiting for wiring — it is an actively working, unauthenticated, key-holding proxy straight to Anthropic, running in production nginx config on every node that has had setup-aiui-server.sh run against it. The canonical image-recipe/configs/nginx-archipelago.conf proxies /aiui/api/claude/ to a standalone Python HTTP server (claude-api-proxy.py, port 3142, its own systemd unit, its own ANTHROPIC_API_KEY env var — a different key than the Rust daemon's data_dir/secrets/claude-api-key) with no session-cookie gate at all ("API key managed by proxy, no session gate needed" — verified in the nginx config comment). This proxy bypasses the entire Rust JSON-RPC dispatcher: no UNAUTHENTICATED_METHODS gate, no CSRF check, no RBAC role.can_access() check, no involvement of session::extract_session_cookie. Anyone who can reach the node's web port can spend the node owner's Claude API budget with zero authentication. This is a pre-existing, currently-live exposure this phase's D-01 (move the agent loop node-side into the authenticated Rust surface) directly closes as a side effect of doing the phase correctly — but it needs to be named explicitly as a finding, because it is more severe than "chat can't act on the node" and is not mentioned in CONTEXT.md.

Both halves of CONTEXT.md's central gating claim are verified true against source: mesh/listener/assist.rs's call_ollama posts to Ollama's /api/generate (not /api/chat) with a bare prompt string and no tools field; call_claude posts to the Anthropic Messages API with a single user message and no tool_use/tools field. Both are single-shot Q&A, no loop. dispatcher.rs registers only mesh.assistant-status/mesh.assistant-configure — grep for "pine. across the entire dispatcher returns nothing; Pine has no RPC surface, and what CONTEXT.md called "the intent→action path Pine already proves" is, on inspection, Home Assistant's own intent_script/Assist framework seeded by package/pine_ha.rs — four read-only hardcoded intents (block height, peer count, sync status, Lightning balance) answered from REST-sensor state, not a Rust-side action-executing loop. HA's Claude conversation agent does get real LLM tool-calling via llm_hass_api: ["assist"], but only over HA's own intents — none of which write to the node. So even Pine's voice path does not yet prove an action-taking loop; it proves Q&A-with-structured-intents at the HA layer. This corrects CONTEXT.md's framing and matters for scoping AIUI-01's "first tool-calling loop in this codebase" honestly.

On the AIUI side (cloned, development branch, read directly): useAI.ts's chat send path calls streamClaude/streamOpenRouter against ${BASE}api/claude/v1/messages / ${BASE}api/openrouter — i.e., exactly the nginx proxy above, or the browser's own vaulted API key. There is no client-side tool-calling either: the "Archy actions" AIUI's system prompt describes (open-app, install-app, read-file, tail-logs, navigate) are informational prose injected into the system prompt by useArchy.ts's buildArchyContext(); only readFile/tailLogs are ever actually invoked by AIUI code, and both are called directly by UI components — never parsed out of a model response. AIUI has zero machinery today for turning an LLM's stated intent into an executed action; everything the model "does" today is either prose or a [[tag:...]] regex match consumed by contentExtraction.ts/useContentPanel.ts to render a content card. This phase must build the tool-calling protocol from scratch on both sides.

A real anti-spoofing confirmation pattern already exists to extend, not invent: contextBroker.ts's install-app handler dispatches a CustomEvent('aiui:install-request') for neode-ui's own UI to render a confirmation, then awaits aiui:install-response with a 60s timeout — this is D-11's mechanism today, just for one action type. It needs a second: aiui-protocol.ts's AIActionType union has no tool-call/confirm member yet.

For Routstr, the phase is not starting from zero on the payment side: crate::swarm::payment::auto_pay_token (used today by streaming.prepare-payment, the swarm content-payment path) already does exactly D-05's job — build a cashuA token for a given price against a set of accepted_mints, hard-capped by a PaymentPolicy::with_budget, degrading to None (never erroring) when unaffordable. Routstr's documented contract (CITED, not independently tested) accepts payment as Authorization: Bearer cashuA... or an X-Cashu header on an OpenAI-compatible POST /v1/chat/completions, and advertises providers via Nostr kind 38421 events — nostr-sdk = "0.44" is already a dependency with a working build_nostr_client (Tor-proxy aware) in nostr_discovery.rs to subscribe from.

For content surfaces, ArchyAppsGrid.vue is confirmed dead (only referenced by dead ContentPanel.vue and its own test). All six of AIUI's "real data" Vite plugins are confirmed configureServer-only or configureServer+configurePreviewServer-only (verified per-file), so none run against the static dist/ a node actually serves. vite-fs.ts's hardcoded /Users/dorian/Projects is further confirmation this was never meant to reach a node. The grids' prop shapes (Film, Song, Podcast, etc. — all display-oriented with posterUrl/coverUrl/sources[]) do not match Archy's ContentItem (id, filename, mime_type, access, availability) — an adapter layer is required, not a straight pass-through.

Primary recommendation: Build one new node-side "assistant" service module (extending, not replacing, mesh/listener/assist.rs's backend-calling code) that owns a hand-written tool registry, a multi-turn loop per backend, and new RPC methods (assistant.chat, assistant.confirm-tool, assistant.list-tools, assistant.history) reached only via neode-ui's contextBroker.ts (new chat:request/chat:response/tool:confirm-request postMessage types) — never by AIUI fetching the RPC endpoint directly, even though nothing currently stops it (see the Same-Origin Sandbox Gap finding). Ship content surfaces as a straight content.* → grid adapter first (D-12, no new backend work beyond what exists), then land the music library (D-13) as its own wave using lofty for tag extraction. Treat the currently-live Claude proxy exposure and the iframe's lack of a hard sandbox boundary as findings the plan must explicitly decide how to handle (fix, accept-with-mitigation, or defer with a named risk) rather than silently working around.

Architectural Responsibility Map

Capability Primary Tier Secondary Tier Rationale
Agent loop (model call → tool call → result) API/Backend (Rust daemon) D-01: key + tool authority must stay server-side
Tool registry + permission scoping API/Backend (Rust daemon) D-06/D-09: curated allowlist, RBAC-adjacent, must not be derivable from the browser
Chat transport (AIUI ↔ Archy) Browser/Client (postMessage bridge) API/Backend (RPC over the neode-ui session) AIUI has no session cookie path of its own by design; neode-ui's contextBroker.ts is the only channel today, and D-03 keeps it that way
Write-confirmation UI Frontend Server / Browser (neode-ui trusted chrome) D-11: must render outside the iframe, Teleport-to-body, drawn from node-authored text
Browser-only actions (navigate, open-app, theme) Browser/Client (ContextBroker) D-03: nothing server-side can perform a client-side navigation
Routstr payment (Cashu token build) API/Backend (Rust daemon, swarm::payment) Wallet/mint state is server-side; reuses existing auto_pay_token
Routstr provider discovery (Nostr) API/Backend (Rust daemon, nostr-sdk) Relay connections should route through the node's existing Tor-proxy-aware Nostr client, not the browser
Content surfaces (peer files, IndeeHub, paid content) API/Backend (content_server.rs, content.* RPCs) Browser/Client (AIUI grids, prop-adapted) Data ownership and access control (AccessControl::Paid) must stay server-enforced; AIUI only renders
Music library index Database/Storage (/var/lib/archipelago) + API/Backend (indexer) Browser/Client (SongGrid consumer) D-13: a persisted, migration-sensitive data model — indexing must not run in the browser
Media playback (Range streaming) API/Backend (existing /content/<id>, /api/raw, peer proxy) Browser/Client (GlobalAudioPlayer) Already solved; grids need data, not a new transport
AIUI static delivery CDN/Static (nginx /aiui/ location, frontend rsync) D-15: built artifact, not a live service

Standard Stack

Core

Library Version Purpose Why Standard
reqwest 0.11 (already in archipelago/Cargo.toml, rustls-tls+socks+json+stream features) HTTP client for Ollama/Claude/Routstr calls Already the codebase's only HTTP client; socks feature already present for Tor-proxied calls
serde_json 1.0 (in-tree) Tool-call schema construction, RPC params Already universal in this codebase
nostr-sdk 0.44 (in-tree, nip04+nip44 features) Routstr provider discovery (kind 38421 subscribe) Already a dependency with a working Tor-aware client builder (nostr_discovery.rs::build_nostr_client) — no new crate needed
tokio 1, full features (in-tree) Async runtime for the multi-turn tool loop Already universal

Supporting (new, for the music library — D-13)

Library Version Purpose When to Use
lofty [ASSUMED — training-knowledge recommendation, not yet added to Cargo.toml; registry existence confirmed] 0.24 (crates.io max_version, VERIFIED: crates.io API, 808K downloads, repo github.com/Serial-ATA/lofty-rs) Read ID3/FLAC/M4A/OGG/WAV/APE tag metadata (title/artist/album/track/duration) in one unified API Primary recommendation for D-13's tag extraction — broad multi-format support in one crate, avoids needing a separate parser per container format

Alternatives Considered

Instead of Could Use Tradeoff
lofty (metadata-only) symphonia (0.6, VERIFIED: crates.io, 9.4M downloads, github.com/pdeljanov/Symphonia) Symphonia is a full audio decoder (needed for playback/transcoding, not tagging) — much heavier dependency surface for a job that's purely "read tags." Not needed here since playback already goes through the existing Range-streaming path, not server-side decode.
lofty (multi-format) id3 (1.17.1, VERIFIED: crates.io, 11.2M downloads, codeberg.org/polyfloyd/rust-id3) ID3-only (MP3). Higher download count reflects broad MP3-tagging use elsewhere, not superiority for a library that must also cover FLAC/M4A/OGG.
Curated hand-written tool allowlist (D-06, locked) Auto-generate tool schemas from dispatcher.rs's method table Explicitly rejected by D-06 — the model must never see the full RPC surface; every capability must be a deliberate decision

Installation:

# cargo add is run from core/ per CLAUDE.md
cd core && cargo add lofty --package archipelago

Version verification: lofty 0.24.0, symphonia 0.6.0, id3 1.17.1 confirmed live via the crates.io API (crates.io/api/v1/crates/<name>) on 2026-08-03 — VERIFIED: crates.io registry, not merely a training-data guess. reqwest/serde_json/nostr-sdk/tokio versions read directly from core/archipelago/Cargo.tomlVERIFIED: in-tree Cargo.toml.

Package Legitimacy Audit

Package Registry Age Downloads Source Repo Verdict Disposition
lofty crates.io Long-running project (Serial-ATA/lofty-rs, active) 808,246 total github.com/Serial-ATA/lofty-rs Not run through gsd-tools query package-legitimacy check in this session (tool unavailable in this environment) — manually checked: real GitHub org, active repo, substantial download count, no suspicious signals found [ASSUMED — recommend a checkpoint:human-verifybeforecargo add]
symphonia crates.io Long-running (pdeljanov/Symphonia) 9,452,628 total github.com/pdeljanov/Symphonia Same manual-check basis — not needed for this phase's scope (tagging only), listed for completeness Not adopted — informational only
id3 crates.io Long-running (rust-id3) 11,202,064 total codeberg.org/polyfloyd/rust-id3 Same manual-check basis Not adopted — informational only

Packages removed due to [SLOP] verdict: none. Packages flagged as suspicious [SUS]: none by manual inspection, but lofty was not run through the automated package-legitimacy check seam (tool unavailable in this research session) — the plan must gate its cargo add behind a checkpoint:human-verify per the package-legitimacy protocol's own fallback rule for [ASSUMED] packages.

Architecture Patterns

System Architecture Diagram

                     Browser (neode-ui page, authenticated session)
   ┌─────────────────────────────────────────────────────────────────────┐
   │  Chat.vue                                                            │
   │  ┌───────────────────────────────┐   postMessage (same-origin,       │
   │  │  <iframe src="/aiui/...">     │◄──origin-checked by broker)──┐    │
   │  │   AIUI (thin chat client)     │                              │    │
   │  │   - renders chat UI            │──chat:request (userText)───►│    │
   │  │   - NO model key, NO RPC       │◄─chat:response (token/done)─┤    │
   │  │     session of its own         │                              │    │
   │  │   - content grids (prop-fed)   │◄─context:response (films,   │    │
   │  │                                │   songs, ...)────────────────┤    │
   │  └───────────────────────────────┘                              │    │
   │                                                                   ▼    │
   │  ContextBroker (contextBroker.ts) ── rpcClient.call() ── uses the    │
   │  page's OWN session cookie + CSRF token (same auth as every other    │
   │  neode-ui RPC call)                                                   │
   │           │                                                           │
   │           │  new: assistant.chat / assistant.confirm-tool /           │
   │           │  content.* / streaming.* RPCs (HTTP POST, session-gated)  │
   └───────────┼───────────────────────────────────────────────────────────┘
               ▼
   ┌───────────────────────────────────────────────────────────────────────┐
   │  archipelago daemon (Rust)                                            │
   │                                                                        │
   │  api::rpc::dispatcher — session + CSRF + RBAC gate (mod.rs:264-330)   │
   │       │                                                                │
   │       ▼                                                                │
   │  NEW: assistant service (extends mesh/listener/assist.rs's backend    │
   │  callers) — owns:                                                     │
   │    - tool registry (D-06 curated allowlist, permission-tagged)        │
   │    - multi-turn loop per backend (Ollama /api/chat tools,             │
   │      Claude Messages API tool_use, Routstr OpenAI-shape)              │
   │    - pending-confirmation queue (D-11: node authors the confirm text) │
   │    - chat history persisted under data_dir (D-08)                    │
   │       │                                          │                    │
   │       ▼ tool call, permission-checked            ▼ pending write      │
   │  existing RPC handlers (system.*, package.*,   → confirm:request      │
   │  container-*, bitcoin.*, content.*, mesh.*)       pushed to broker →  │
   │  — the SAME handlers every other authenticated    neode-ui trusted    │
   │  caller uses, no new "AI-only" backdoor            chrome modal        │
   │                                                                        │
   │  Backend selection: Ollama (local, free) → Claude (secrets/claude-    │
   │  api-key) → Routstr (Nostr-discovered provider + Cashu budget via     │
   │  swarm::payment::auto_pay_token, D-05 hard cap)                       │
   └───────────────────────────────────────────────────────────────────────┘
core/archipelago/src/
├── assistant/                    # NEW — the D-02 shared service
│   ├── mod.rs                    # public API: chat(), confirm_tool(), list_tools()
│   ├── tools.rs                  # D-06 curated tool registry + schemas
│   ├── backends/
│   │   ├── ollama.rs             # /api/chat with tools[] (extends assist.rs::call_ollama)
│   │   ├── claude.rs             # Messages API with tools[] / tool_use blocks
│   │   └── routstr.rs            # OpenAI-shape POST + Cashu payment attach
│   ├── loop_.rs                  # multi-turn tool-call loop, backend-agnostic
│   ├── confirm.rs                # D-11 pending-confirmation queue
│   └── history.rs                # D-08 node-side chat persistence
├── api/rpc/
│   └── assistant_chat.rs         # NEW RPC handlers: assistant.chat, .confirm-tool, etc.
├── music/                        # NEW — D-13 music library (own wave)
│   ├── index.rs                  # on-disk index format + freshness
│   ├── tags.rs                   # lofty-based extraction
│   └── mod.rs
neode-ui/src/
├── services/contextBroker.ts     # EXTENDED — new chat:*, tool:confirm-* message types
├── types/aiui-protocol.ts        # EXTENDED — new AIUIRequest/ArchyResponse variants
└── components/                   # NEW — trusted-chrome confirm modal (Teleport to body)

Pattern 1: Tool-call confirmation via node-authored text (D-11)

What: The Rust assistant, not AIUI and not the model's raw text, writes the human-readable description of a pending destructive action. That description is pushed to neode-ui via a new postMessage type; neode-ui renders it in a Teleport-to-body modal outside the iframe; the user's yes/no is sent back over the same authenticated RPC channel (not postMessage) so the iframe cannot forge it.

When to use: Every tool call where destructive: true or confirm: true in the D-06 tool schema — which per D-07 is every write, regardless of backend.

Example (extends the existing install-app pattern, contextBroker.ts):

// Source: existing pattern at neode-ui/src/services/contextBroker.ts:140-196
// (install-app confirm flow — the model to extend for tool-call confirms)
window.dispatchEvent(new CustomEvent('aiui:install-request', {
  detail: { requestId: id, appId, marketplaceUrl, version },
}))
const responseHandler = (e: Event) => {
  const detail = (e as CustomEvent).detail as { requestId: string; confirmed: boolean }
  if (detail.requestId !== id) return
  window.removeEventListener('aiui:install-response', responseHandler)
  // ... proceed or decline
}
window.addEventListener('aiui:install-response', responseHandler)
setTimeout(() => window.removeEventListener('aiui:install-response', responseHandler), 60000)

The new tool-confirm flow should NOT reuse aiui:install-request (that event is install-specific); it needs its own aiui:tool-confirm-request/response pair, driven by RPC-fetched (not postMessage-fetched) pending-action text so the iframe cannot inject the description.

Pattern 2: Backend-agnostic tool-call loop shape

What: Ollama's /api/chat (not /api/generate, which call_ollama uses today) accepts a tools array of {type: "function", function: {name, description, parameters}} and returns message.tool_calls. Anthropic's Messages API accepts tools: [{name, description, input_schema}] and returns content blocks of type: "tool_use"; the loop must send a follow-up tool_result content block keyed by tool_use_id. Both require re-invoking the backend after executing the tool, i.e. a real loop rather than the single call_ollama/call_claude request-response used by mesh assist today. When to use: All three backends (Ollama, Claude, Routstr — Routstr is OpenAI-compatible, so its tool-calling shape matches OpenAI's tools/tool_calls, distinct from both Ollama's and Anthropic's shapes — three distinct wire formats to normalize). Note: assist.rs's OLLAMA_TIMEOUT (60s) and MAX_REPLY_CHARS/chunking constants are mesh-airtime-specific and should NOT be reused as-is for the AIUI path, which has no radio bandwidth constraint — the new assistant module needs its own timeout/streaming constants.

Anti-Patterns to Avoid

  • Auto-generating tool schemas from dispatcher.rs's method table: explicitly rejected by D-06. Every tool must be a hand-written, reviewed decision — this is the only way "the model never sees the full RPC surface" stays true rather than becoming an implementation detail nobody re-checks.
  • AIUI fetching /rpc (or any authenticated endpoint) directly: nothing in the current CSP or iframe attributes technically prevents this (see "Same-Origin Sandbox Gap" below) — but doing so would make the browser-side ContextBroker/aiui-protocol.ts sandbox purely decorative. All new capability must be added as new postMessage message types, never as a new same-origin fetch from AIUI's own code.
  • Reusing data_dir/secrets/claude-api-key as the ONLY key ledger while the claude-api-proxy.py/port-3142 path with its separate ANTHROPIC_API_KEY env var still exists: two live Claude credential paths with different auth postures is itself a landmine (see below) — the plan must decide to retire, consolidate, or explicitly gate the legacy proxy, not silently leave both running.

Don't Hand-Roll

Problem Don't Build Use Instead Why
Cashu token construction for Routstr payment A new BDHKE/Cashu wallet client crate::wallet::ecash + crate::swarm::payment::auto_pay_token (already in-tree, already budget-capped, already degrades to None on any failure) Exact fit for D-05's "prepaid budget, silent spend, hard stop" requirement — already tested (over_budget_declines_without_touching_wallet, zero_budget_is_origin_only)
Nostr provider discovery (Routstr kind 38421) A raw WebSocket relay client nostr-sdk = "0.44" + nostr_discovery.rs::build_nostr_client (Tor-proxy aware) Already a dependency, already has the Tor-routing pattern this codebase requires for all Nostr traffic
Audio/video tag extraction for the music library A hand-rolled ID3/FLAC/MP4 parser lofty (D-13) Multi-format tag parsing is a well-solved, edge-case-heavy problem (ID3v1 vs v2.2/2.3/2.4, FLAC Vorbis comments, MP4 atoms) — not worth re-implementing
Confirmation UI anti-spoofing A new "trust the iframe's postMessage payload" confirm dialog The existing Teleport-to-body / outside-iframe pattern (D-11), extending aiui:install-request's shape The codebase already has one correct instance of this pattern; a second bespoke one risks diverging in a security-relevant way

Key insight: almost every primitive D-04/D-05/D-13 need already exists somewhere in this codebase in a slightly different shape (mesh assist's backend calls, swarm's payment auto-pay, the install-app confirm flow, the FileBrowser scoped-token pattern). The phase's real net-new work is a tool-calling loop and a grid-data adapter — not new payment/discovery/confirmation primitives.

Common Pitfalls

Pitfall 1: Treating the live claude-api-proxy.py (port 3142) as dormant or as "the Claude assistant"

What goes wrong: A plan that assumes "AIUI's Claude chat isn't wired to anything yet" will miss that on any node where setup-aiui-server.sh has run, /aiui/api/claude/ is a working, unauthenticated passthrough to api.anthropic.com using a node-owner-funded key, entirely bypassing the Rust RPC auth stack. This is verified in image-recipe/configs/nginx-archipelago.conf:49-60 and scripts/deploy-to-target.sh:875-940 (the embedded claude-api-proxy.py). Why it happens: The proxy was built as a pragmatic stopgap to get AIUI's chat "working" during the demo/UI-design phase (Phase 2), predating any of the D-01..D-11 security decisions this phase makes. How to avoid: The plan must explicitly decide what happens to this proxy: (a) delete it and the nginx location block once assistant.chat (D-01) exists, (b) gate it behind session auth as an interim step, or (c) something else — but it cannot be silently left running alongside the new authenticated path, or the phase ships a second, worse, unauthenticated door into the same capability it just spent effort locking down. Warning signs: Any verification step that only tests the new assistant.chat RPC's auth and never checks whether /aiui/api/claude/ is still reachable unauthenticated is incomplete.

Pitfall 2: Assuming the iframe boundary is a hard sandbox

What goes wrong: AIUI-04 ("sandboxed by construction") is easy to read as "the browser enforces this." Verified from source: the AIUI iframe (Chat.vue:34-42) has no sandbox attribute, is served same-origin (/aiui/, confirmed via aiuiUrl computed and the nginx location /aiui/ block sharing the same server block as neode-ui), and the site's CSP (connect-src 'self' ws: wss: http://$host:* https:) does not restrict same-origin fetches. This means AIUI's own JavaScript, running in the user's authenticated session, is not browser-prevented from calling /rpc directly with the ambient session cookie — the entire "AIUI never gets an RPC session" property is a code-discipline convention (AIUI's code simply doesn't do this today), not an enforced boundary. Why it happens: The embed was built for a same-origin production deploy (nginx path-based routing) specifically so cookies/theming could flow naturally — origin isolation was never a design goal until this phase's threat model made it one. How to avoid: The plan needs to explicitly decide the sandbox's actual mechanism: a sandbox iframe attribute (careful — allow-scripts allow-same-origin together is a well-known escape pattern and must NOT both be set unless there is a compensating origin split), a stricter connect-src CSP scoped only to the /aiui/ response (e.g. disallow connect-src to /rpc from that document), or accepting the convention-based boundary explicitly as a residual risk with compensating controls (e.g. server-side rate limiting / anomaly detection on assistant.chat regardless of caller). Silence on this in the plan is itself a gap. Warning signs: A plan that says "AIUI can't reach the RPC surface" without naming the specific enforcement mechanism.

Pitfall 3: Conflating Pine's "intent→action path" with a working action-executing loop

What goes wrong: Scoping AIUI-01 as "expose what Pine already does for voice" undersells the actual work: Pine's HA-side intents (package/pine_ha.rs) are four hardcoded, read-only Q&A intents (block height/peers/sync/balance) resolved from REST-sensor state, not a general tool-calling framework, and they never write to the node. Why it happens: CONTEXT.md's phrasing ("the Pine stack already proves the intent→action path exists for voice") reads as if a generalized action framework exists; verification shows only Q&A exists anywhere in this codebase today (mesh assist AND Pine/HA). How to avoid: Scope AIUI-01's tool-calling loop as genuinely new engineering (the first action-executing agent loop in this codebase), not as "extending an existing action mechanism." The reusable parts are the backend-calling code shape (call_ollama/call_claude) and the permission-gating pattern (is_sender_allowed), not an existing loop. Warning signs: A plan task that says "wire AIUI into the existing Pine action framework" — there isn't one to wire into.

Pitfall 4: Assuming AIUI's grid components can consume ContentItem directly

What goes wrong: content_server.rs::ContentItem (id, filename, mime_type, size_bytes, description, access, availability, added_at) has no overlap in shape with AIUI's Film/Song/Podcast types (posterUrl, coverUrl, sources: FilmSource[] with type: 'plex'|'nextcloud'|..., genres, runtime, director, etc.). A plan that treats this as "just point the grid at the RPC" will produce broken/empty cards. Why it happens: AIUI's types were designed for a rich third-party metadata catalog (TMDB-style); Archy's content model is a generic file-sharing record with access control. How to avoid: Build an explicit adapter layer (Rust RPC response shape → AIUI prop shape, or a thin mapping function on the AIUI side) as its own task, with test fixtures pinning the mapping (e.g., what sources[].type value represents "this node's own file" vs "a peer's file" vs "IndeeHub"). Warning signs: A plan that has no explicit "map ContentItem → Film/Song" task.

Pitfall 5: The filebrowser-client.ts JWT-in-URL pattern being copied for the new tool/content RPCs

What goes wrong: filebrowser-client.ts::streamUrl() embeds a JWT in the query string (?auth=${token}), justified as "short-lived JWT so exposure in URL is acceptable" — but CONTEXT.md flags this as "the known leak to resolve rather than propagate." A new content-streaming path built by copying this pattern propagates the same leak (URLs land in browser history, server access logs, Referer headers). Why it happens: It's the path of least resistance for <audio>/<video> src attributes, which cannot set custom headers. How to avoid: For any new streaming URL construction in this phase (peer content via AIUI, paid content), prefer the existing fetchBlobUrl()-style header-auth pattern where the consumer can set headers, and where a <source>/<video> element is unavoidable, scope the token tightly (single-resource, single-use) rather than reusing the general FileBrowser session token. Warning signs: Any new ...&auth=${token} or ...&token=${token} string concatenation for a long-lived credential.

Code Examples

The Ollama single-shot call being replaced (what NOT to build on top of as-is)

// Source: core/archipelago/src/mesh/listener/assist.rs:429-451 (VERIFIED, read in full)
async fn call_ollama(model: &str, prompt: &str) -> anyhow::Result<String> {
    let client = reqwest::Client::builder().timeout(OLLAMA_TIMEOUT).build()?;
    let body = serde_json::json!({
        "model": model,
        "prompt": prompt,
        "stream": false,
    });
    let resp = client.post(OLLAMA_URL).json(&body).send().await?;
    // ... no `tools` field, no multi-turn loop — /api/generate, not /api/chat
}

The existing budget-capped Cashu payment primitive (reusable for Routstr, D-05)

// Source: core/archipelago/src/swarm/payment.rs:77-101 (VERIFIED, read in full)
pub async fn auto_pay_token(
    data_dir: &Path,
    policy: &PaymentPolicy,       // budget_sats + max_fee_sats
    accepted_mints: &[String],
    price_sats: u64,
) -> Result<Option<String>> {
    if !policy.affords(price_sats) { return Ok(None); }  // hard cap, D-05
    match ecash::build_payment_token(data_dir, accepted_mints, price_sats, policy.max_fee_sats).await {
        Ok(token) => Ok(Some(token)),
        Err(e) => Ok(None), // never errors on a wallet/mint problem — origin always wins
    }
}

The nginx block that must be reconciled with D-01 (the currently-live unauthenticated proxy)

# Source: image-recipe/configs/nginx-archipelago.conf:49-60 (VERIFIED, canonical production config)
location /aiui/api/claude/ {
    proxy_pass http://127.0.0.1:3142/;   # claude-api-proxy.py, own ANTHROPIC_API_KEY, no auth check
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffering off;
    proxy_cache off;
}

Routstr chat-completions call shape (CITED: docs.routstr.com, not independently tested)

POST https://api.routstr.com/v1/chat/completions
Authorization: Bearer cashuAeyJ0...     (or: X-Cashu: cashuAeyJ0...)
Content-Type: application/json

{"model":"gpt-4","messages":[{"role":"user","content":"..."}],"stream":false}

Discovery: Nostr kind 38421, tags including ["d","routstr-provider"], content carrying endpoints (http/onion), models, pricing. Default relays cited in docs: wss://relay.damus.io, wss://relay.nostr.band, wss://nos.lol[CITED: docs.routstr.com — MEDIUM confidence, not cross-verified against a second source or a live provider event].

State of the Art

Old Approach Current Approach When Changed Impact
Mesh assist: /api/generate, single prompt, no tools This phase: /api/chat with tools[], multi-turn loop This phase (net-new) Ollama backend needs its own request builder distinct from call_ollama
AIUI chat → nginx proxy → Anthropic directly (client-vaulted or proxy-baked key) AIUI chat → postMessage → neode-ui RPC (session-authed) → Rust assistant service → model This phase (D-01) The nginx /aiui/api/claude/, /aiui/api/openrouter/, /aiui/api/ollama/ proxy blocks become legacy/dead once migrated — must be explicitly retired or gated, not left dangling
AIUI content grids fed by regex-parsed model prose (updatePanelFromText) against fixture catalogs baked into the system prompt Grids fed by real content.* RPC data via an adapter This phase (D-12) System prompt shrinks (no more filmContext/songContext/podcastContext fixture dumps for Archy-sourced content — though AIUI's general recommendation feature for content NOT on this node may still want some fixture/tag mechanism, that's a design choice for the plan)

Deprecated/outdated:

  • claude-api-proxy.py (port 3142) and its nginx blocks — once assistant.chat exists, this is a strictly worse, unauthenticated duplicate of the same capability and should not coexist indefinitely.
  • AIUI's vite-fs/vite-tmdb/vite-rss/vite-web-search/vite-music-search/vite-dev-chats middleware as a "real data" story for production — confirmed dev-only; only the D-12-covered slice (Archy content) gets a production answer this phase, the rest stays explicitly deferred per CONTEXT.md.

Assumptions Log

# Claim Section Risk if Wrong
A1 lofty is the right tag-extraction crate for D-13 Standard Stack / Don't Hand-Roll Low — swappable later since it's an internal indexer implementation detail behind the music index's own schema; D-13's own note already flags the entity model (not the crate) as the one-way cost
A2 Routstr's exact wire contract (headers, kind 38421 tag names, default relay list) as CITED from docs.routstr.com Code Examples / State of the Art Medium — if the docs site's content has drifted from the actual routstr-core implementation, the Rust client's header names or the Nostr filter subscription could be wrong on first integration attempt; the plan should budget a task to test against a real Routstr provider event before hand-writing the full client, not just against the docs
A3 No gsd-tools package-legitimacy check was run against lofty/symphonia/id3 (tool unavailable in this research session) — legitimacy assessed by manual crates.io inspection only Package Legitimacy Audit Low-Medium — crates.io download counts and repo links were checked directly via the crates.io API, which is the same signal the automated check would use, but the automated seam's full heuristic set was not run
A4 The claude-api-proxy.py/port-3142 path is reachable without authentication on every node that has run setup-aiui-server.sh, not just the specific nodes checked in this session Common Pitfalls / Summary High if wrong in the safe direction (i.e. if some nodes actually do have it gated some other way this research didn't find) — but the canonical image-recipe/configs/nginx-archipelago.conf (the ISO-shipped, non-manual-script config) has no gate either, so this is the default state for any node built from the current image recipe, which is HIGH confidence, not just this-node-specific

If this table is empty: N/A — see entries above; none are structural blockers, but A2 and A4 both warrant explicit plan tasks (a live-Routstr-provider smoke test; an audit of which fleet nodes currently expose the unauthenticated Claude proxy).

Open Questions

  1. What happens to the port-3142 claude-api-proxy.py and its nginx blocks?

    • What we know: it is live, unauthenticated, and holds its own API key separate from secrets/claude-api-key.
    • What's unclear: whether any currently-deployed node's users rely on it continuing to work exactly as-is during the migration window, and whether deleting it is this phase's job or a follow-up.
    • Recommendation: the plan should make an explicit decision (delete-and-replace vs. gate-then-deprecate) with a checkpoint, not leave it implicit.
  2. What is the actual enforcement mechanism for AIUI-04's "sandboxed by construction"?

    • What we know: today there is no sandbox iframe attribute, no origin split, and a permissive same-origin CSP.
    • What's unclear: whether the plan should add a sandbox attribute (and handle the microphone permission + allow-same-origin interaction correctly), tighten CSP for the /aiui/ response specifically, or explicitly accept the convention-based boundary with compensating server-side controls.
    • Recommendation: name this as its own task with a concrete decision, since D-11's whole premise ("the iframe cannot spoof... the confirmation dialog") assumes the postMessage channel is the only channel — which is true only by convention today.
  3. Is Routstr's documented protocol (kind 38421, header names) accurate against the live routstr-core/routstrd implementation?

    • What we know: docs.routstr.com describes the shape (CITED, medium confidence).
    • What's unclear: whether a live provider on the default relays actually publishes exactly this event shape today, given Routstr is a young, actively-developed project.
    • Recommendation: budget an early spike task that subscribes to the real relays and inspects at least one live kind-38421 event before writing the parser against the docs alone.
  4. Does the RBAC role.can_access(&method) check apply to whatever new assistant.* RPC methods this phase adds, and should it?

    • What we know: every existing authenticated RPC method goes through user.role.can_access(&rpc_req.method) (api/rpc/mod.rs:296-307).
    • What's unclear: whether the AI permission-category model (10 categories, D-16) should be layered on top of, integrated with, or kept fully separate from the existing role/RBAC system.
    • Recommendation: the planner should decide explicitly rather than let this fall out implicitly from wherever the new RPC methods happen to get registered.

Environment Availability

Dependency Required By Available Version Fallback
cargo / Rust toolchain All Rust-side work 1.95.0 (VERIFIED, cargo --version)
~/Projects/AIUI clone, development branch All AIUI-side work HEAD 6e8b96d, clean working tree (VERIFIED, git status/git log)
Push access to git.tx1138.com/lfg2025/AIUI Landing AIUI-side commits (D-18) Per phase brief: CONFIRMED by orchestrator
Ollama (local LLM) D-04 primary backend Not probed on a live node in this research session (no node reachable from this environment) Detection code (detect_ollama(), mesh/rpc/mesh/assistant.rs:164-192) already exists and reports ollama_detected/models — reuse rather than re-probe
A live Routstr provider (for protocol verification) Open Question 3 ✗ (not reachable from this research environment) Docs-only (CITED) until a spike task runs against real relays
Node's own data_dir/secrets/claude-api-key Existing mesh assist Claude backend Not probed (no live node in this environment) Mesh assist code already handles its absence gracefully (call_claude returns an error, caught by run_assist)

Missing dependencies with no fallback: none — every dependency either has an existing detection/fallback path in-tree or is deferred to a named spike task (Open Question 3).

Validation Architecture

Test Framework

Property Value
Framework (Rust) cargo test (in-tree unit/integration tests, e.g. swarm/payment.rs's #[tokio::test] suite, pine_ha.rs's #[test] suite)
Framework (neode-ui) Vitest 3.1 (neode-ui/package.json"test": "vitest run"), existing contextBroker.test.ts/chatAiuiEmbed.test.ts to keep green
Framework (AIUI) Not yet inspected in this research pass — packages/app/src/__tests__/ and composables/__tests__/ exist (contentExtraction.test.ts, useAI.test.ts) — planner should confirm AIUI's own package.json test command before relying on it
Config file core/archipelago/Cargo.toml (Rust); neode-ui/vitest.config.ts (frontend)
Quick run command cd core && cargo test --package archipelago assistant:: (once the module exists); cd neode-ui && npx vitest run src/services/__tests__/contextBroker.test.ts
Full suite command cd core && cargo test (release-profile per CARGO_INCREMENTAL=0 if lld errors appear, per CLAUDE.md); cd neode-ui && npm run test

Phase Requirements → Test Map

Req ID Behavior Test Type Automated Command File Exists?
AIUI-01 A typed chat request executes a real read-only tool (e.g. "how much space is left" → system.disk-status) and returns the real result integration (Rust) cargo test assistant::tests::disk_status_tool_executes Wave 0 — module doesn't exist yet
AIUI-01 A typed chat request for a write action (e.g. "restart bitcoin") produces a pending confirmation, NOT an executed action, until confirmed integration (Rust) + component (Vue) cargo test assistant::tests::destructive_tool_requires_confirm; npx vitest run src/services/__tests__/toolConfirm.test.ts Wave 0
AIUI-01 An unauthenticated caller cannot reach any new assistant.* RPC method integration (Rust) cargo test rpc::middleware::tests::assistant_methods_require_session Wave 0
AIUI-02 A conversational settings change (system.settings.set via tool call) is scoped to a granted permission category and refused when not granted unit (Rust) cargo test assistant::tools::tests::settings_tool_respects_category_grant Wave 0
AIUI-03 content.* RPC data renders correctly in FilmGrid/SongGrid via the new adapter (regression-pins the mapping named in Pitfall 4) unit (Vue/TS) npx vitest run src/composables/__tests__/archyContentAdapter.test.ts Wave 0
AIUI-04 AIUI's own code cannot reach /rpc or any authenticated endpoint directly (whatever mechanism Open Question 2 resolves to) integration/manual (per chosen mechanism) Depends on Open Question 2's resolution Wave 0 — mechanism undecided
AIUI-05 AIUI's build enforces VITE_BASE_PATH=/aiui/ and a post-deploy check fetches a live asset by hash shell/CI scripts/build-aiui.sh (new) exits non-zero if VITE_BASE_PATH unset; post-deploy curl check on a known asset path Wave 0 — no such script exists today
AIUI-06 Manual UAT: embedded iframe on archi-dev-box, desktop + mobile viewport manual N/A — real-device verification, not automatable

Sampling Rate

  • Per task commit: the relevant quick-run command for the touched module (Rust assistant:: tests, or the specific Vitest file).
  • Per wave merge: full cargo test + full npm run test (neode-ui) + AIUI's own test command (to be confirmed).
  • Phase gate: full suite green, plus the AIUI-06 manual on-device pass on archi-dev-box (desktop and mobile), before /gsd-verify-work.

Wave 0 Gaps

  • core/archipelago/src/assistant/mod.rs + its #[cfg(test)] module — the entire tool-calling loop is net-new, zero existing test coverage.
  • neode-ui/src/services/__tests__/toolConfirm.test.ts — new confirm-flow coverage (extends the existing contextBroker.test.ts pattern).
  • neode-ui/src/composables/__tests__/archyContentAdapter.test.ts — pins the ContentItemFilm/Song/Podcast mapping (Pitfall 4).
  • scripts/build-aiui.sh (or equivalent) — does not exist; D-15's VITE_BASE_PATH enforcement and commit-pinning have no automated check today.
  • AIUI's own test command/framework — confirm before wave planning assumes Vitest parity (not verified in this research pass; AIUI's package.json was read for build scripts only).

Security Domain

Applicable ASVS Categories

ASVS Category Applies Standard Control
V2 Authentication yes New assistant.* RPCs go through the existing session-cookie + CSRF stack (api/rpc/mod.rs:264-330) — no bespoke auth
V3 Session Management yes Chat history/pending-confirmation state must be scoped to the authenticated session/node, not a separate identity
V4 Access Control yes D-06 curated tool allowlist + D-16 default-closed permission categories + (Open Question 4) RBAC integration decision
V5 Input Validation yes D-10: peer-supplied content must enter the model context inside explicit untrusted-content delimiters; tool-call arguments from the model must be schema-validated against each tool's declared parameters before execution (not just trusted because the model emitted well-formed JSON)
V6 Cryptography yes Routstr Cashu payments reuse crate::wallet::ecash/bdhke.rs — never hand-roll token construction; model API keys stay server-side (secrets/claude-api-key pattern)
V13 API and Web Service yes The port-3142 claude-api-proxy.py is a standing V13 violation (unauthenticated proxy to a paid third-party API) that this phase's D-01 should resolve, per Common Pitfall 1

Known Threat Patterns for this stack

Pattern STRIDE Standard Mitigation
Prompt injection via peer-supplied content (filenames, mesh chat, Nostr posts) driving unintended tool calls Elevation of Privilege D-10: untrusted-content delimiters + D-11: human confirmation naming the REAL action for every write, regardless of what the model claims it's doing
Unauthenticated proxy to a paid API (the live port-3142 finding) Spoofing / Elevation of Privilege / Denial of Service (budget exhaustion) Retire or session-gate the legacy proxy (Common Pitfall 1)
Iframe escaping its intended postMessage-only channel via ambient same-origin session cookie Elevation of Privilege Resolve Open Question 2 (sandbox attribute / CSP scoping / accepted residual risk with compensating controls)
Routstr budget exhaustion via repeated/looped tool calls Denial of Service (financial) D-05's hard budget cap in PaymentPolicy — already proven to degrade to None rather than error, must be wired so the loop actually stops and surfaces to the user rather than silently retrying
Confirmation-dialog spoofing (model-authored text presented as a system confirmation) Spoofing / Tampering D-11: node-authored text only, rendered outside the iframe

Sources

Primary (HIGH confidence — read directly from source in this session)

  • core/archipelago/src/mesh/listener/assist.rs (full file) — run_assist, is_sender_allowed, call_ollama, call_claude
  • core/archipelago/src/api/rpc/mesh/assistant.rs, core/archipelago/src/api/rpc/dispatcher.rs (grepped in full for pine./mesh.assistant/method registry)
  • core/archipelago/src/api/rpc/pine_status.rs, core/archipelago/src/api/rpc/package/pine_ha.rs (full files)
  • core/archipelago/src/streaming/mod.rs, streaming/gate.rs, api/rpc/streaming.rs, swarm/payment.rs (full files)
  • core/archipelago/src/nostr_discovery.rs (partial), core/archipelago/Cargo.toml (grepped)
  • core/archipelago/src/api/rpc/middleware.rs, core/archipelago/src/api/rpc/mod.rs (auth/CSRF/RBAC flow)
  • core/archipelago/src/content_server.rs (ContentItem/AccessControl structs)
  • neode-ui/src/types/aiui-protocol.ts, neode-ui/src/services/contextBroker.ts (full files)
  • neode-ui/src/api/filebrowser-client.ts (grepped), neode-ui/src/views/Chat.vue (partial)
  • /home/archipelago/Projects/AIUI (cloned repo, development branch, HEAD 6e8b96d) — packages/app/src/composables/useAI.ts, useArchy.ts (full files), contentExtraction.ts (partial), vite-fs.ts/vite-tmdb.ts/vite-rss.ts/vite-web-search.ts/vite-music-search.ts/vite-dev-chats.ts (grepped for configureServer), components/content/FilmGrid.vue/SongGrid.vue (partial), packages/core/src/types/content.ts (partial)
  • image-recipe/configs/nginx-archipelago.conf, scripts/setup-aiui-server.sh, scripts/deploy-to-target.sh (all grepped/read for the AIUI/Claude-proxy deploy path)
  • apps/aiui/manifest.yml (full file)
  • crates.io API (crates.io/api/v1/crates/lofty, /symphonia, /id3) — VERIFIED: crates.io registry, queried live in this session

Secondary (MEDIUM confidence)

  • docs.routstr.com (/, /api/endpoints/, /client/integration/, /provider/discovery/) — fetched via WebFetch in this session; official documentation but not cross-verified against a live Routstr node or a second independent source — [CITED: docs.routstr.com]
  • github.com/routstr org listing — fetched via WebFetch; confirms no existing Rust SDK, so a hand-written reqwest-based client is the correct approach — [CITED: github.com/routstr]

Tertiary (LOW confidence)

  • None used as load-bearing claims; all [ASSUMED] items are logged in the Assumptions table above.

Metadata

Confidence breakdown:

  • Standard stack (Rust-side reuse: reqwest/nostr-sdk/swarm payment): HIGH — all read directly from in-tree source
  • Standard stack (lofty for music tagging): MEDIUM — crate choice is a training-knowledge recommendation cross-checked against live crates.io data, but not run through the automated package-legitimacy seam
  • Architecture (tool-calling loop, confirm-gate mechanism, content adapter need): HIGH — every claim traced to specific file:line evidence in both repos
  • Architecture (Routstr wire protocol): MEDIUM — CITED from official docs only, not independently protocol-tested
  • Pitfalls (live unauthenticated Claude proxy, same-origin sandbox gap, Pine-is-Q&A-only): HIGH — all independently re-derived from source, not merely repeating CONTEXT.md's claims (and in the live-proxy and same-origin cases, going beyond what CONTEXT.md flagged at all)

Research date: 2026-08-03 Valid until: ~14 days for the Rust/neode-ui findings (stable, slow-moving codebase areas); ~7 days for the Routstr protocol claims (young, actively-developed external project — re-verify against a live relay before implementation) and for the AIUI repo state (actively developed, development branch may move).