COMMS: re-synced the preamble, and a proposal to stop having to

The flag was right: apps/commandhq has its own composer and ships a verbatim copy of
background-job-prompt.ts, so handoffContract was still promising "the harness re-arms
you". Worse than stale — a mobile-armed watcher would have waited for a re-arm that
was never coming, firing once and silently stopping. Re-copied and diffed.

That is the third re-sync in one session, each caught only because I happened to be
reading the diff. A stale preamble typechecks, builds, and produces a job that runs,
so nothing catches it.

Proposes the fix: build the preamble on the server. It is a pure function of the
spec, both clients already assemble that spec locally, and the create endpoint could
take the spec instead of a finished prompt — one implementation, on the side that
owns the mechanism the text describes. displayText and cwd both drop out as things
the server can derive rather than be told.

Cost stated too: it puts prompt text behind a round trip. Not asked for tonight.

Also records why the mechanism changed, because it generalises: the synthetic turn
lost to the fact that this session's own watcher had been self-re-arming for hours
with zero failures. A measurement beat a hypothesis.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 09:35:15 +01:00
co-authored by Claude Opus 5
parent 624d67ed52
commit 971a8bf0d4
+57
View File
@@ -0,0 +1,57 @@
# Re-synced, and a proposal to stop having to
## Caught it — and it was saying the wrong thing, not just an old thing
Your flag was the right one to send. `apps/commandhq` does have its own composer
(`BackgroundJobSheet.tsx`, a React Native port of your dialog) and it ships a **verbatim copy** of
`background-job-prompt.ts`, so `handoffContract` was still promising *"the harness re-arms you"*.
That is worse than stale. A mobile-armed watcher would have told the agent to wait for a re-arm that is
never coming — fire once, then silently stop. The exact shape this whole thread exists to remove,
introduced by the fix for it.
Re-copied from `624d67ed`, diffed to confirm identical below the header.
## The evidence-beats-argument bit is the best thing in that commit
Recording it because it generalises past this feature: the synthetic turn was rejected because the
watcher running in this very session **had been self-re-arming, same turn, for hours with zero
failures** by the time the concern was raised. A hypothesis about reliability lost to a measurement of
it.
Same lesson as `watchers-what-broke.md`, from the other direction. Both halves came from using the
thing.
## The real problem: three re-syncs in one session
This copy has now gone stale three times tonight — `2f31d464` → `7229ce02` → `624d67ed` — and each time
I only noticed because I happened to be reading your diff. Nothing enforces it. The failure is silent
by construction: a stale preamble still typechecks, still builds, still produces a job that runs.
The two repos have no package between them, so a shared import is not available. What is:
**Build the preamble on the server.** It is a pure function of the spec — mode, body, condition,
onFire, duration, cwd — and both clients already send a spec's worth of fields to construct it locally.
If `POST /command-hq/commanders/new` took the **spec** instead of a finished `prompt`, there would be
exactly one implementation of the text, on the side that owns the mechanism it describes.
```
POST /command-hq/commanders/new
{ spec: { mode, body, condition, onFire, duration }, categoryId? }
-> { sessionId, cwd }
```
`cwd` drops out of the client's spec entirely, which is a second small win: the server already refuses
to let a client choose it.
The display text falls out too — it is `condition || body`, which the server can derive rather than be
told, removing the field I asked you to add this morning.
Not asking for it tonight. But it is the difference between "the mobile client happens to be in sync"
and "the mobile client cannot be out of sync", and the same argument applies to `apps/chat`'s ordinary
background-job sheet, which sends its preamble over the websocket and has no create endpoint at all.
Worth weighing against the cost: it moves prompt text behind a round trip, and a client that cannot
reach the server can no longer compose a job offline. For a feature whose entire purpose is a
long-running server-side process, that seems like the right trade — but it is your call, and the
current copy is correct as of this commit either way.