opencode-thrum-pro 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/assets/commands/thrum-compact-extended.md +367 -0
  2. package/assets/commands/thrum-compact.md +301 -0
  3. package/assets/commands/thrum-inbox.md +1 -1
  4. package/assets/commands/thrum-restart-extended.md +85 -16
  5. package/assets/commands/thrum-restart.md +106 -14
  6. package/assets/commands/thrum-send.md +4 -1
  7. package/assets/commands/thrum-sleep-extended.md +68 -51
  8. package/assets/commands/thrum-sleep.md +68 -63
  9. package/assets/commands/thrum-update-project.md +1 -1
  10. package/assets/skills/adversarial-critique/SKILL.md +5 -8
  11. package/assets/skills/brainstorm-steward/SKILL.md +65 -11
  12. package/assets/skills/brainstormer-staying-current/SKILL.md +1 -4
  13. package/assets/skills/choosing-subagent-models/SKILL.md +200 -70
  14. package/assets/skills/closing-findings-while-warm/SKILL.md +1 -2
  15. package/assets/skills/configure-roles/SKILL.md +2 -2
  16. package/assets/skills/conversation-only-context/SKILL.md +11 -11
  17. package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
  18. package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
  19. package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
  20. package/assets/skills/coordinator-branch-split-on-block/SKILL.md +1 -2
  21. package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
  22. package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
  23. package/assets/skills/coordinator-dispatching-work/SKILL.md +26 -14
  24. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +65 -21
  25. package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +1 -2
  26. package/assets/skills/coordinator-maintaining-memory/SKILL.md +1 -1
  27. package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +7 -8
  28. package/assets/skills/coordinator-merging-code/SKILL.md +43 -22
  29. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +29 -9
  30. package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +4 -5
  31. package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
  32. package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
  33. package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +25 -25
  34. package/assets/skills/coordinator-running-review-cycles/SKILL.md +16 -6
  35. package/assets/{commands/thrum-_deviations-protocol.md → skills/deviations-protocol/SKILL.md} +6 -12
  36. package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
  37. package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -3
  38. package/assets/skills/generator-idempotency-protocol/SKILL.md +9 -0
  39. package/assets/skills/implementer-maintaining-memory/SKILL.md +3 -4
  40. package/assets/skills/implementer-orchestrating-execution/SKILL.md +12 -16
  41. package/assets/skills/implementer-receiving-dispatch/SKILL.md +15 -17
  42. package/assets/skills/implementer-receiving-review-feedback/SKILL.md +3 -4
  43. package/assets/skills/implementer-status-and-handoff/SKILL.md +1 -14
  44. package/assets/skills/implementer-tdd-and-quality/SKILL.md +2 -2
  45. package/assets/skills/memory-curate/SKILL.md +1 -1
  46. package/assets/skills/memory-write-discipline/SKILL.md +1 -1
  47. package/assets/skills/orchestrate/SKILL.md +77 -36
  48. package/assets/skills/persistent-watcher-archetype/SKILL.md +172 -25
  49. package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
  50. package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
  51. package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
  52. package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
  53. package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
  54. package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
  55. package/assets/skills/prime-agent/SKILL.md +10 -0
  56. package/assets/skills/project-hotpath-gate/SKILL.md +1 -2
  57. package/assets/skills/project-philosophy/SKILL.md +1 -2
  58. package/assets/skills/project-rules-protocol/SKILL.md +12 -0
  59. package/assets/skills/project-setup/SKILL.md +27 -22
  60. package/assets/skills/project-setup/resources/implementation-agent.md +55 -35
  61. package/assets/skills/researcher-answering-queries/SKILL.md +1 -2
  62. package/assets/skills/researcher-investigating/SKILL.md +8 -13
  63. package/assets/skills/researcher-maintaining-memory/SKILL.md +1 -1
  64. package/assets/{commands/thrum-_snapshot-protocol.md → skills/snapshot-protocol/SKILL.md} +27 -21
  65. package/assets/{commands/thrum-_stamp-protocol.md → skills/stamp-protocol/SKILL.md} +14 -8
  66. package/assets/skills/thrum/SKILL.md +14 -7
  67. package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
  68. package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
  69. package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
  70. package/assets/skills/thrum/resources/MESSAGING.md +18 -11
  71. package/assets/skills/thrum/resources/WORKTREES.md +1 -1
  72. package/assets/skills/thrum-memory/SKILL.md +1 -1
  73. package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
  74. package/assets/skills/update-queue/SKILL.md +6 -8
  75. package/assets/skills/using-the-dashboard/SKILL.md +30 -0
  76. package/assets/skills/using-the-queue/SKILL.md +76 -5
  77. package/assets/skills/using-thrum-state/SKILL.md +85 -2
  78. package/assets/skills/verify-against-plan/SKILL.md +122 -7
  79. package/assets/skills/watcher-archetype/SKILL.md +18 -0
  80. package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
  81. package/dist/installer-assets.test.js +41 -0
  82. package/dist/installer.js +4 -4
  83. package/dist/permission-fixture.integration.test.js +4 -4
  84. package/package.json +1 -1
  85. package/assets/commands/thrum-_generator-idempotency-protocol.md +0 -15
  86. package/assets/commands/thrum-_project-rules-protocol.md +0 -18
  87. package/assets/commands/thrum-_verify-before-write-protocol.md +0 -27
  88. package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
  89. package/dist/allowlist-sync.test.js +0 -44
  90. /package/dist/{allowlist-sync.test.d.ts → installer-assets.test.d.ts} +0 -0
@@ -9,8 +9,11 @@
9
9
  3. **Receive** — `thrum inbox` or `thrum wait` (blocking)
10
10
  4. **Verify sent state** — `thrum sent` or `thrum message get <msg-id>`
