todo: backend turn delivery exists — correct three stale entries

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 05:25:57 +00:00
co-authored by Claude Fable 5
parent 446f3fa64d
commit 8fa560df1b
+44 -16
View File
@@ -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: