diff --git a/agents/filbert/work/queue-59/build.md b/agents/filbert/work/queue-59/build.md new file mode 100644 index 000000000..653eb43ca --- /dev/null +++ b/agents/filbert/work/queue-59/build.md @@ -0,0 +1,174 @@ +# Row 59 (#1548): bus read, agent state per role, round 1 + +Filbert, 2026-10-11. Brief: `docs/plans/2026-10-10_design-implementation.md`, +"Bus read: agent state per role" (blob a360554c). Darkwing and Dewey +review. Sage commits after the gate; nothing is committed, staged or +pushed yet. + +Base f2f2861b. `build.patch` applies there with `git apply --index`; +`candidate-manifest.sha256` pins all 8 files after the patch. + +## Which: state on the bus + +The brief allows either a seat-to-role mapping read from committed data or +state on the bus. I chose state on the bus, because no committed data joins +a seat to a role: + +- The business file's `holder` for a role instance is a display name. It + isn't a seat id the control board knows. +- The bus's claim names a run (`holder_run`), not a seat. A run is one + launch; the next launch of the same role is a different run. + +So a mapping would be a new hand-kept file that goes stale on each launch. +The bus already records everything the state needs: the claim, message +deliveries and decisions. The state is derived at read time. Nothing new is +written and the schema is unchanged. + +## The derivation + +`agents` for a human or reader session returns each held claim row plus: + +- `working`: `{messages, since}` or null. Messages to the role, in the + business, that the current holder received (`delivered`) and hasn't + marked `read`; `since` is the oldest delivery. The runner + (`packages/harness/src/runner.mjs`) receives a batch, takes one turn per + message, replies, then marks each read, so this is "a turn is under way + or queued". +- `inputNeeded`: the role's open decisions routed to the human (`raised_by_role` + is the role, `route_to` is `human`, no resolved, withdrawn or expired + event), gated first and then oldest, the Inbox's order. Each item is + `{id, question, class, blocking, task_ref, at}`; options and the rest of + the decision stay in the Inbox. Seen is still open. +- `state`: `working` if `working` is set, else `waiting` if `inputNeeded` + is nonempty, else `idle`. Working wins, as on the control board + (`scan.mjs`). + +A role session gets the claim rows unchanged, so a role can't read another +role's decisions through `agents`. The CLI's `formatAgents` prints only +role, holder, harness and time, so `mosaic agents` is unchanged; +`--json` carries the new fields. The webui server passes `agents` rows +through, so it has no change. + +Limits, in the bus README: + +- A message routed but not yet received shows idle. +- A reply that fails for a reason other than a gone session leaves its + message unread, so the role stays working. `since` shows for how long. +- No error or offline state is derived. The control board has both; the + bus has no record to derive them from. +- A new holder doesn't inherit the last holder's unread deliveries. + Decisions stay with the role across holders. +- Unheld and released roles aren't listed, as before. + +## The Agents view + +`#/agents` gets State and Attention columns, between Role and Holder: + +- State: the board's mark (`.st-*` from `shell.css`: ● working, ◐ waiting, + – idle) with the word, plus "N messages since