diff --git a/TODO.md b/TODO.md index ffc91289..0e91e80d 100644 --- a/TODO.md +++ b/TODO.md @@ -79,6 +79,36 @@ hold a platform checkout, three run it, and it records how to reach each one. - The updater then does a checkout at that SHA, not a `pull` — same discipline the plugin-pinning item asks for: **verify `rev-parse HEAD` matches, or refuse.** +## Speaking into a session from the backend + +**Correction, 2026-08-21: the door exists, and three entries in this file said it did not.** The claim +that `pushTurn` has exactly one caller (the chat websocket) is **stale**. `api/chat/nudger.ts:102` calls +`startClaudeTurn({ ws: null, … })` — a turn started server-side, with no browser attached, into a live +session. Whoever is watching the chat sees it arrive the same way they would see a turn typed in another +tab. `startNudger()` is wired at `server.tsx:182` and ticks once a minute against a `nudges` table. + +So **both** "missing" pieces are built: a recurring trigger, and a way to put a prompt into a running +session. What remains is narrower, and worth doing. + +- [ ] **Generalise it: deliver a turn to a named session, from anywhere in the backend.** Wanted + 2026-08-21 — "ideal" is the owner's word for it. Today the only server-side caller is the nudger, + and delivery is welded to its schedule: a row in `nudges`, one per (user, session), a fixed + message, a tick. What the dispatcher, the file watchers and Command HQ all want instead is a plain + verb — *here is some text, deliver it to session X* — with the schedule, if any, belonging to the + caller. + + Two things already established and worth not re-deriving. The nudger's `checkSafe` holds three + refusals — not live, live but generating, another live session in the same directory — and the + comments above it carry why each one is there. And "not live" is the case that differs by caller: + the nudger refuses (nudging a dead session would CREATE an agent, which is not what nudge means), + while a dispatcher handing over five todos does want it created. + +- [ ] **The cost of resuming grows with the transcript.** Raised 2026-08-21 by the owner as a challenge + worth taking rather than a limitation to live with. Every resume re-reads the whole conversation as + input tokens, so a long-lived commander costs more per note than it did in week one, for identical + work. Measurable today rather than theoretical: `command_hq_turns` already records input tokens and + USD per turn, so the curve can be plotted before anyone argues about a fix. + ## File watchers - [ ] **Watch a directory, and attach actions to what lands in it.** Wanted 2026-08-21. The owner sets a @@ -91,10 +121,10 @@ hold a platform checkout, three run it, and it records how to reach each one. session open and no phone unlocked into the right screen. What it needs, in rough order: a watcher registry (path + what to do), a trigger that fires on - file arrival rather than on a clock, and a way to hand the file to an agent turn. Note the second - and third are the **same two gaps the Nudger item names** — the platform has no periodic/event - trigger, and `pushTurn` has one caller, so nothing server-side can speak into a session. Build - them once. + file arrival rather than on a clock, and a way to hand the file to an agent turn. **The third is + no longer missing** — see "Speaking into a session from the backend"; the nudger already starts a + server-side turn with no browser attached. What is missing here is arrival-triggered firing, since + the existing scheduler is a clock tick. Open, not decided: whether the watcher is a plugin or core; whether "action" is a task, a prompt from the prompt library, or a chat message; and how a file is marked consumed so it is not @@ -186,11 +216,11 @@ hold a platform checkout, three run it, and it records how to reach each one. - **Inferred.** Nothing is named, and the dispatcher works it out from the content. This is the one that makes the app worth using: the ideal is that most notes need no prefix at all. - What this implies, and it is the same missing piece named twice already: **destinations must be - addressable and speakable.** "My todo guy" is a name for a standing agent session, so the - dispatcher needs a registry of destinations with names a person would actually say, and a door to - deliver into a live session — which is the `pushTurn`-has-one-caller gap in the Nudger and File - watchers items. Three features now want it; it is the thing to build. + What this implies: **destinations must be addressable and speakable.** "My todo guy" is a name for + a standing agent session, so the dispatcher needs a registry of destinations with names a person + would actually say. Command HQ is already most of that registry — `command_hq_sessions` names + commanders and keys them on Claude's own transcript uuid. Delivery into a session exists too; see + "Speaking into a session from the backend". Fits with the rest rather than duplicating it: the **File watchers** split applies unchanged — a script transcribes on arrival, the dispatcher is the agent stage — and the **iPhone Action @@ -959,13 +989,11 @@ outside the example's new README: and speaks over the chat websocket, exactly as a browser does, so the platform needed no changes. That is the workaround, not the feature. - **Two things the platform genuinely lacks**, and they are the work: - - 1. **A recurring trigger.** The queue has retries and delays but nothing periodic, and - `queue/handlers/index.ts` is empty. `officer-notify` is now a natural home for "fire this - later" — it already runs as core and already fans out on a schedule-free signal. - 2. **A way to put a prompt into a live session.** `pushTurn` has exactly ONE caller, the chat - websocket handler. Anything server-side that wants to speak to a session has no door. + **Both things this said the platform lacked have since been built** (verified 2026-08-21): the + recurring trigger is `startNudger()` at `server.tsx:182`, ticking once a minute over a `nudges` + table; the door into a session is `startClaudeTurn({ ws: null })` at `nudger.ts:102`. What is left + is the UI — a control in the chat composer, beside the background-job button — plus generalising + delivery beyond the nudger's own schedule. See "Speaking into a session from the backend". Design notes worth keeping: