320 lines
19 KiB
Markdown
320 lines
19 KiB
Markdown
# Phase 13: AIUI — Conversational Node Control & Content Surfaces - Context
|
|||
|
|
|
||
|
|
**Gathered:** 2026-08-03
|
||
|
|
**Status:** Ready for planning
|
||
|
|
|
||
|
|
<domain>
|
||
|
|
## Phase Boundary
|
||
|
|
|
||
|
|
Make the embedded AIUI functional in three directions: (1) **human-language node control** —
|
||
|
|
a typed request in AIUI chat reaches a real node action and returns a real result;
|
||
|
|
(2) **conversational settings** — system settings reachable by conversation, scoped to what
|
||
|
|
the user granted; (3) **content surfaces made real** — peer files, music, IndeeHub movies and
|
||
|
|
owned/paid content rendered live in the design AIUI already has. All of it inside a
|
||
|
|
**user-granted capability sandbox** that keeps keys, secrets and identity material away from
|
||
|
|
both the browser and the model.
|
||
|
|
|
||
|
|
**Not in scope:** cross-node content distribution with payments (the "archipelago content
|
||
|
|
source"); wallet spends, seed/key operations, federation trust changes and factory reset as
|
||
|
|
chat-reachable actions; Nostr integration polish; reviving the dead `ContentPanel.vue`
|
||
|
|
architecture.
|
||
|
|
|
||
|
|
</domain>
|
||
|
|
|
||
|
|
<decisions>
|
||
|
|
## Implementation 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 — the RPC surface becomes a contract AIUI, and later the voice
|
||
|
|
pipeline, are written against; moving the loop browser-side afterwards means re-homing key
|
||
|
|
handling and re-implementing every tool in TypeScript.
|
||
|
|
|
||
|
|
- **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. Avoids two
|
||
|
|
divergent security models. Note 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`. Nothing is discarded; each
|
||
|
|
side owns what only it can do.
|
||
|
|
|
||
|
|
- **D-08:** Chat history lives **node-side in the per-node data dir** (`/var/lib/archipelago`),
|
||
|
|
inheriting the node's backup, factory-reset and future LUKS story rather than growing a
|
||
|
|
second sensitive-data location.
|
||
|
|
|
||
|
|
### 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. The assistant already
|
||
|
|
reports `ollama_detected` / `claude_available`, so the selection signals exist.
|
||
|
|
**Routstr (<https://github.com/routstr>) is explicitly in scope at the user's request** —
|
||
|
|
it is an OpenAI-compatible endpoint paid per request in Cashu ecash, with providers, models
|
||
|
|
and prices discovered over Nostr. All three of those substrates already exist in this
|
||
|
|
codebase (`core/archipelago/src/streaming/` holds Cashu token handling and the
|
||
|
|
`list-mints`/`configure-mints` RPCs; Nostr discovery is ADR-003/ADR-006).
|
||
|
|
|
||
|
|
- **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 — a
|
||
|
|
prompt-injected model cannot exceed it.
|
||
|
|
— **Reversibility:** reversible — the ceiling is a config value, not a contract.
|
||
|
|
|
||
|
|
- **D-07:** The **local model does get tools**, and every write needs confirmation regardless
|
||
|
|
of backend. A mis-called tool from a weak local model surfaces as a confirmation prompt the
|
||
|
|
user rejects, not a wrong action. Consequence: the confirm gate does the safety work, so
|
||
|
|
**backend choice stays a privacy decision rather than 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: every capability the chat has must be a
|
||
|
|
decision someone made, which is the only way the sandbox claim stays true.
|
||
|
|
— **Reversibility:** reversible — adding tools later is additive; the allowlist is the point.
|
||
|
|
|
||
|
|
- **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. Those stay UI-only.
|
||
|
|
— **Reversibility:** costly — widening later is safe, but any capability shipped and then
|
||
|
|
withdrawn breaks a behaviour users will have learned.
|
||
|
|
|
||
|
|
- **D-10:** **Tool authority never derives from content.** Peer-supplied text (file names,
|
||
|
|
content descriptions, mesh chat, Nostr posts) enters the context inside explicit
|
||
|
|
untrusted-content delimiters that mark it as data, not instructions. The tool layer takes
|
||
|
|
its permissions solely from the user's grants and the confirm gate. An injected "now restart
|
||
|
|
bitcoin" still has to clear a human confirmation naming the real action. Pattern-stripping
|
||
|
|
filters were considered and **rejected** as an arms race that reads as a guarantee it isn't.
|
||
|
|
|
||
|
|
- **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 and
|
||
|
|
never from model-authored text. The iframe cannot spoof, restyle or pre-click it. Uses the
|
||
|
|
project's mandated Teleport-to-body modal pattern.
|
||
|
|
— **Reversibility:** costly — this is the load-bearing anti-spoofing property; moving the
|
||
|
|
dialog inside the iframe later would invalidate the threat model, not just the styling.
|
||
|
|
|
||
|
|
- **D-16:** All 10 permission categories (`apps`, `system`, `network`, `wallet`, `files`,
|
||
|
|
`media`, `search`, `ai-local`, `notes`, `bitcoin`) **default closed** on a fresh node.
|
||
|
|
Nothing is shared with the model until deliberately granted. The assistant looks
|
||
|
|
unconfigured until the user opens categories — accepted cost.
|
||
|
|
|
||
|
|
- **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. They are not to be
|
||
|
|
widened to accommodate this phase. See `10-CONTEXT.md` D-01..D-04.
|
||
|
|
|
||
|
|
### Content surfaces
|
||
|
|
|
||
|
|
- **D-12:** **Feed the existing grids from Archy, replacing the LLM-synth source.** AIUI's
|
||
|
|
design is kept exactly — `FilmGrid`, `SongGrid`, `NewsGrid`, the detail views — and what
|
||
|
|
fills them changes: peer files, IndeeHub movies, owned/paid content and node media arrive as
|
||
|
|
real records instead of being regex-scraped out of model prose.
|
||
|
|
— **Reversibility:** reversible — the grids are prop-driven; the data source behind them is
|
||
|
|
swappable.
|
||
|
|
|
||
|
|
- **D-13:** **Build a real music library** — albums, artists, tracks, tag/metadata extraction,
|
||
|
|
an index that stays fresh. The user chose this over the narrower MIME-filtered-files option
|
||
|
|
after being told no library domain exists today. It lands as **its own wave of plans inside
|
||
|
|
Phase 13, not blocking the rest** — peer files, movies and conversational control ship on
|
||
|
|
their own track and the library lights up `SongGrid` when ready.
|
||
|
|
— **Reversibility:** one-way — an album/artist/track schema and its on-disk index become a
|
||
|
|
persisted data model with a migration cost once nodes have indexed libraries; changing the
|
||
|
|
entity model afterwards needs a reindex path, not just a code change.
|
||
|
|
|
||
|
|
- **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" from the Phase 2 note is deferred
|
||
|
|
— it is a distribution and payments feature spanning federation, not an AIUI surface.
|
||
|
|
|
||
|
|
### Delivery and the two-repo split
|
||
|
|
|
||
|
|
- **D-15:** AIUI is **built and shipped with the frontend, versioned and verified** — the
|
||
|
|
rsync path is kept because it is the one that works, but made deliberate: AIUI's commit
|
||
|
|
pinned in this repo, `VITE_BASE_PATH=/aiui/` enforced by the build script rather than
|
||
|
|
remembered, and a post-deploy check that **fetches a live asset** instead of trusting a
|
||
|
|
directory listing. Making AIUI a signed-catalog app was considered and rejected for this
|
||
|
|
phase: `*-ui` apps are outside the catalog by design today, and changing that platform rule
|
||
|
|
mid-phase is its own work.
|
||
|
|
|
||
|
|
- **D-17:** AIUI **keeps its standalone mode**; embedded mode delegates to the node. It goes
|
||
|
|
on working on its own with its own proxy for development and for anyone running it outside a
|
||
|
|
node; when `embedded=true` it hands the loop, the tools and the key to Archy. The 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**, treated as a
|
||
|
|
prerequisite rather than discovered mid-plan. Last time this surfaced at execution and left
|
||
|
|
neode-ui shipping two query params that were inert no-ops against every deployed AIUI build
|
||
|
|
until a maintainer merged (see `.planning/WINDOWS.md` window 4).
|
||
|
|
|
||
|
|
### 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.
|
||
|
|
|
||
|
|
</decisions>
|
||
|
|
|
||
|
|
<canonical_refs>
|
||
|
|
## Canonical References
|
||
|
|
|
||
|
|
**Downstream agents MUST read these before planning or implementing.**
|
||
|
|
|
||
|
|
### The existing AIUI bridge (this is NOT greenfield — read before designing anything)
|
||
|
|
- `neode-ui/src/types/aiui-protocol.ts` — protocol v1.0.0, `aiui:` message prefix, the
|
||
|
|
request/response contract. Defines `AIContextCategory` (10 categories) and `AIActionType`
|
||
|
|
(`install-app | open-app | navigate | launch-app | search-web | read-file | tail-logs`).
|
||
|
|
- `neode-ui/src/stores/aiPermissions.ts` — the 10 user-toggled permission categories with
|
||
|
|
labels; `isEnabled` / `toggle`.
|
||
|
|
- `neode-ui/src/services/contextBroker.ts` — the 624-line origin-scoped postMessage broker
|
||
|
|
that "checks permissions, fetches data from Pinia stores, sanitizes it (strips sensitive
|
||
|
|
fields), and responds". The asset D-03 splits.
|
||
|
|
- `neode-ui/src/services/__tests__/contextBroker.test.ts`, `neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts` — existing coverage to keep green.
|
||
|
|
- `neode-ui/src/views/Chat.vue` — the iframe embed, `aiuiUrl` construction, origin check, the
|
||
|
|
`ready` handshake, `allow="microphone"`.
|
||
|
|
|
||
|
|
### The existing node-side assistant (the thing D-02 extends)
|
||
|
|
- `core/archipelago/src/api/rpc/mesh/assistant.rs` — `mesh.assistant-status` /
|
||
|
|
`mesh.assistant-configure`; reports `ollama_detected`, `claude_available`, `models`,
|
||
|
|
`trusted_only`, `allowed_contacts`, `denied_askers`; key at `data_dir/secrets/claude-api-key`.
|
||
|
|
- `core/archipelago/src/mesh/listener/assist.rs` — `run_assist`, `is_sender_allowed`,
|
||
|
|
`call_ollama`, `call_claude`, `cap_reply`. **Q&A only — no tool-calling today.**
|
||
|
|
- `core/archipelago/src/api/rpc/dispatcher.rs` — the method registry (`mesh.assistant-*` at
|
||
|
|
~445). **Confirmed: there are no `pine.*` methods** — Pine has no RPC surface.
|
||
|
|
|
||
|
|
### Routstr (new integration, user-requested)
|
||
|
|
- <https://github.com/routstr> — org; `routstr-core`, `routstrd`, `routstr-sdk`, `routstr-chat`.
|
||
|
|
- <https://docs.routstr.com/> — protocol docs.
|
||
|
|
- `core/archipelago/src/streaming/` — existing Cashu handling (`gate.rs` verifies/receives
|
||
|
|
tokens, `pricing.rs`, `session.rs`) and the `streaming.list-mints` / `.configure-mints` RPCs.
|
||
|
|
Note: currently `#![allow(dead_code)]`, "suppress dead_code until callers land".
|
||
|
|
|
||
|
|
### Content subsystem (what D-12/D-14 wire the grids to)
|
||
|
|
- `core/archipelago/src/content_server.rs` — `ContentItem` shape (`id`, `filename`,
|
||
|
|
`mime_type`, `size_bytes`, `description`, `access`, `availability`, `added_at`),
|
||
|
|
`AccessControl` (`Free | PeersOnly | Paid`), `parse_range_header`, the paid-preview logic
|
||
|
|
and the ISOBMFF faststart check.
|
||
|
|
- `core/archipelago/src/api/handler/content.rs` — `GET /content`, `/content/<id>`,
|
||
|
|
`/preview`, `/invoice`; Range → 206 with `Content-Range`; 402 body with `price_sats`.
|
||
|
|
- `core/archipelago/src/api/handler/proxy.rs:188-265` — the peer Range-streaming proxy
|
||
|
|
(`/api/peer-content/<onion>/<id>`). Its docstring explains why base64 blobs broke seeking.
|
||
|
|
- `core/archipelago/src/api/rpc/content.rs` — the `content.*` RPCs including
|
||
|
|
`browse-peer`, `download-peer*`, `preview-peer`; auto-filing by MIME at ~668.
|
||
|
|
- `neode-ui/src/composables/useAudioPlayer.ts`, `neode-ui/src/components/GlobalAudioPlayer.vue`
|
||
|
|
— the singleton bottom-bar player. **Audio never opens the lightbox** — enforced in 5 places.
|
||
|
|
- `neode-ui/src/api/filebrowser-client.ts` — the scoped-token pattern (`app.filebrowser-token`)
|
||
|
|
that D-01 follows. **Known leak to fix rather than propagate:** `streamUrl` puts the JWT in
|
||
|
|
the URL query string.
|
||
|
|
|
||
|
|
### Prior phase context (locked decisions that constrain this phase)
|
||
|
|
- `.planning/phases/10-key-material-hardening/10-CONTEXT.md` — D-01..D-04, the
|
||
|
|
`UNAUTHENTICATED_METHODS` hard-refuse gates. **Must not be widened.**
|
||
|
|
- `.planning/phases/02-ui-performance/02-CONTEXT.md` — D-14 (the shipped AIUI embed defaults)
|
||
|
|
and the Deferred Ideas block, which is the origin of this phase.
|
||
|
|
- `.planning/phases/02-ui-performance/02-AIUI-D14.md` — the embed parameter contract, AIUI's
|
||
|
|
repo location and branch, and the push-access history.
|
||
|
|
- `.planning/WINDOWS.md` window 4 — the 403 that made D-18 a prerequisite.
|
||
|
|
|
||
|
|
### Project invariants
|
||
|
|
- `CLAUDE.md` — commit/push discipline, rootless-Podman invariant, the frontend-build verify
|
||
|
|
rule (grep the built bundle), "verify on the real node before any tag".
|
||
|
|
- `.planning/PROJECT.md` — ADR-003 (Nostr discovery), ADR-006 (DID-signed, trust tiers),
|
||
|
|
ADR-008 (dual keys from one seed), ADR-009 (container security).
|
||
|
|
|
||
|
|
</canonical_refs>
|
||
|
|
|
||
|
|
<code_context>
|
||
|
|
## Existing Code Insights
|
||
|
|
|
||
|
|
### Reusable Assets
|
||
|
|
- **The permission + consent layer already exists** — 10 categories, a store, a broker that
|
||
|
|
sanitizes, and tests. This phase extends it rather than inventing it.
|
||
|
|
- **The assistant already abstracts two model backends** and already holds a key server-side
|
||
|
|
at `data_dir/secrets/claude-api-key` — the pattern D-01 generalizes.
|
||
|
|
- **Cashu, Nostr and Lightning are all already in-tree**, which is why Routstr is a smaller
|
||
|
|
lift here than it would be elsewhere.
|
||
|
|
- **Range-streaming media delivery is solved** — both own files (filebrowser `/api/raw`) and
|
||
|
|
peer files (the Rust proxy). The grids need data, not a transport.
|
||
|
|
|
||
|
|
### Established Patterns
|
||
|
|
- Audio belongs to the global bottom-bar player, never the lightbox (enforced in 5 call sites).
|
||
|
|
- Modals Teleport to body for a full-screen backdrop (project rule, repeatedly reinforced).
|
||
|
|
- Scoped tokens minted by an authenticated RPC, credentials never reaching the browser.
|
||
|
|
|
||
|
|
### Integration Points
|
||
|
|
- `dispatcher.rs` — where new assistant/tool RPCs register.
|
||
|
|
- `ContextBroker.handleMessage` — where the browser-only action split (D-03) lands.
|
||
|
|
- `ChatPage.vue` → `ContentGridView.vue` → the `*Grid` components — the live render tree the
|
||
|
|
Archy data must reach (**note `ContentPanel.vue` is dead; do not build through it**).
|
||
|
|
|
||
|
|
### Landmines found during scouting (verified, not assumed)
|
||
|
|
- **AIUI's grids are fed by regex-parsing the model's own reply text** (`updatePanelFromText`
|
||
|
|
→ `contentExtraction.ts`), resolving IDs against fixture catalogs that are themselves
|
||
|
|
injected into the system prompt (`useAI.ts:24-34`). The largest data bucket is
|
||
|
|
LLM-synthesized, not an API awaiting a base URL.
|
||
|
|
- **Every "real" data path in AIUI is Vite dev middleware** — all six plugins are
|
||
|
|
`configureServer`/`configurePreviewServer` only, so they are **absent from a static `dist/`
|
||
|
|
deploy**. On a node, TMDB posters, web search, RSS and filesystem all 404.
|
||
|
|
- **`vite-fs.ts:7` hardcodes `PROJECTS_ROOT = '/Users/dorian/Projects'`** — broken on any
|
||
|
|
other machine, including the Linux dev box.
|
||
|
|
- **`ContentPanel.vue` is dead code**, taking `ArchyAppsGrid` (the Archy bridge grid),
|
||
|
|
`FavoritesGrid`, `DiscoverPanel`, `RecipeDetail` and `AppDetail` with it. Clicking a recipe
|
||
|
|
or an app currently does nothing.
|
||
|
|
- **`ShareModal.vue`'s mime map omits `m4a`/`aac`/`opus`/`wma`** — those share as
|
||
|
|
`application/octet-stream`, so they never route to the audio player and are auto-filed to
|
||
|
|
`Documents` instead of `Music`. Relevant to D-13.
|
||
|
|
|
||
|
|
</code_context>
|
||
|
|
|
||
|
|
<specifics>
|
||
|
|
## Specific Ideas
|
||
|
|
|
||
|
|
- Routstr was named by the user directly, with the repo link, and asked to be planned in as
|
||
|
|
part of the backend work — not treated as a future option.
|
||
|
|
- The sandbox framing is the user's own: "we must sandbox and protect the users sensitive
|
||
|
|
keys, information, etc whatever they allow access to." The last clause is the design brief —
|
||
|
|
authority is bounded by what the user allows, not by what the model asks for.
|
||
|
|
- The origin of this phase is the user's Phase 2 wording: AIUI "talks to the node safely when
|
||
|
|
permissioned, without leaking data, **using the same command surface as Pine** and everything
|
||
|
|
else enableable in settings." D-02's shared-service shape is that sentence made concrete.
|
||
|
|
|
||
|
|
</specifics>
|
||
|
|
|
||
|
|
<deferred>
|
||
|
|
## Deferred Ideas
|
||
|
|
|
||
|
|
- **Cross-node "archipelago content source" with payments** — any IndeeHub install plugs into
|
||
|
|
every node's content, with payments; same for music. A federation distribution + payments
|
||
|
|
feature; its own phase (deferred at D-14).
|
||
|
|
- **AIUI Nostr integration polish** — "make the Nostr integration in AIUI more beautiful."
|
||
|
|
Carried over from Phase 2's deferred list, still not scoped here.
|
||
|
|
- **Reviving `ContentPanel.vue` and the plugin-renderer path** — considered and rejected for
|
||
|
|
this phase; the dead-code inventory should be resolved as cleanup, not as architecture.
|
||
|
|
- **AIUI's dev-only Vite middleware** (`vite-tmdb`, `vite-rss`, `vite-web-search`, `vite-fs`,
|
||
|
|
`vite-music-search`, `vite-dev-chats`) — needs a production answer eventually; only the parts
|
||
|
|
D-12 replaces are in scope now.
|
||
|
|
- **Pine voice reusing the tool registry** — D-02 makes it possible and is the reason for the
|
||
|
|
shared-service shape, but wiring the voice pipeline to it is not in this phase.
|
||
|
|
|
||
|
|
### Reviewed Todos (not folded)
|
||
|
|
- *Connected-nodes list must scroll at row-matched height* — keyword match only; belongs to
|
||
|
|
Phase 1 (UIFIX-02, already complete).
|
||
|
|
- *Fedimint gateway must not install with a pre-set password* — keyword match only; Phase 1
|
||
|
|
FED-07 territory.
|
||
|
|
- *Keep FIPS/Tor pills on cloud files and show them on mobile* — keyword match only; Phase 1
|
||
|
|
UIFIX-01.
|
||
|
|
|
||
|
|
</deferred>
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Phase: 13-AIUI — Conversational Node Control & Content Surfaces*
|
||
|
|
*Context gathered: 2026-08-03*
|