Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/substrate/tasks/node-connection-manager.md
Fimeg e480809c70 docs: rescue the agent-substrate tree out of a gitignored directory
219 files, 2.0 MB, untracked in souveraine/docs and existing nowhere else.
The volume is at 100% with no snapshots.
2026-07-26 12:11:50 -04:00

8.4 KiB

task_id title status priority phase created references
node-connection-manager-001 Node & Connection Manager — Local, Remote, Bootstrap scoping high 3.0 2026-05-14
docs/tasks/federation-seed-id.md
src/api/auth.rs
src/api/mod.rs
src/backend/remote.rs
src/core/identity/seed.rs

Task: Node & Connection Manager

The Question

Right now souveraine starts and goes straight into the TUI or CLI depending on flags. There's no "where do you want to connect?" step. Running remotely requires knowing the URL and token ahead of time through config. There is no discovery, no pairing flow, no way to say "I'm setting up a new instance."

The goal: souveraine loads and presents you with a connection chooser. Local, Connect to a remote, Bootstrap a new remote. The substrate knows where it is in the topology at all times.


What already exists

Component Status Where
SeedId (Ed25519 keypair) Landed src/core/identity/seed.rs
souveraine identity show/sign/verify Landed src/cli/commands.rs
Per-agent seeds Landed server/agents/{uuid}/seed/
Instance registry + uptime Landed SQLite agent_instances table
Auth middleware (require_token) Landed src/api/auth.rs — but only wired to memory routes
LocalBackend Landed In-process, no network
RemoteBackend Landed HTTP/SSE, requires manual config
souveraine.toml bifrost + server config Landed URL, port, token fields exist

What's missing

Gap Why it matters
No "where to connect?" startup flow First-run or flag-day: user sees nothing, has to know --local or souveraine.example.toml exists
Auth middleware not on agent/conversation routes Remote backend can't safely expose agent update/delete or conversation read/inject
No node identity advertisement A remote souveraine instance has no way to say "I am souveraine@hostname, trust anchor is X"
No pairing/bootstrapping flow Setting up a new remote requires manual SCP/scp of tokens
No connection health/status The TUI shows agent state but not backend connection state
souveraine.example.toml not wired File exists at repo root but nothing reads it for guided setup

Flow: The startup experience

$ souveraine
  ╭─ Souveraine ─────────────────────────────╮
  │                                           │
  │  Where do you want to connect?            │
  │                                           │
  │  ◉ Local (in-process, no server needed)   │
  │  ○ Connect to a remote instance           │
  │  ○ Bootstrap a new remote instance        │
  │                                           │
  │  [Enter to select, ↑↓ to navigate]        │
  │                                           │
  │  Souveraine v0.1.0                        │
  │  Seed: glyphlike · No remotes configured  │
  ╰───────────────────────────────────────────╯

If Local is chosen (or single-node is the only option), it proceeds to the existing TUI/CLI. If Connect is chosen, it prompts for URL + token (or discovers via mDNS / known hosts). If Bootstrap is chosen, it SSH'es into a remote machine and runs souveraine init + returns the connection info.


Phases

Phase 1: Startup chooser (TUI-only, 1-2 days)

Before entering the Screen loop, App::new() or main.rs shows a connection chooser as a ratatui screen. Three options:

  1. Local — immediately creates LocalBackend, proceeds to Welcome screen. Default when no remote config exists.
  2. Connect to remote — inline form: URL, API token. Validates by hitting /health. On success, stores in config + creates RemoteBackend.
  3. Bootstrap new remote — SSH host, SSH user (optional, defaults to current user), SSH key path (optional, defaults to ~/.ssh/id_ed25519). Runs souveraine init on the remote via SSH, captures the seed pubkey + API token, stores connection info. This is the "one-command setup" flow.

The chooser is skipped when --local, --remote <url>, or a saved default connection is set. It only shows when ambiguous.

