morgana: #1782 design r1 (sage-2481): owner thread claims as a human/reader verb thread.claims, agents unchanged; Console and TUI read c.role, so the briefed mixed list would need them too (Q1, Q2 to Duckworth)
This commit is contained in:
1 parent
c27207f6e6
commit
3e9939b7b1
1 file changed
+151
@@ -0,0 +1,151 @@
|
||||
# #1782: the owner sees a thread agent's claims (design r1)
|
||||
|
||||
Morgana, 2026-10-22, for Duckworth. Brief: docs/plans/2026-10-22_owner-thread-claims.md (Sage, for
|
||||
mos-1262). The brief wasn't committed and had no queue row when this was written, so the folder is named
|
||||
for the brief. It moves to queue-<row> once the row exists. Base: refactor c27207f6e (205 landed at
|
||||
ce6a96ad6, then 268). No code yet.
|
||||
|
||||
## What the code does now
|
||||
|
||||
- A thread run's `role.claim` writes `thread_claims` only and returns (broker.mjs:2682 at ce6a96ad6).
|
||||
- `agents` (broker.mjs:3409) reads `role_claims` only:
|
||||
- human and reader sessions get `#agentState(c)` per held role;
|
||||
- role and tester sessions get the raw rows.
|
||||
- A held thread claim is the thread's latest `thread_claims` row with op `claim`. `#threadHolders`
|
||||
(:483) and `#threadRows` (:624) already compute it that way, with `c.seq = max(seq)` per thread.
|
||||
- `threads.name` is the name a user wrote. `threads.instance` is the role instance from the business
|
||||
file, which the operator writes, not the user.
|
||||
|
||||
## The problem with the brief's shape
|
||||
|
||||
The brief adds thread rows to the human and reader `agents` result, after the role rows. Every consumer
|
||||
of that result assumes one row per role:
|
||||
|
||||
| Consumer | What it reads | What a thread row would do |
|
||||
|---|---|---|
|
||||
| Console Agents view, webui bus.js:474 | `c.role` in a "Held roles" table, `claims.length` as its count | blank role cell, wrong count |
|
||||
| Console board held table, bus.js:447 and :462 `held-count` | same | same |
|
||||
| Console composer, bus.js:298 | `c.role`, filtered with `typeof r === 'string'` | dropped safely |
|
||||
| CLI TUI agents view, screens.mjs:280 and :284, app.mjs:288 and :433 | `c.role` as title and column | blank title |
|
||||
| CLI `mosaic agents`, format.mjs:54 | `c.role` | `undefined` in the line (brief item 4 covers this one) |
|
||||
|
||||
So the shape as briefed also needs the Console (webui) and the TUI changed in this row, or the live
|
||||
Agents view breaks on the next re-pin. The brief's manifest names only format.mjs.
|
||||
|
||||
## Proposed shape: a separate owner verb
|
||||
|
||||
The brief allows this ("for example a separate owner verb, it goes in BUILD.md to Duckworth before
|
||||
code").
|
||||
|
||||
**D1. New verb `thread.claims`**, human and reader sessions only. Every other session refuses with
|
||||
`human-required`, the code `decisions` uses at :3426. No arguments (`keys(a, [], [])`). The result, in
|
||||
thread creation order:
|
||||
|
||||
```js
|
||||
[{ thread, instance, holder_run, harness, at }]
|
||||
```
|
||||
|
||||
- One row per thread whose latest `thread_claims` op is `claim`.
|
||||
- `at` is that claim row's time.
|
||||
- It doesn't return `name`, message content or `address`. `address` matches the brief's four fields;
|
||||
it can be added if Duckworth wants it next to the role rows.
|
||||
- `instance` is beyond the brief's four. It names which assistant holds the thread, and the operator
|
||||
writes it, not the user.
|
||||
|
||||
SQL, the `#threadHolders` join without the instance and owner filter:
|
||||
|
||||
```sql
|
||||
SELECT t.id AS thread, t.instance, c.holder_run, c.harness, c.at FROM threads t
|
||||
JOIN thread_claims c ON c.seq=(SELECT max(seq) FROM thread_claims WHERE thread=t.id)
|
||||
WHERE t.business=? AND c.op='claim' ORDER BY t.seq
|
||||
```
|
||||
|
||||
**D2. `agents` doesn't change** for any session. That is stronger than brief item 2 (role sessions
|
||||
only), and it means the Console, TUI and composer need no change in this row.
|
||||
|
||||
**D3. Reader allow-list.** `thread.claims` goes in the reader list at :2370. The processing gate (:402)
|
||||
already exempts human and reader sessions, so a halt doesn't hide claims. No TESTER_VERBS or USER_VERBS
|
||||
entry is added (see D6).
|
||||
|
||||
**D4. CLI `mosaic agents`** (cli.mjs:314) calls `agents` and then `thread.claims`. `formatAgents(list,
|
||||
threads = [])` prints the role lines unchanged, then for each held thread claim:
|
||||
|
||||
```
|
||||
thread <thread> <instance> <holder_run> <harness> since <at>
|
||||
```
|
||||
|
||||
Each line starts with `thread `, so a reader can tell the two kinds apart (brief item 1). Role output
|
||||
with no thread claims is byte-equal to today, including "agents: no role is claimed". When there are no
|
||||
roles but there are thread claims, the empty-roles line is still printed, then the thread lines.
|
||||
|
||||
**D5. The governance pin.** message-filter.test.mjs, the "claims" item: `threadsState` becomes
|
||||
`call(owner, 'thread.claims')`, which must include `{ thread: U, holder_run: 'assistant-run' }`. The
|
||||
comment that names this row goes away (brief item 5).
|
||||
|
||||
**D6. What this row leaves out**, so as not to widen beyond owner and reader sessions (mos-1202):
|
||||
|
||||
- tester sessions;
|
||||
- FED R3 user principals, who own threads under R3, so their own claims are an R6 question;
|
||||
- a Console section;
|
||||
- the TUI.
|
||||
|
||||
## Questions for Duckworth and Sage
|
||||
|
||||
- **Q1.** Do you accept D1 over the briefed mixed list? If you'd rather have the mixed list, the
|
||||
manifest grows to webui bus.js, screens.mjs and app.mjs, plus their tests (bus-browser,
|
||||
webui bus.test and the TUI tests).
|
||||
- **Q2.** Should the Console show thread claims before a user thread goes live? PRD :1083-1084 promises
|
||||
visibility to the owner, and a Console user won't run `mosaic agents`. My recommendation: a follow-up
|
||||
row bound like this one (it lands before any user thread goes live), so this row stays small for
|
||||
10-27. The alternative is a "Thread claims" section in the Console Agents view in this row.
|
||||
|
||||
## Tests (under D1)
|
||||
|
||||
All new tests go in packages/bus/tests/agents.test.mjs unless named otherwise. Each one runs on a store
|
||||
with one held role claim and one thread run.
|
||||
|
||||
- **T1.** Human `thread.claims` lists the held thread claim (thread, instance, holder_run, harness,
|
||||
at). Reader gets the same result.
|
||||
- **T2.** After release, the thread is gone. After a re-claim followed by a revoke, it's gone again.
|
||||
The thread has an earlier `claim` row, so a first-op reading would still list it.
|
||||
- **T3.** A role session's `thread.claims` refuses `human-required`.
|
||||
- **T4.** `agents` is deepEqual to a copy taken before the thread run claimed, for the role, human
|
||||
and reader sessions on the same store.
|
||||
- **T5.** `JSON.stringify` of the `thread.claims` result has neither the thread's name nor the body of
|
||||
a message sent in that thread.
|
||||
- **T6.** packages/cli/tests/format.test.mjs: a mixed list renders the role lines exactly as today,
|
||||
then `thread …` lines. The empty-roles case with threads is covered too.
|
||||
- **T7.** packages/bus/tests/message-filter.test.mjs: the moved pin (D5).
|
||||
|
||||
## Mutants (each killed by a named test)
|
||||
|
||||
| Mutant | Change | Killed by |
|
||||
|---|---|---|
|
||||
| M1 | `thread.claims` returns `[]` | T1, T7 |
|
||||
| M2 | the held check uses `min(seq)` (first op) | T2 |
|
||||
| M3 | the human/reader check is dropped, so a role session gets thread rows | T3 |
|
||||
| M4 | `agents` for human appends the thread rows | T4 |
|
||||
| M5 | the SELECT adds `t.name` | T5 |
|
||||
| M6 | formatAgents drops the thread lines | T6 |
|
||||
|
||||
## Manifest (expected)
|
||||
|
||||
- packages/bus/src/broker.mjs
|
||||
- packages/bus/README.md (a paragraph for `thread.claims`, next to `agents`)
|
||||
- packages/bus/tests/agents.test.mjs
|
||||
- packages/bus/tests/message-filter.test.mjs
|
||||
- packages/cli/src/cli.mjs
|
||||
- packages/cli/src/format.mjs
|
||||
- packages/cli/tests/format.test.mjs
|
||||
- this BUILD.md
|
||||
|
||||
Verb-list pins checked at c27207f6e:
|
||||
|
||||
- tester-writes.test.mjs:19 pins `TESTER_VERBS` exactly. D6 adds nothing to it, so the pin holds.
|
||||
- No test pins the reader list at :2370.
|
||||
- `thread.messages` appears in tester-threads, threads and webui chat as calls, not as a verb set.
|
||||
|
||||
Before the in-review move, I'll rerun the same grep for `thread.claims` and for any new verb-set pin.
|
||||
|
||||
Gate (brief): Duckworth approves the manifest. On Sage's gate, the touched test files run whole, the
|
||||
bus and cli suites are green, every scripts/test-*.sh is green, and M1-M6 are killed.
|
||||
Reference in new issue
Block a user