diff --git a/COMMS/README.md b/COMMS/README.md new file mode 100644 index 00000000..60845379 --- /dev/null +++ b/COMMS/README.md @@ -0,0 +1,13 @@ +# COMMS + +Notes between the people and agents working on Officer, kept **in the repository** rather than in a +directory on one machine. + +Why here: a note on a server is invisible to whoever picks the work up next, has no history, and cannot +be reviewed alongside the change it argues about. In git it is diffable, it arrives with `git pull`, +and the commit that answers a note sits next to the note. + +One directory per subject — `command-hq/` — and one file per side of the conversation, so a reply is a +new file or an edit with a visible diff, never an overwrite that loses what was asked. + +Write plainly. These are read by someone who was not in the conversation that produced them. diff --git a/COMMS/command-hq/mobile.md b/COMMS/command-hq/mobile.md new file mode 100644 index 00000000..a201a9ec --- /dev/null +++ b/COMMS/command-hq/mobile.md @@ -0,0 +1,75 @@ +# Command HQ — the mobile side, and the one thing it cannot do + +Written 2026-08-18 by the mobile side, after building `apps/commandhq` against +`src/servers/api/command-hq/router.ts` at `eb94af87`. + +## The API needed nothing changed to be usable + +`/api/command-hq` was read rather than described: the types in the app are copied from the router, so +the two cannot drift. Everything the grid needs is already there — categories, commanders with +`categoryId`/`tagIds`/`running`, rename via PATCH, purge. + +Two decisions in the design record turned out to matter more than they look, and both were right: + +**No name column.** Renaming proxies to `renameClaudeSession`, so there is exactly one title and no +second field to disagree with it. + +**`running` is derived, not stored.** The client re-reads it on a timer and trusts it, which it could +not do with a status column. The grid polls every 30s — a watcher polls its own source every ten +minutes or more, so half a minute behind is still the truth. + +And one that saved the whole build: **the detail route deliberately does not return the conversation.** +Because the transcript is `GET /chat/sessions/:id` unchanged, a commander's detail on mobile is the +*existing* chat screen. The composer, the streaming, the tool blocks and the background-job sheet all +work inside a commander for free. Had that route returned its own transcript shape, mobile would have +needed a second reader. + +## What mobile cannot do: create a commander + +The client has no way to start one, and this is the only gap. + +Creation requires a session in the commanders directory. The design record is explicit that this is +`DATA_PATH//commanders/`, one shared directory per user — but **no endpoint returns that path**, +and `POST /command-hq/commanders` only files a session that already exists there. A listed commander +carries `cwd` (spread from the session summary), so a client with one commander could infer it; a +client with none cannot create its first. + +The web composer does not have this problem: per the design record it flags the spawn and the server +picks the directory. + +### The decision, made with the owner + +**The API spawns it. The client is not responsible for knowing where commanders live.** + +Something of the shape: + +``` +POST /command-hq/commanders/new + body: { categoryId?: number | null, prompt?: string } + -> { sessionId, cwd } +``` + +- The server resolves `getCommandersDir(email)` itself, spawns the session there, writes the + `command_hq_sessions` row, and returns the id. +- **The response must carry `sessionId`**, or the client has to poll the list and guess which row is + new — which is a race with nothing to break the tie, since a commander has no name at creation. +- `cwd` alongside it is convenient but not required; the app only needs it to open the conversation, + and it can equally come from the commander list afterwards. + +Rejected on the way: a `GET /command-hq/workspace` returning the path. It hands the client an absolute +directory so the client can construct what the server could have constructed — which is how two answers +to "where does a commander live" come to exist. + +Also rejected: a `commandHq: true` flag on the initial chat message, mirroring the web composer. +Reasonable, and the reason not to is specific — mobile spawns chats over a websocket with its own +parameter set, so the flag would have to be carried correctly by two independent clients, and one that +gets it wrong silently creates an ordinary chat in the wrong directory. A POST is one endpoint, one +code path, and testable with curl. + +## Status of the mobile app + +`apps/commandhq` exists, typechecks, and is committed — categories, the commander grid with liveness, +rename, move, purge, and the conversation as its detail. It has never made a request: the routes are +not reachable until the server restarts. Nothing has been built or published. + +The first real run is the one that finds where this and the router actually disagree.