fleetd #365: a reply reports whether anything was waiting for it
The REST reply endpoint's response shape changed: 'delivered' is no longer always true, and an 'outcome' field names which of three things happened. 15-REST-API-Reference.md still documented the old constant. Adds the Features entry the charter requires for a visible behaviour change, covering both doors and the nudge-counter rename (delivered -> sent).
+44
@@ -83,6 +83,7 @@ six weeks, and the table alone will not carry it.
|
||||
| [The model opencode actually ran is read back and checked](#the-model-opencode-actually-ran-is-read-back-and-checked) | automatic (opencode profiles) | #175 | `member/OpenCodeSessionDiscovery`, `member/OpenCodeLauncher` |
|
||||
| [Every file a member must read follows the member's own user](#every-file-a-member-must-read-follows-the-members-own-user) | `memberHerdrSocket:` + `worktreeGroup:` | #222 #224 | `member/ClaudeCodeLauncher`, `session/GitWorktrees` |
|
||||
| [A role fleetd cannot bind is refused](#a-role-fleetd-cannot-bind-is-refused-not-quietly-downgraded) | automatic | #123 | `auth/MemberRegistry` |
|
||||
| [A reply says whether anything was waiting for it](#a-reply-says-whether-anything-was-waiting-for-it) | automatic | #365 | `msg/MessageService.ReplyOutcome` |
|
||||
|
||||
Nearly every knob above lives in one file, on one profile:
|
||||
|
||||
@@ -4712,3 +4713,46 @@ its `size()`. It does not count members a second way, so it always agrees with t
|
||||
proof is a live `caffeinate` process, or a member that survives an idle night.
|
||||
|
||||
fleetd #355 / #374.
|
||||
|
||||
## A reply says whether anything was waiting for it
|
||||
|
||||
**What.** `fleet_reply` and `POST /sessions/{id}/reply` now report which of three things happened to
|
||||
a worker's reply, instead of saying "delivered" for all of them:
|
||||
|
||||
| Outcome | What happened | REST `delivered` |
|
||||
|---|---|---|
|
||||
| `resolved_send` | A `fleet_send` or `fleet_ask` was actively waiting, and took the reply now | `true` |
|
||||
| `resolved_async_ticket` | No live waiter, but the reply completed a parked async ticket — a `fleet_poll` caller sees it at once | `true` |
|
||||
| `queued` | Nothing was waiting. The reply is held in the inbox for a later drain | `false` |
|
||||
|
||||
Over MCP the tool result carries the wording, for example `queued — no send or ticket was waiting;
|
||||
held in the inbox for a later drain`. Over REST the body gains an `outcome` field, and `delivered`
|
||||
stops being a constant.
|
||||
|
||||
**The knob.** None. It is how both doors answer now.
|
||||
|
||||
**Why it exists.** All three outcomes are successes, but they are not the same fact, and the caller
|
||||
could not tell them apart. "Delivered" for a reply nobody was waiting for is the report reading
|
||||
better than the state — the same failure this project keeps finding in other places. A lead that
|
||||
sees `queued` knows its send never opened, or had already timed out, which is a real and different
|
||||
situation from a clean handoff. Before this, that difference was visible only in a metrics label
|
||||
nobody reads during a task.
|
||||
|
||||
The nudge counters were renamed in the same change, for the same reason. `fleet_push_nudges_total`
|
||||
and `fleet_lead_heartbeat_nudges_total` used to record an outcome called `delivered`; it is now
|
||||
`sent`. Nothing about the count changed — only the word. That call is a one-way herdr
|
||||
paste-and-submit into a pane, and there is no read-receipt concept at that layer, so "sent" is the
|
||||
most the counter can ever honestly claim.
|
||||
|
||||
**Gotchas.**
|
||||
|
||||
- **`delivered: false` over REST is not an error.** The status is still `200`, and the reply is
|
||||
safely held. Any client that treats `delivered: false` as a failure and retries will queue the
|
||||
reply twice. This is the one behaviour change that can break an existing caller, and it is the
|
||||
reason the `outcome` field exists: branch on `outcome`, not on `delivered`.
|
||||
- **`queued` does not mean the lead will never see it.** It means nothing was waiting *at that
|
||||
moment*. The reply is drainable with `fleet_poll{target}`, and the push loop nudges the lead.
|
||||
- **The wording is not a contract; `outcome` is.** The human-readable text may be reworded. The
|
||||
three `outcome` values (`resolved_send`, `resolved_async_ticket`, `queued`) are the stable names.
|
||||
|
||||
fleetd #365.
|
||||
|
||||
+7
-1
@@ -91,7 +91,13 @@ A herdr-level failure during the blocking send (not the `turnId` path) maps thro
|
||||
Handler: `replyMessage`, `FleetApp.java:554-571`. This is `fleet_reply`'s REST face.
|
||||
|
||||
Body: `{"content": "..."}`. Unlike `sendMessage`, there is no blank check on `content` — a missing
|
||||
or empty value is delivered as `""`. Response: `200 {sessionId, delivered: true}`.
|
||||
or empty value is delivered as `""`. Response: `200 {sessionId, delivered, outcome}`.
|
||||
|
||||
`delivered` used to be unconditionally `true`. Since fleetd #365 it says whether anything was
|
||||
actually waiting: `true` when the reply resolved an open send or a parked async ticket, `false`
|
||||
when it was only queued in the inbox for a later drain. Both are still `200` — a queued reply is
|
||||
a success, not an error. `outcome` names which of the three happened: `resolved_send`,
|
||||
`resolved_async_ticket` or `queued`.
|
||||
|
||||
The path's session id is the caller's own identity claim, and this is the one place that claim is
|
||||
checked over REST — over MCP a worker's identity already comes from its connection, never an
|
||||
|
||||
Reference in New Issue
Block a user