Providers and hosts: what runs, and where, as independent choices
A session now names a provider (what: driver kind, command, models) and optionally a host (where: an ssh target). Keeping them independent is what the real setup needs -- the backend runs where the phone can reach it, which isn't where the CLI is installed -- and it means any provider can be sent to any host rather than a machine being baked into one. The first provider is claude-cli, named for the CLI rather than bare "claude", which would suggest the credit-billed API. A fresh config is seeded with it so a new install has something to spawn and a worked example to edit; echo stays a built-in provider needing no config. ssh.rs builds the child process either way: locally, or `ssh -T` with BatchMode and keepalives, every argument single-quoted for the remote shell (a working directory that tries to close the quote and start a command is covered by a test), and `exec` so dropping the connection takes the CLI down instead of orphaning it. App: the spawn screen reads /providers and /hosts instead of hardcoded lists, so config changes need no rebuild. Chip rows are FlowRow, fixing the reported bug where a row of models that didn't fit wrapped *inside* each chip -- one letter of "haiku" per line -- rather than onto a second line. Verified: 29 tests, clippy clean; the same claude-cli provider run once locally and once over ssh, with the remote one visibly in a different environment; an unknown host name refused with the configured list; and the spawn screen on the emulator showing server-driven providers, hosts, and models that wrap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
91bbc73ae5
commit
fff1fb49e8
12 files changed
+833
-170
No files matched your search
@@ -44,6 +44,26 @@ Decisions already made (2026-08-24):
|
||||
|
||||
## Architecture
|
||||
|
||||
### Providers and hosts (decided 2026-08-25)
|
||||
|
||||
Two independent axes, configured separately and chosen per session:
|
||||
|
||||
- A **provider** is *what* runs: a driver kind, the command to invoke, and
|
||||
the models worth offering. `claude-cli` is the first — named for the CLI
|
||||
specifically, since bare "claude" would suggest the credit-billed API,
|
||||
which this is not. llama.cpp becomes a second provider later.
|
||||
- A **host** is *where* it runs: an ssh target. Absent means the backend
|
||||
machine itself.
|
||||
|
||||
Sessions name both. Keeping them independent is what the motivating setup
|
||||
requires: the backend runs on the machine the phone can reach (where
|
||||
WireGuard terminates), which is not necessarily where a CLI is installed —
|
||||
here the Claude CLI lives only in a VM on that machine, while llama.cpp
|
||||
will be on the host itself. Pinning a host into a provider would make "the
|
||||
Claude CLI" and "the Claude CLI over there" two things to configure and
|
||||
choose between, and would stop the same provider from being sent somewhere
|
||||
else for one session.
|
||||
|
||||
```
|
||||
Android app (Compose)
|
||||
│ HTTPS (pinned CA) — REST for actions, SSE for live events
|
||||
@@ -52,7 +72,8 @@ backend (Rust/Axum, desktop)
|
||||
├─ SessionManager ── Session ── Driver (trait)
|
||||
│ ├─ ClaudeDriver (claude stream-json)
|
||||
│ └─ PiDriver (pi --mode rpc)
|
||||
│ each driver's process is spawned locally or as `ssh host …`
|
||||
│ each driver's process is spawned locally or as `ssh host …`,
|
||||
│ decided per session by the host it names
|
||||
├─ LlamaServerManager (llama-server lifecycle, local + SSH)
|
||||
├─ UsageMonitor (Anthropic OAuth usage endpoint)
|
||||
└─ config.json + per-session transcript files
|
||||
@@ -214,8 +235,10 @@ the pinned TLS listener. SSE over WebSocket because resume-by-cursor
|
||||
is plain POSTs anyway.
|
||||
|
||||
```
|
||||
GET /sessions list (id, kind, title, host, model, status, last activity)
|
||||
POST /sessions spawn {kind, host, model, cwd, permission_mode, title}
|
||||
GET /providers what can be spawned (name, kind, models)
|
||||
GET /hosts machines a session can be run on
|
||||
GET /sessions list (id, provider, host, title, model, status, last activity)
|
||||
POST /sessions spawn {provider, host, model, cwd, permission_mode, title}
|
||||
GET /sessions/:id/events?after=N SSE: transcript replay from N, then live
|
||||
POST /sessions/:id/message {text, attachment_ids}
|
||||
POST /sessions/:id/answer {question_id, answer} (questions and permissions)
|
||||
@@ -416,7 +439,18 @@ window just fills.
|
||||
up in this VM, so this phase isn't testable here — Claude first; the
|
||||
driver seam is ready when it is.*
|
||||
5. **SSH** — host config, remote spawn for both kinds, remote llama-server
|
||||
with port forward, attachment shipping.
|
||||
with port forward, attachment shipping. *Host config and remote spawn
|
||||
done 2026-08-25* (any session of any provider can name a host; the
|
||||
command is the identical one wrapped in `ssh -T`, with every argument
|
||||
shell-quoted). Attachment shipping turned out to be unnecessary for the
|
||||
Claude driver — images ride the stdio JSONL as base64 in both
|
||||
directions, so nothing needs `scp`. Still outstanding: remote
|
||||
llama-server with its port forward, which comes with phase 4.
|
||||
Two things learned doing it: a remote session inherits ssh's non-login
|
||||
PATH, which is narrower than an interactive shell's (point `command` at
|
||||
an absolute path if a CLI isn't found), and the remote command is run
|
||||
with `exec` so dropping the connection takes the CLI down rather than
|
||||
orphaning it.
|
||||
6. **Polish** — reconnect edges, notification when a session awaits an answer
|
||||
(the "your turn" push), transcript search, whatever daily use surfaces.
|
||||
|
||||
|
||||
Reference in new issue
Block a user