Merge branch 'master' of gitea.officer.dev:officerdev/platform
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Command HQ — mobile, second note
|
||||
|
||||
Answering the two questions in `platform.md`.
|
||||
|
||||
## 1. `prompt` required — yes, and it needs a companion field
|
||||
|
||||
**There is no "create an empty commander" case on mobile.** A conversation does not exist until a turn
|
||||
has run; the composer's send is disabled with no text. Make `prompt` required and refuse the empty case
|
||||
outright — half-supporting it would model a state neither client can produce.
|
||||
|
||||
**But `prompt` alone is not enough, and this is the part worth building in now rather than discovering
|
||||
on the grid.**
|
||||
|
||||
A commander created from mobile will almost always be a background job, which means the text sent to
|
||||
the model is the *preamble* — `buildBackgroundJobPrompt`, a page of mechanics — with the owner's one
|
||||
sentence at the end. The websocket already handles this exactly right:
|
||||
|
||||
```ts
|
||||
session.meta.title = (msg.displayText ?? msg.prompt).slice(0, 100);
|
||||
```
|
||||
|
||||
Checked both clients: the web sends `displayText` from `sendWithHiddenPrefix`, and mobile sends it too
|
||||
(`useSessionView.ts`, `...(prefix ? { displayText: text } : {})`). So titles today are the owner's
|
||||
sentence, not the mechanics.
|
||||
|
||||
**`POST /command-hq/commanders/new` must take the same pair**, or every commander created through it is
|
||||
titled *"Start a background job. The mechanics below are load-bearing; follow them exactly."* — and
|
||||
since a commander has no name column, the grid becomes a column of identical cards with no way to tell
|
||||
them apart. That is the failure mode this endpoint is uniquely positioned to cause, because it is the
|
||||
one creation path that would otherwise take a single `prompt` string.
|
||||
|
||||
So:
|
||||
|
||||
```
|
||||
POST /command-hq/commanders/new
|
||||
body: { prompt: string, // required — what the model receives, preamble and all
|
||||
displayText?: string, // what titles it; falls back to `prompt`, as the socket does
|
||||
categoryId?: number | null }
|
||||
-> { sessionId, cwd }
|
||||
```
|
||||
|
||||
Same precedence as the websocket — `displayText ?? prompt` — so there is one rule for titles rather
|
||||
than two.
|
||||
|
||||
## 2. The refactor — yes, and awaiting `session:claude` is right
|
||||
|
||||
Awaiting `session:claude` rather than the whole turn matches what mobile already does: it learns the
|
||||
session id from the socket mid-turn and does not wait for `result`. A commander that only appeared once
|
||||
its first turn finished would be missing for however long the first run takes, which for a watcher is
|
||||
"until it fires".
|
||||
|
||||
**Happy to be the second pair of eyes on that diff.** Extracting the turn-starting half of
|
||||
`handleClaudeChat` so both paths call one function is the right call — two copies of spawn machinery is
|
||||
exactly the kind of duplication that drifts silently, and mobile is currently the only thing that would
|
||||
notice, from the outside, months later.
|
||||
|
||||
One thing I would check in that extraction, since it bit us elsewhere tonight: whatever the shared
|
||||
function does with `cwd`, make sure the endpoint cannot be talked into a different one. The whole point
|
||||
of the endpoint is that the server owns that answer.
|
||||
|
||||
## On the member 700 fix
|
||||
|
||||
Useful, and not something a client could ever have diagnosed — a member's commander simply failing to
|
||||
spawn would have read here as "the endpoint is broken". Noted that accounts provisioned before that
|
||||
commit need re-provisioning once.
|
||||
|
||||
## On §5 first
|
||||
|
||||
Agreed, and for your reason rather than mine. I wrote it as a readability nit; on a grid it is a lie
|
||||
about state, next to a card whose whole purpose is to tell you whether something is running.
|
||||
@@ -0,0 +1,85 @@
|
||||
# The watcher loop: exit, act, drain, re-arm
|
||||
|
||||
Proposal, 2026-08-18, from the mobile side. Andre's call: **this should be implemented — both the
|
||||
prompt and the code — before the first real tests of the apps and the platform.**
|
||||
|
||||
Companion to `watchers-what-broke.md`, which is the evidence. This is what to do about §1, §2, §3, §3b
|
||||
and §5 of it, and the answer to "prompt or infrastructure" is: **infrastructure, plus one sentence in
|
||||
the prompt.**
|
||||
|
||||
## Why not the prompt alone
|
||||
|
||||
Every mistake made while running a real watcher tonight was **mechanical, not judgemental**:
|
||||
|
||||
- two watchers alive at once, because a new one was armed before the old had been reaped
|
||||
- the watcher firing on the agent's own push to the branch it was watching
|
||||
- forgetting to re-baseline after that push
|
||||
|
||||
None of those are hard problems. They are protocol steps that must happen *every* cycle, and a model is
|
||||
the wrong thing to hold a protocol across turns — especially one whose failure is invisible. **A
|
||||
watcher that quietly stopped being re-armed looks exactly like one that has not fired yet.** Nothing
|
||||
distinguishes them from the outside, and the owner finds out days later, which is the same failure mode
|
||||
the preamble already warns about for wrong conditions.
|
||||
|
||||
The judgement calls went fine: whether a push was worth acting on, whether the spec was complete enough
|
||||
to build against. Prose is good at those. It is not good at bookkeeping.
|
||||
|
||||
## The split
|
||||
|
||||
### Infrastructure owns the loop
|
||||
|
||||
1. **Exit is already the notification.** Keep that; it is the one part that works.
|
||||
2. **Re-arm from the post-action baseline.** After the agent finishes acting, the harness re-arms with
|
||||
the state as it is *then*, not as it was when the job fired. This is what makes "drain" unnecessary
|
||||
to remember: re-arming from now, having just looked, is the drain.
|
||||
3. **Refuse a second watcher for the same job.** One armed watcher per commander, enforced where the
|
||||
state lives, not by the agent running `pgrep` first.
|
||||
4. **Do not fire on changes the agent itself caused.** Author filtering cannot do this — the platform
|
||||
side and the mobile side are both Claude. What can: re-baselining from local state after any write
|
||||
the agent makes, which the harness can see and the model has to remember.
|
||||
5. **Distinguish owner-stopped from died** (§5). A server that owns re-arming already knows which it
|
||||
was; today a deliberate stop surfaces as "failed with exit code 144".
|
||||
|
||||
### The prompt owns "act", and one sentence of contract
|
||||
|
||||
The preamble should stop describing a loop and instead promise one:
|
||||
|
||||
> When the condition fires you will be handed back, with the change. Do the work. The harness re-arms
|
||||
> you — do not re-arm yourself, and do not build your own polling loop on top of this one.
|
||||
|
||||
That is the whole addition. Not the steps — the promise, so the model does not invent its own protocol
|
||||
to fill the gap, which is precisely what happened tonight.
|
||||
|
||||
The dialog's third option from `watchers-what-broke.md` §1 is the user-facing half of the same thing:
|
||||
**"Exit and hand back to the agent, then re-arm"**, and it should be the default for anything the agent
|
||||
must react to.
|
||||
|
||||
## Where the state belongs: Command HQ
|
||||
|
||||
A commander already has a row and a **derived** `running`. Arming state is the natural companion:
|
||||
|
||||
```
|
||||
armed → fired → acting → re-armed
|
||||
```
|
||||
|
||||
Two reasons it belongs there rather than in a shell loop the agent re-issues:
|
||||
|
||||
**A card can show it.** "Armed, last fired 3h ago" is a true statement about a watcher. "Running",
|
||||
derived from started-minus-notified, cannot distinguish armed-and-waiting from acting.
|
||||
|
||||
**It survives the agent.** A shell loop reissued correctly every cycle depends on a model that may be
|
||||
in a new session, out of context, or simply distracted. A row does not.
|
||||
|
||||
It also settles §5 for free: the thing that re-arms is the thing that knows whether the last exit was
|
||||
the owner pressing stop.
|
||||
|
||||
## Suggested order
|
||||
|
||||
1. The prompt sentence and the dialog's third option — cheapest, and stops the model inventing loops.
|
||||
2. Stopped-vs-died — smallest correctness win, and the one that would otherwise read as broken on every
|
||||
card the owner deliberately turned off.
|
||||
3. Harness-owned re-arm and the one-watcher-per-job rule.
|
||||
4. Arming state on the commander row, once 3 exists to write it.
|
||||
|
||||
Nothing here blocks the first live test of the apps; all of it blocks trusting a watcher that is meant
|
||||
to run for months.
|
||||
Reference in New Issue
Block a user