11
11
  5. **Read** — Auto-marked read when displayed via `thrum inbox`; use
12
- `thrum inbox --unread` to peek without marking. Explicit:
13
- `thrum message read --all`
12
+ `thrum inbox --unread` to peek without marking. Explicit: `thrum message
13
+ read <id> [<id>...]`, or `thrum message read --all` (DRY RUN — lists
14
+ candidates, marks nothing) then `--apply` (marks the shown/seen ones) or
15
+ `--force` (marks ALL unread, seen or not; needs a fresh `--all` dry run
16
+ first).
14
17
  6. **Search — do not page through the inbox to find old messages.** The
15
18
  default page is 10, newest-first, so stale unread sorts LAST exactly when
16
19
  it has waited longest. Use `thrum message search "<term>"` (full-text,
@@ -22,15 +25,19 @@
22
25
 
23
26
  ## Addressing
24
27
 
25
- | Target | Routing | When to use |
26
- | ------------------- | ------------------------------------------------------------- | ------------------------------------ |
27
- | `--to @lead_agent` | **Direct** — routes to the named agent | Default for all task messages |
28
- | `--to @coordinator` | **Role fanout** — ALL agents with that role (warning emitted) | Only when you want every coordinator |
29
- | `--to @everyone` | **Broadcast** — all registered agents | Critical alerts |
30
-
31
- **Critical:** `@coordinator` is a role, not an agent name. Sending
32
- `--to @coordinator` fans out to every agent registered with that role. Use
33
- `thrum team` to find agent names, then send `--to @<name>` for direct messages.
28
+ | Target | Routing | When to use |
29
+ | ------------------------------ | ------------------------------------------------- | -------------------------------- |
30
+ | `--to @lead_agent` | **Direct** — routes to the named agent | Default for all task messages |
31
+ | `--to @agent_a --to @agent_b` | **Explicit multi-recipient** — repeatable `--to` | Several specific named agents |
32
+
33
+ **(P0 ruling):** one-to-many fan-out — `--broadcast`,
34
+ `@everyone`, and role-based mentions/groups (e.g. `--to @coordinator` fanning
35
+ out to every agent with that role) — was removed by design across the whole
36
+ product. Do not restore it. `--to` and `--mention` only resolve exact agent
37
+ IDs now; a bare role name that is not also an agent ID matches nobody. Use
38
+ `thrum team` to find agent names, then send `--to @<name>` for direct
39
+ messages, or address a scoped agent-type group for a fixed, explicit set of
40
+ recipients.
34
41
 
35
42
  - **Reply:** `thrum reply <msg-id>` — same audience as original; takes **no
36
43
  `--to`** (recipient derived from the message)
@@ -86,7 +86,7 @@ thrum sent # Verifies what this worktree sent and who read it
86
86
  thrum reply <msg-id> --stdin <<'EOF'
87
87
  Integration tests passing, ready to merge
88
88
  EOF
89
- thrum message read --all # Mark all messages as read
89
+ thrum message read --all --force # Mark ALL unread as read (run `read --all` alone first — dry run)
90
90
  ```
91
91
 
92
92
  ## File Coordination
@@ -59,7 +59,7 @@ thrum memory search --tag substrate --kind arch_decision
59
59
  Show a single memory by ID:
60
60
 
61
61
  ```bash
62
- thrum memory show thrum-mem-01HQXZP3V8K2N7M0YJWQ8R5T6F
62
+ thrum memory show thrum-mem-01EXAMPLE00000000000000000
63
63
  ```
64
64
 
65
65
  Edit a single field (other zoom levels stay intact; only changed-zoom embeddings
@@ -1,7 +1,7 @@
1
1
  # Sweep agent — shared instructions
2
2
 
3
3
  > Dispatcher: write this file once, then give each agent ONLY its item list and its report path. Keeps dispatches short and identical.
4
- > **Model:** sonnet · **Effort:** medium. The verdict call needs judgement; the searching does not.
4
+ > **Model and effort:** per `choosing-subagent-models`. The verdict call needs judgement; the searching does not.
5
5
 
6
6
  ---
7
7
 
@@ -20,15 +20,13 @@ never a write to their queue (see the `using-the-queue` skill).
20
20
 
21
21
  ```bash
22
22
  thrum queue list
23
- thrum queue list | awk -F'\t' 'NF>1{print $2}' | sort | uniq -c # status counts
23
+ thrum queue list --json | jq -r '.bundles[].status' | sort | uniq -c # status counts
24
24
  ```
25
25
 
26
26
  Read the whole list yourself. The status counts are the before-number to
27
- reconcile against and report the delta from. `-F'\t'` is load-bearing: bundle
28
- rows are tab-delimited, but `queue list` also prints a space-delimited footer
29
- (`Oldest bundle updated …`) to stdout — under default whitespace splitting that
30
- footer's second word lands in the status tally and corrupts the denominator.
31
- The tab delimiter drops it (footer has no tab ⇒ NF=1).
27
+ reconcile against and report the delta from. Use `--json` for the tally, not
28
+ the text table: the JSON machine surface (`{"bundles": [...]}`) is a stable
29
+ contract, while the text table's column layout and footer line are not.
32
30
 
33
31
  Two staleness classes to expect:
34
32
 
@@ -112,7 +110,7 @@ Give each sub-agent:
112
110
  `merge-base`, `status` ONLY — never `checkout`/`reset`/`restore`/`stash`/
113
111
  `clean`/`rebase`/`commit`/`push`, in any directory; never enter another
114
112
  worktree.
115
- - pinned `model:` (sonnet; intended tier is low for mechanical lookups — the
113
+ - pinned `model:` (per `choosing-subagent-models`; a cheap tier for mechanical lookups — the
116
114
  Agent tool takes no `effort`, so carry the tier via the agent definition or
117
115
  the tmux launch flags).
118
116
 
@@ -135,7 +133,7 @@ on a guess.
135
133
  Re-run the status counts and report the delta:
136
134
 
137
135
  ```bash
138
- thrum queue list | awk -F'\t' 'NF>1{print $2}' | sort | uniq -c
136
+ thrum queue list --json | jq -r '.bundles[].status' | sort | uniq -c
139
137
  ```
140
138
 
141
139
  One line: dropped N, added M, reclassified K; note anything left deliberately
@@ -126,6 +126,36 @@ resubmit — do not recompute the whole board inside one invocation.
126
126
  - HTML files: no script/style/iframe/event-handlers — the render side strips
127
127
  them, but author clean anyway.
128
128
 
129
+ ## Widget schema
130
+
131
+ ```json
132
+ {"title": "<board title>", "widgets": [
133
+ {"id": "hdr", "type": "stat-row", "stats": [{"label": "<label>", "value": "<value>"}]},
134
+ {"id": "rows", "type": "list", "title": "<list title>", "rows": [{"id": "r0", "text": "<one-line markdown>"}]},
135
+ {"id": "note", "type": "card", "title": "<card title>", "body": "<markdown>"},
136
+ {"id": "grp", "type": "section", "label": "<section label>"}
137
+ ]}
138
+ ```
139
+
140
+ - `stat-row` takes `stats`, `list` takes `rows` (each row has its own `id`), `card` takes
141
+ `title` + `body`, `section` takes `label`. Widget `id`s are the upsert key — keep them stable.
142
+ - Write the document to a file; `thrum dashboard set --doc-file <f>`. Pass `--replace` when one
143
+ generator owns the whole board.
144
+ - Regenerate the board from a script that prints one summary line, run from the agent's own
145
+ session. A daemon-spawned monitor process is an unauthenticated caller (identity is
146
+ kernel-verified per process) and cannot write the board, whatever environment it carries, so
147
+ schedule the monitor as a reminder (`--schedule`, `--match`, `--notify-on-success`) and run
148
+ the generator when the reminder arrives. The cron schedule is evaluated in the daemon's local
149
+ time zone, not UTC.
150
+
151
+ ## The board is for the human reader, not an audit trail
152
+
153
+ - Show what the reader needs to decide or know; filter out coordinator mechanics.
154
+ - A "landed since deploy" list shows feature/fix branch landings only — no trunk-sync merges,
155
+ pull merges, or artifact commits.
156
+ - An "orchestrators" view shows each orchestrator's open work, not liveness.
157
+ - When the owner asks for a change, change the generator, not the snapshot.
158
+
129
159
  ## When to reach for the dashboard (vs a nudge vs a message)
130
160
 
131
161
  - **Dashboard:** state the human will consume VISUALLY and possibly answer
@@ -63,22 +63,46 @@ Three distinct fields, do not conflate them:
63
63
  | `thrum queue add --title "..."` | Create a bundle in your queue |
64
64
  | `thrum queue add --from-message <msg_id>` | Special case: lift ONE specific message you received (content->title, author->assigned_by, refs->refs). Use the exact msg-id you're acting on - never head-1 of the inbox |
65
65
  | `thrum queue add --ref bead:<id>` | Promote a backlog item into active work |
66
- | `thrum queue list [--status <s>] [--agent <id>]` | List your queue (or another agent's) |
66
+ | `thrum queue list [--status <s>] [--stage <s>] [--agent <id>]` | List your queue (or another agent's); `--status`/`--stage` AND-combine when both given |
67
67
  | `thrum queue show <bundle-id> [--agent <id>]` | Full detail for one bundle |
68
68
  | `thrum queue start <bundle-id>` | -> in_progress (clears block_reason) |
69
- | `thrum queue block <bundle-id> [--reason "..."]` | -> blocked (records the reason) |
69
+ | `thrum queue block <bundle-id> [--reason "..."]` | -> blocked (records the reason; `--reason` required) |
70
70
  | `thrum queue done <bundle-id>` | -> done (TEMPORARY waypoint, not deleted; `start` reopens it; clears block_reason) |
71
+ | `thrum queue wait <bundle-id> [--reason "..."]` | -> waiting (alias for `queue update --status=waiting`; `--reason` optional) |
72
+ | `thrum queue ready <bundle-id>` | -> ready (alias for `queue update --status=ready`) |
73
+ | `thrum queue update <bundle-id> [flags]` | General partial update: `--status`, `--stage`, `--reason` (status_reason), `--title`, `--ref` (repeatable, replaces existing refs), `--priority`, `--assigned-by`, `--assigned-to-remove` - at least one required; `--status=blocked` requires `--reason` |
71
74
  | `thrum queue drop <bundle-id>` | DELETE bundle + all its items, any status (the one destructive verb - drop promptly once a done bundle's record stops being useful) |
72
75
  | `thrum queue assign <bundle-id> <agent-id>...` | Append agent(s) to assigned_to (routing metadata only) |
76
+ | `thrum queue note <bundle-id> --stdin` (or `--body-file <f>`) | Append a dated progress note (append-only; shown by `show`, last note previewed by `list`) |
73
77
  | `thrum queue item add <bundle-id> --title "..."` | Add one embedded item |
74
78
  | `thrum queue item batch-add <bundle-id> --title "..." --title "..."` | Add several items in one call (shared --ref/--priority) |
75
79
  | `thrum queue item start/block/done <bundle-id> <item-id>` | Item-level status (start/done clear the item's block_reason) |
80
+ | `thrum queue item update <bundle-id> <item-id> [flags]` | Partial item update: `--title`, `--assigned-by`, `--ref` (repeatable, replaces existing refs), `--priority` - at least one required |
76
81
 
77
82
  Optional: `--ref <type>:<value>` (repeatable), `--priority N` (higher sorts
78
83
  first), and `--from-message` (special case, below). `add` / `item add` require at
79
84
  least one of `--title` or `--from-message`; explicit `--title` wins over
80
85
  `--from-message`. **`--title` is the normal way to add work.**
81
86
 
87
+ ### Status and stage: two independent axes
88
+
89
+ A bundle carries two orthogonal fields, both settable via `queue update`:
90
+
91
+ - **`status`** - `pending`, `in_progress`, `waiting`, `ready`, `blocked`,
92
+ `done`. Says whether the owning agent can or must act now. `start` / `block`
93
+ / `done` / `wait` / `ready` are all just `queue update --status=<x>` under a
94
+ short name; `wait` and `ready` are exact aliases with no independent
95
+ behavior of their own.
96
+ - **`stage`** - `intake`, `planning`, `dispatched`, `implementation`,
97
+ `integration`, `review`, `rework`, `gate`, `merge`, `deploy`,
98
+ `verification`, `cleanup`. Says where the bundle sits in its workflow,
99
+ independent of status. Optional; never set on items, bundle-only. Set/read
100
+ via `queue update --stage <s>` / `queue list --stage <s>` / `queue show`.
101
+
102
+ `status_reason` (set via `--reason` on `update`/`block`/`wait`) is the
103
+ general reason field; `--status=blocked` requires it, `wait` accepts it
104
+ optionally, other transitions clear it.
105
+
82
106
  ## Common Flows
83
107
 
84
108
  **Add work you're taking on (the normal case):**
@@ -139,6 +163,50 @@ close only the bundles a check proves done, and skip the rest. Two checks only:
139
163
  Do NOT read each bundle's full history - the sweep is those two checks against
140
164
  merged/closed state, nothing more.
141
165
 
166
+ ## Identity-field mutation: queue vs. state (two intentional shapes)
167
+
168
+ `queue update` / `queue item update` and `state set` handle identity-field
169
+ mutation differently. This is an intentional two-shape convention chosen per
170
+ domain, not an inconsistency - do not "fix" one to match the other.
171
+
172
+ **`queue.update` / `queue.item.update`**: wrapped by `RejectIdentityKeysHandler`,
173
+ which returns a typed `*queue.ErrImmutableField` error (`queue: cannot modify
174
+ immutable field %q`) if a raw identity key (`id`, `agent_id`, `created_at` for
175
+ bundles; `id`, `created_at` for items) appears in the wire payload at all -
176
+ checked BEFORE decode, fail-closed. Note: `QueueUpdateRequest`/
177
+ `QueueItemUpdateRequest` have no JSON fields for these identity keys anyway,
178
+ so this wrapper is defense-in-depth, not load-bearing on its own.
179
+
180
+ **`state.set`**: no reject path at all, and that is by design, per field:
181
+ - **Kind/Scope** are the addressing key - a different `(Kind,Scope)` pair
182
+ addresses a DIFFERENT record, so setting them isn't a mutation attempt and
183
+ there's nothing to reject.
184
+ - **Class** is derived server-side from the kind registry; no wire field
185
+ exists for a caller to even attempt to set it.
186
+ - **CreatedAt** is server-stamped once at creation (on the fleet tier it's
187
+ actually derived from `UpdatedAt` at read time rather than stored
188
+ independently); not client-spoofable, and `StateSetRequest` has no field to
189
+ smuggle it through.
190
+ - **EstablishedBy** is influenceable only via `CallerAgentID` on a
191
+ non-peercred transport (browser/WS, unit tests) - but that is a general,
192
+ already-documented property of the RPC layer (identity trust depends on
193
+ transport), not something unique to `state.set`, and it is explicitly out
194
+ of scope per `docs/security_model/master.md` §1.5: "Thrum does not attempt
195
+ to prevent agent identity forgery" is an owner ruling, stated as
196
+ [INTENDED] and permanent, not tracked as debt.
197
+
198
+ **The convention, stated plainly:** queue rejects raw identity keys at the
199
+ wire as defense-in-depth (even though its request types don't have those
200
+ fields anyway); state has no reject path because its identity/system-derived
201
+ fields are either addressing semantics (not a mutation) or server-derived/
202
+ unspoofable (nothing to reject). Two different but each internally-consistent
203
+ shapes, chosen by domain, not an oversight.
204
+
205
+ Non-goal: fail-closed reject-symmetry for `state.set` (mirroring queue's
206
+ `RejectIdentityKeysHandler`) is a possible future non-security hardening
207
+ preference, not built here - out of scope per `docs/security_model/master.md`
208
+ §1.5.
209
+
142
210
  ## Common Mistakes
143
211
 
144
212
  - **Treating `add`/`done` as the work itself.** The queue *tracks* your work; it
@@ -162,9 +230,12 @@ merged/closed state, nothing more.
162
230
  bundle stays, and only `drop` deletes it. Drop each done bundle promptly once
163
231
  its record stops being useful; a queue full of undropped done bundles is the
164
232
  primary rot mode.
165
- - **Reaching for a `remove`/`rename`/item-`drop` verb.** They do not exist by
166
- design: removal happens only at the bundle level via `drop`; titles are set at
167
- creation and not edited.
233
+ - **Reaching for a `remove`/item-`drop` verb.** Removal (of a bundle and every
234
+ item inside it) happens only at the bundle level via `drop` - there is no
235
+ finer-grained delete. Renaming/re-titling is NOT in this category: `queue
236
+ update --title` and `queue item update --title` are the supported post-create
237
+ edit path for a bundle's or item's title (and other fields - stage, refs,
238
+ priority, assigned-by).
168
239
  - **Trying to edit another agent's queue.** Impossible by design - you only
169
240
  write your own. Cross-agent work is messaging plus the recipient's own `add`.
170
241
  - **Treating the queue as durable/shared history.** It is local, private, and
@@ -42,8 +42,8 @@ time** — writing a fleet row without a fresh by-effect check, even to
42
42
  ## Reconcile, don't just read
43
43
 
44
44
  - **Agent-local** (all roles): check at session start
45
- (`thrum state list`), update as you go. Carried-over state is NOT
46
- auto-injected into prime — you must ask.
45
+ (`thrum state list`), update as you go. Prime injects only a capped digest
46
+ of agent-local state; `thrum state list` shows all.
47
47
  - **Fleet** (coordinators write; everyone reads): before citing a fleet
48
48
  fact in a decision, check `as_of`/`method`. Past the kind's threshold
49
49
  (`thrum state describe <kind>`)? Re-verify by effect before acting, or
@@ -71,6 +71,48 @@ when you're stating it without a live check; a write relayed from a message
71
71
  is forced to `--method relayed` (low-trust) — never choose `relayed`
72
72
  yourself.
73
73
 
74
+ ## Field Mutability Matrix
75
+
76
+ `Entry` (`internal/state/types.go`) has five mutability classes, not one
77
+ uniform "immutable field" guard:
78
+
79
+ - **immutable-identity** — part of the `(Kind, Scope)` addressing key, or
80
+ fixed at creation with no update path. "Changing" one of these isn't an
81
+ edit, it addresses a different record.
82
+ - **mutable-whole-object** — caller-settable via `state set`, which is
83
+ always a full upsert (overwrite), never a field-level PATCH. Need a
84
+ partial update? GET, edit client-side, SET the full value back.
85
+ - **mutable** — caller-settable via specific `state set` flags,
86
+ independent of Value.
87
+ - **system-derived** — recomputed server-side from non-caller input
88
+ (registry declaration, resolved caller identity); any caller-supplied
89
+ value for these is ignored, not merely defaulted.
90
+ - **derived** — set only as an automatic side effect of a `state set`
91
+ call, never independently caller-directed.
92
+
93
+ | Field | Class | How it's set/changed | Notes |
94
+ |---|---|---|---|
95
+ | `Kind` | immutable-identity | Parsed from the CLI/RPC target at call time | Part of the addressing key; a new `Kind` is a new record, not an edit. |
96
+ | `Scope` | immutable-identity | Parsed from the CLI/RPC target at call time | Same addressing-key reasoning as `Kind`. No error on "change" — it creates an independent entry (`TestStateHandler_HandleSet_DifferentScope_CreatesIndependentEntry`). |
97
+ | `Value` | mutable-whole-object | `state set --value` | Every `state set` is a full upsert; no field-level PATCH RPC/CLI surface exists. |
98
+ | `AsOf` | mutable (fleet only) | `state set --as-of` | Server only checks that it *parses* as a timestamp; nothing compares it against the previous stored `AsOf` or rejects a bump with no real change — a documented, deliberately unenforced soft-gap. |
99
+ | `Method` | mutable (fleet only) | `state set --method` | Forced to `relayed` server-side whenever the request carries `FromMessage`, overriding any caller-claimed value. Outside that one rule, a caller-supplied `Method` is accepted as-is with no check that it reflects how the value was actually established. |
100
+ | `VerifyCmd` | mutable (fleet only) | `state set --verify-cmd` | Plain caller-settable field. |
101
+ | `FreshnessOwner` | mutable (fleet only) | `state set --freshness-owner` | Plain caller-settable field. |
102
+ | `OriginDaemon` | system-derived | Stamped by sync/durable-lane plumbing | Exact assignment site outside `internal/state` is unconfirmed; not caller-settable via `state set`. |
103
+ | `Class` | system-derived, immutable-per-caller | Declared per-`Kind` in the code registry (`registry.go` `KindDef.Class`), stamped server-side on every write | `StateSetRequest` has no `class` wire field at all — a raw request that smuggles an unrecognized `class` key is silently dropped, not rejected (`TestStateHandler_HandleSet_ClassFieldOnWireIsSilentlyDropped`). Changes only if the registry declaration itself changes. |
104
+ | `EstablishedBy` | system-derived, immutable-per-caller | Re-resolved from the caller's identity on every write (`resolveCallerAgent` / `guard.DaemonResolve`), fleet tier only | No dedicated wire field to set directly — only `CallerAgentID`, fed through identity resolution, can influence it. On peercred transports the claim is verified against kernel evidence; on non-peercred transports (browser/WS, unit tests) an unverified claim is accepted verbatim. Local tier never populates this field (always `""`). |
105
+ | `CreatedAt` | immutable-identity (local tier only) | Stamped once at first insert | Local tier: preserved across updates by the store's rewrite path (`local_store.go` `Set`), not validated/rejected — it's simply never reassigned. Fleet tier: **not** independently preserved — there's no separate `created_at` column, so `fleet_store.go`'s `toEntry()` derives it from `UpdatedAt` on every read; do not assume local/fleet parity here. |
106
+ | `UpdatedAt` | derived | Automatic side effect of every `state set` | Never independently caller-directed. |
107
+
108
+ None of this is a uniform "reject the write" guard (that mechanism —
109
+ `RejectIdentityKeysHandler` / `ErrImmutableField` — belongs to `thrum queue`,
110
+ a different subsystem, not state). State's actual enforcement is a mix:
111
+ addressing semantics for the identity fields (no error, just a different
112
+ record), silent wire-level drop for `Class`/`EstablishedBy` (no field to
113
+ send them on), and preserve-on-rewrite for local-tier `CreatedAt` (no
114
+ validation, just never overwritten).
115
+
74
116
  ## Common Flows
75
117
 
76
118
  **Deploy runbook, after verifying a box's serving SHA:**
@@ -91,6 +133,47 @@ thrum state list
91
133
  effect, then `thrum state set` with the fresh value and `--method
92
134
  by_effect`. Never "correct" a row you have not personally re-measured.
93
135
 
136
+ ## Consolidating your agent-harness memories into state (durable overflow)
137
+
138
+ Your **agent-harness memories** — the memory index your runtime auto-loads at
139
+ session start (each runtime has its own: a `MEMORY.md`-style index on some,
140
+ an equivalent auto-injected file on Codex/Cursor/Copilot/others) — has a **load
141
+ size cap**. Past that cap the runtime **silently drops the tail entries** at
142
+ wake: they are invisible exactly when you most need them. Thrum state is an
143
+ uncapped, queryable durable store, so it is the right overflow home.
144
+
145
+ **Technique (scope-preserving — the only safe way to shrink the index):**
146
+
147
+ 1. **Classify, don't cut.** For each auto-loaded entry ask: *if this were
148
+ absent at wake, would I take a WRONG action, or merely a SLOWER one?*
149
+ WRONG-if-absent (destructive-op guards, owner rulings, disbelieve-the-
150
+ instrument traps, habits that must interrupt a confident wrong answer) STAY
151
+ in the auto-loaded index. SLOWER-if-absent (things you would just look up
152
+ once you know the topic exists) are demotable.
153
+ 2. **Demote by MOVING, never by shortening.** Relocate each demotable entry
154
+ **verbatim** into a thrum state entry (e.g. `thrum state set --kind
155
+ reference --scope <agent>_discipline_index`) and/or your runtime's secondary
156
+ lookup file. Leave a short **bidirectional pointer**: the auto-loaded index
157
+ keeps a one-line pointer to the state entry; the state entry names the topic
158
+ file the full detail lives in. **Never merge two entries, summarise to save
159
+ bytes, or delete** — summarising discards the SCOPE that makes an entry true,
160
+ which is the whole failure this guards against. The per-topic detail files
161
+ are never touched.
162
+ 3. **Reduction comes only from relocation.** Prove conservation:
163
+ `entries_removed_from_index == entries_added_to_state` (set equality, not
164
+ just count). Target comfortably under the load cap; do not chase a smaller
165
+ number by demoting genuine must-fire entries.
166
+ 4. **Use a high-reasoning subagent to index both stores** and PROPOSE the moves
167
+ (it produces the new index, the state value, and a verification checklist);
168
+ you review the judgment calls (which must-fire entries to keep) and apply.
169
+ Watch for **line-packing collateral**: if two entries share one physical
170
+ line, demoting one must not drag its line-mate out with it.
171
+ 5. **Refuse the size-nag hook.** A `PostToolUse` hook may fire after an index
172
+ edit demanding "compact / merge or drop stale entries." Merge and drop are
173
+ forbidden by this contract; the sanctioned action is verbatim relocation,
174
+ which you have already done. Being under the load cap is the goal, not the
175
+ hook's smaller aspirational number.
176
+
94
177
  ## Common Mistakes
95
178
 
96
179
  - **Normalising the deploy table to one SHA "because that's probably
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: verify-against-plan
3
- description: "Use after implementation is complete to verify the code covers every requirement from the plan / design spec - runs alongside code-review as the second pass in the Code Review Protocol. Outputs structured findings - missing scope, unmet acceptance criteria, silent deviations from the spec, newly-introduced surprises."
3
+ description: "Use after implementation is complete to verify the code covers every requirement from the plan / design spec - runs alongside code-review as the second pass in the Code Review Protocol. Takes an optional design= input so requirements that only the design doc states are checked too. Outputs structured findings - missing scope, unmet acceptance criteria, silent deviations from the spec, newly-introduced surprises."
4
4
  ---
5
5
 
6
6
  # Verify Against Plan
7
7
 
8
8
  ## Inputs
9
9
 
10
- Two inputs are required. Missing either is a pre-flight bail.
10
+ Two inputs are required and one is optional. Missing a required input is a
11
+ pre-flight bail.
11
12
 
12
13
  ### 1. Plan or spec path (required)
13
14
 
@@ -29,6 +30,17 @@ file-structure and acceptance-criteria anchors the skill compares against.
29
30
  - **Worktree path** — infer the branch and diff from the worktree's current
30
31
  state. E.g. `<workspace>/thrum/plugin-skills-slate`.
31
32
 
33
+ ### 3. Design doc path (optional): `design=<path>`
34
+
35
+ A markdown design doc or spec the implementation must also honour. The plan
36
+ stays the authoritative anchor; the design is a second source of requirements.
37
+ Omit it and the skill behaves exactly as before (plan only). When supplied, the
38
+ skill also runs the **Design-doc pass** below.
39
+
40
+ Pass it when the plan implements all or part of a design: `design=<absolute path
41
+ to the design spec>`. A path that is missing, empty or unreadable is a
42
+ pre-flight bail (check 1b), never a silent skip.
43
+
32
44
  ### Context-inferred defaults
33
45
 
34
46
  When the caller supplies no explicit scope, infer:
@@ -45,6 +57,12 @@ When the caller supplies no explicit scope, infer:
45
57
  /verify-against-plan plan=dev-docs/plans/YYYY-MM-DD-topic-plan.md branch=feat/plugin-skills-slate
46
58
  ```
47
59
 
60
+ **With a design doc (requirements only the design states are checked too):**
61
+
62
+ ```text
63
+ /verify-against-plan plan=<plan file> design=<design file> branch=feat/plugin-skills-slate
64
+ ```
65
+
48
66
  **Context-inferred (from current worktree):**
49
67
 
50
68
  ```text
@@ -62,6 +80,11 @@ clear error — do not proceed with partial inputs.
62
80
  file is non-empty markdown. Error message on failure names the path and the
63
81
  issue (missing / empty / unreadable).
64
82
 
83
+ 1b. **Design doc exists and is readable (only when `design=` is supplied).**
84
+ Same shape as check 1: the path resolves and the file is non-empty markdown,
85
+ and the error names the path and the issue. Unlike the plan, a design doc is
86
+ not required to have a File Structure table or an Acceptance section.
87
+
65
88
  2. **Implementation scope resolves to a non-empty diff.** For branch or
66
89
  commit-range inputs, `git diff` must produce at least one changed file. An
67
90
  empty diff means either the branch is at merge-base or the range is
@@ -81,7 +104,7 @@ clear error — do not proceed with partial inputs.
81
104
  Regex: `^\*\*Authored-against:\*\* \`([0-9a-f]+)\` target: \`([^\`]+)\``
82
105
  (sha → capture group 1, merge_target → capture group 2)
83
106
 
84
- (Canonical stamp definition: `claude-plugin/commands/_stamp-protocol.md`. The
107
+ (Canonical stamp definition: the /thrum:stamp-protocol skill. The
85
108
  stamp follows the same literal `grep -F` / fixed-field-order / case-sensitive /
86
109
  ASCII-only convention as the `THRUM-REVIEW` marker — see
87
110
  `coordinator-running-brainstorm-cycles` skill § "Footer → commit → stamp".)
@@ -135,7 +158,7 @@ clear error — do not proceed with partial inputs.
135
158
  present" outcome above, since this check only needs the stamp's
136
159
  `<sha>`/`<merge_target>`, not the plan's cited-files list. Run the
137
160
  ordered, short-circuiting two-check sequence defined canonically in
138
- `_stamp-protocol.md` § "Read-time provenance re-derivation" (SHA-resolves
161
+ `stamp-protocol.md` § "Read-time provenance re-derivation" (SHA-resolves
139
162
  first, then merge-status) against the stamp's `<sha>`/`<merge_target>` —
140
163
  the exact commands live there as the single source of truth; this section
141
164
  states outcomes and severity only:
@@ -160,10 +183,98 @@ clear error — do not proceed with partial inputs.
160
183
  that the callee package was actually searched and the cost formula uses
161
184
  realistic production scale, not a test-fixture number.
162
185
 
163
- Only when checks 1–3 pass should the comparison pass begin. Checks 4, 5, and 6
186
+ Only when checks 1–3 (and 1b, when `design=` is supplied) pass should the
187
+ comparison pass begin. Checks 4, 5, and 6
164
188
  run alongside and contribute findings; a missing stamp never blocks, and check 6
165
189
  only fires when the diff actually touches a hot root.
166
190
 
191
+ ## Design-doc pass (only when `design=` is supplied)
192
+
193
+ Runs after the plan comparison and reports on the same scale. It exists because
194
+ a design doc can carry a requirement that no plan task mentions, and a plan-only
195
+ review can never see that gap.
196
+
197
+ **1. Extract requirements, mechanically.** Enumerate in DOCUMENT ORDER and number
198
+ them R1..Rn, recording `<design file>:<line>` and the verbatim quote for each.
199
+ Nothing is extracted by judgment; exactly these rules apply:
200
+
201
+ - **Section items.** Under any heading whose title contains (case-insensitive)
202
+ `requirement`, `acceptance`, `success criteria`, `invariant`, `constraint`,
203
+ `non-goal` or `out of scope`, EVERY bullet, numbered item or table row is one
204
+ requirement. The section runs until the next heading of the same or a higher
205
+ level. A nested sub-bullet is its own requirement only if it carries its own
206
+ MUST/SHALL; otherwise it belongs to its parent item.
207
+ - **Standalone MUST sentences.** Outside those sections, every sentence that
208
+ contains `MUST`, `MUST NOT`, `SHALL` or `SHALL NOT` in capitals is one
209
+ requirement.
210
+ - **Non-goals.** Items under `non-goal` / `out of scope` headings are NEGATIVE
211
+ requirements: the diff must not implement them.
212
+ - **Skipped, exactly:** everything else — background, motivation, alternatives
213
+ considered, open questions, examples, and lowercase "must"/"shall"/"should"
214
+ prose.
215
+
216
+ If the design has no extracted requirement, say so in one informational line
217
+ ("design supplied but no extractable requirements") and continue plan-only;
218
+ never report that as design coverage. Let N be the number extracted.
219
+
220
+ **2. Scope them, mechanically.** A requirement Rk is CHECKED if and only if
221
+ EITHER of these holds; otherwise it is OUTSIDE:
222
+
223
+ - **(a) Name match.** Build the set of names from (i) every path segment and
224
+ backticked token in the plan's File Structure table and (ii) every path
225
+ segment of the diff's changed paths and every identifier on its added or
226
+ removed lines. Drop generic names (`internal`, `src`, `cmd`, `pkg`, `lib`,
227
+ `docs`, `test`, `tests`, `main`, `index`, `util`, `utils`, `common`) and names
228
+ shorter than 4 characters. Rk is CHECKED when its text contains any remaining
229
+ name as a whole word, case-insensitively (`export` matches `export/handler.go`;
230
+ it does not match `exports`).
231
+ - **(b) Cited heading.** Rk sits under a design heading whose exact title also
232
+ appears verbatim in the plan.
233
+
234
+ Requirements that are OUTSIDE are never flagged; they are counted so silence
235
+ cannot pass for coverage, and C + O must equal N:
236
+
237
+ ```text
238
+ **Design scope:** <N> extracted: <C> checked, <O> outside this plan's scope, not checked
239
+ ```
240
+
241
+ A checked Rk that the plan explicitly defers or excludes is not a finding; cite
242
+ the plan's exclusion instead.
243
+
244
+ **3. Compare.** Each checked Rk is MET, ABSENT or DEVIATED against the diff and
245
+ the branch-HEAD code (a requirement already satisfied by unchanged code is MET;
246
+ cite where). An Rk that a plan item also covers is reported once, under the plan
247
+ finding.
248
+
249
+ **4. Report.** Same four fields and the same BLOCKING / IMPORTANT / MINOR
250
+ buckets; the first field is labelled `Design reference` instead of
251
+ `Plan reference`, so consolidation is unchanged:
252
+
253
+ ```markdown
254
+ ### BLOCKING #2 — <short descriptor>
255
+
256
+ - **Design reference:** <design file>:<line> — "<verbatim requirement>"
257
+ - **Implementation state:** <code path>:<line> or "absent"
258
+ - **Why it matters:** <the design requirement or invariant this ties back to>
259
+ - **Suggested resolution:** <add the missing code, or flag the design/plan gap for the coordinator>
260
+ ```
261
+
262
+ Severity: an unmet MUST, acceptance criterion or stated invariant is BLOCKING; a
263
+ silent deviation (different name, shape or behavior) or a violated non-goal is
264
+ IMPORTANT; a documentation or reference gap is MINOR.
265
+
266
+ **Example.** Design line 9 reads "The export endpoint MUST reject request bodies
267
+ over 1 MiB with HTTP 413." The plan lists only the audit-log task and its File
268
+ Structure names `export/handler.go` and `audit/audit.go`; the diff adds only
269
+ audit logging. Plan-only, nothing is flagged. With `design=`, that requirement is
270
+ extracted (it sits under a Requirements heading), CHECKED by name match (`export`
271
+ is a name from the File Structure and the diff), ABSENT from the code, and
272
+ reported as a BLOCKING finding quoting design line 9 with implementation state
273
+ `absent`. A second requirement in the same design, "The billing service MUST
274
+ retry failed charges three times", contains no name from the plan or the diff, so
275
+ it is OUTSIDE: counted in the `Design scope` line, never flagged, even though it
276
+ is a MUST that the diff does not implement.
277
+
167
278
  ## Output format
168
279
 
169
280
  Findings go to stdout in this exact shape so the coordinator can consolidate the
@@ -173,6 +284,8 @@ dual-review batch without reformatting.
173
284
  ## Verify-Against-Plan Findings
174
285
 
175
286
  **Plan:** <path> **Implementation:** <branch> (<N> commits, <M> files changed)
287
+ **Design:** <path> (only when design= was supplied)
288
+ **Design scope:** <N> extracted: <C> checked, <O> outside this plan's scope, not checked (only when design= was supplied)
176
289
  **Summary:** <N> BLOCKING, <N> IMPORTANT, <N> MINOR
177
290
 
178
291
  ---
@@ -303,7 +416,8 @@ not insubordination.
303
416
 
304
417
  ## Scope discipline
305
418
 
306
- This skill has ONE job: does the code match what the plan said it would do?
419
+ This skill has ONE job: does the code match what the plan (and the design doc,
420
+ when `design=` is supplied) said it would do?
307
421
 
308
422
  It does NOT:
309
423
 
@@ -316,7 +430,8 @@ It does NOT:
316
430
  branch.
317
431
  - **Review the plan itself** — pre-implementation plan review is handled by
318
432
  `superpowers:writing-plans` (built-in reviewer). If the plan is wrong, that's
319
- a separate workflow.
433
+ a separate workflow. The same holds for the design doc: it is a source to
434
+ check the code against, not something this skill critiques.
320
435
 
321
436
  If a finding would belong in any of the above categories, omit it from
322
437
  verify-against-plan output. Coordinator will pick up quality / security /
@@ -82,3 +82,21 @@ lives in the domain's scanner, never in agent prose.
82
82
  The watcher's config lives at `.thrum/agents/<id>/` at the repo root
83
83
  (resolve via `.thrum/redirect` from a worktree) — see the role preamble for
84
84
  the per-wake read. It runs as a `scheduled_agent` job.
85
+
86
+ ## Escalation
87
+
88
+ ### 🔴 HUMAN-NEEDED escalation goes to the owner by message, never pane-only
89
+
90
+ When you need a human decision, resolve the owner from CONFIG — never hardcode
91
+ a name — and deliver it as a message, not pane text:
92
+
93
+ OWNER=$(jq -r '.permission_supervisors[] | select(startswith("@user:"))' .thrum/config.json | sed 's/^@//' | head -1)
94
+ thrum send --to "$OWNER" --stdin <<'EOF'
95
+ [ESCALATION] <what needs a decision>: <why>. Action needed: <what>.
96
+ EOF
97
+
98
+ Writing the escalation only into your own pane is not delivery — nobody reads
99
+ watcher panes. If you get no response within 15 minutes, resend and verify
100
+ delivery (`thrum sent --unread`) before assuming it was seen. SAFE recognized
101
+ modals stay approver-of-record and are approved directly, unchanged — this
102
+ rule applies only to escalations that need a human decision.
@@ -58,6 +58,11 @@ shorten data.
58
58
  No issue IDs, no dates, no agent names, no measurements in customer-facing files.
59
59
  The tracker references the change; the change never references the tracker.
60
60
 
61
+ ## A directive has no author and no history
62
+
63
+ State the rule; never name who ruled it. Never show a before-and-after — the
64
+ previous wording is deleted, not exhibited.
65
+
61
66
  ## A skill is generic — never specific to the repo you edit from
62
67
 
63
68
  Write the rule so it holds for any repo running the skill. No repo names, config