Files:

  • src/ui/connection_chooser.rs — new, the ratatui chooser screen
  • src/main.rs — edit, route to chooser before App init unless flags/saved
  • src/ui/app.rs — the chooser populates config.remote before App builds
  • souveraine.example.toml — already exists, used as config template

Phase 2: Auth middleware expansion (2-3 days)

Move agent update/delete and conversation routes behind require_token:

  • PATCH /v1/agents/:id — agent owner only
  • DELETE /v1/agents/:id — agent owner only
  • GET /v1/conversations/:id — conversation's agent owner only
  • POST /v1/conversations/:id/messages — conversation's agent owner only
  • GET /v1/agents and POST /v1/agents stay open (listing, bootstrap)

Conversation auth needs a new middleware wrapper that resolves agent_id from the conversation lookup, then delegates to the existing require_token.

Files:

  • src/api/mod.rs — route groups with .route_layer()
  • src/api/auth.rs — new require_conversation_token wrapper
  • src/backend/remote.rs — send the token header on API calls

Phase 3: Node identity advertisement (1 day)

A GET /v1/node endpoint that returns the instance's seed pubkey, version, agent list, and connection hint:

{
  "id": "did:key:z6Mk...",
  "version": "0.1.0",
  "agents": ["ani", "sam"],
  "hostname": "onyx.local",
  "public_url": "https://souveraine.onyx.local:8484"
}

The connection chooser's "Connect" flow hits this endpoint to validate the URL and pre-fill the instance name.

Files:

  • src/api/handlers.rs — new get_node_info handler
  • src/api/mod.rs — wire to GET /v1/node (public, no auth)

Phase 4: Connection manager (2-3 days)

Persistent connection profiles stored in config or a new JSON file at ~/.souveraine/connections/. Each profile has:

[connections.onyx]
url = "https://souveraine.onyx.local:8484"
token = "souv_..."
default = true
last_seen = "2026-05-14T18:30:00Z"

The chooser reads these and shows them as quick-select options below "Connect to a remote." Pressing Enter on a saved connection skips the form.

Files:

  • src/core/config.rs — add connections field to ConsciousnessConfig
  • src/ui/connection_chooser.rs — show saved connections as selectable items
  • src/backend/remote.rs — accept connection profile, not manual URL+token

Phase 5: Bootstrap via SSH (2-3 days)

The "Bootstrap new remote" flow:

  1. Prompt for SSH target (user@host), key path, install path
  2. ssh user@host "curl -sfL https://souveraine.sh | bash" or scp the binary
  3. ssh user@host "souveraine init --seed-from <local-seed-pubkey>"
  4. Read back the remote's /v1/node to get its seed pubkey
  5. Read back the remote's API token from ~/.souveraine/server/token
  6. Store the connection profile locally
  7. Return to the chooser with the new connection selected

SSH is the bootstrap transport; once configured, all communication goes through the HTTP/SSE API with bearer token auth.

Files:

  • src/core/ssh.rs or src/cli/ssh.rs — new, SSH command execution
  • src/ui/connection_chooser.rs — add Bootstrap flow state machine

Non-goals

  • mDNS / automatic discovery — typed-in URLs and SSH bootstrap only. No bonjour/avahi. Discovery can be layered on top later.
  • WebSocket relay server — not yet. Phase 5 only establishes a point-to-point connection. A relay for NAT traversal is future work.
  • Multi-hop federation (A→B→C) — star topology only. One local node connects to one remote node.
  • Remote agent creation — agents are created on the local instance and synced. The remote is a read/submit surface, not a creation surface.

Build sequence

  1. SeedId, per-agent seeds, instance registry, CLI commands
  2. Connection chooser screen (Phase 1) — pure TUI, no SSH
  3. Auth middleware expansion (Phase 2) — secure remote API
  4. Node identity endpoint (Phase 3) — GET /v1/node
  5. Connection profiles (Phase 4) — saved connections in config
  6. SSH bootstrap (Phase 5) — one-command remote setup
  7. Connection health in TUI — status indicator in header/footer