diff --git a/PLAN.md b/PLAN.md index 843799e..a42b87a 100644 --- a/PLAN.md +++ b/PLAN.md @@ -44,7 +44,35 @@ Decisions already made (2026-08-24): ## Architecture -### Providers and hosts (decided 2026-08-25) +### Setups and providers (decided 2026-08-28, superseding the below) + +**A setup is a machine, and it carries the providers that machine has.** +Optional ssh details, plus the list of what can be run there. Spawning is +then two choices in order: pick a setup, then pick one of its providers. + +This replaces the independent providers × hosts model recorded below, +which is what the code does today. What went wrong with it: the two axes +are not actually independent. A provider is only real on a machine where +that CLI is installed, so a free cross-product offers combinations that +cannot work — `claude-cli` on a machine with no `claude`, and every +provider paired with a host the driver ignores entirely (`EchoDriver` +takes no host, so "Run on" is a control that silently does nothing for +it). Grouping providers under the machine they exist on makes the picker +show only what is true. + +Open, and worth settling before this is built: + +- **Where the built-in echo provider lives.** It needs no configuration + and is the connectivity check that costs no tokens, so probably a + provider of an implicit local setup rather than something configured. +- **Migration.** Sessions store the provider and host names they were + spawned with; they would store a setup and a provider instead. +- **Setups are edited from the phone**, not by hand in `config.ron` — the + standing preference for this app. Key material is the exception that + cannot travel, so a setup names an identity file that must already exist + on the backend machine. + +The superseded model, for the reasoning it recorded: Two independent axes, configured separately and chosen per session: @@ -225,8 +253,26 @@ host) and **hosts**. The manager runs at most one llama-server per `ssh -T host …`. Process death ≙ connection death; the session shows as `exited` and both dialects resume (`--resume` / pi session file) on respawn, so a dropped SSH connection is an annoyance, not data loss. -- Images and attachments for remote sessions are written to the remote - session dir via `scp`/stdin before the message referencing them is sent. +- **The transport wraps the driver, not the other way round** (decided + 2026-08-28). A driver says what to run — program, arguments, working + directory — and something above it turns that into a process, locally or + through ssh. Today `ClaudeDriver::spawn` calls `ssh::command` itself, + which puts transport knowledge inside a translator whose job is a wire + format, and means every future driver has to remember to do the same. + Inverting it also removes the "Run on" lie for free: a driver that emits + no command, like the echo one, has nothing for a transport to wrap, and + the picker can say so. +- The interface that inversion needs is **not just "run a command"**, and + llama.cpp is the case that shows it: a managed `llama-server` is started + as a process but then spoken to over HTTP, so a remote one needs a + forwarded port (`ssh -L`) as well as a spawned process. A transport is + therefore "run this" plus "reach this port", and the second operation is + a no-op locally. +- Attachments need no file transfer, contrary to what this section said + before: `attachment_block` base64s an uploaded image into the + stream-json message itself, and produced images come back the same way + for the translator to write out locally. Nothing has to exist on the + remote filesystem, so there is no `scp` step to get wrong. ### Usage limits (Claude)