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,7 +9,7 @@ thrum inbox # All recent messages (auto-marks as read)
9
9
  thrum inbox --unread # Unread only (does not mark as read)
10
10
  thrum inbox --json # Machine-readable
11
11
  thrum sent --unread # Check sent items with unread recipients
12
- thrum message read --all # Mark all messages as read
12
+ thrum message read --all --force # Mark ALL unread as read (run `read --all` alone first — dry run)
13
13
  ```
14
14
 
15
15
  **Search — do not page through it.** The default page is 10, newest-first, so
@@ -34,12 +34,16 @@ artifact alone.
34
34
 
35
35
  ```bash
36
36
  # $REPO must be YOUR worktree — the directory `thrum prime` reads the restart
37
- # snapshot back from. Resolve it from the daemon's authoritative identity, NOT
38
- # `git rev-parse` (which keys off the current shell CWD and would write to the
39
- # wrong .thrum/restart/ if a bash step left your worktree). Fall back to git
40
- # only if whoami can't answer.
41
- REPO=$(thrum whoami --field worktree 2>/dev/null)
42
- [ -n "$REPO" ] || REPO=$(git rev-parse --show-toplevel) || { echo "ERROR: cannot resolve your worktree"; exit 1; }
37
+ # snapshot back from. Resolve it ONLY via the daemon's authoritative,
38
+ # peercred-resolved identity (`thrum agent worktree --authoritative`) — NEVER
39
+ # via `thrum whoami --field worktree` or `git rev-parse --show-toplevel`
40
+ # (both cwd-resolved). A cwd-derived pattern could silently
41
+ # resolve to a DIFFERENT agent's worktree, or the main repo. There is NO git
42
+ # fallback — if the daemon can't answer, refuse and stop rather than guess.
43
+ REPO=$(
44
+ thrum agent worktree --authoritative 2>/dev/null
45
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently saving this snapshot to the wrong repo. Check daemon connectivity and retry."; exit 1; }
46
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
43
47
  AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
44
48
  [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
45
49
  mkdir -p "${REPO}/.thrum/restart"
@@ -50,11 +54,7 @@ mkdir -p "${REPO}/.thrum/restart"
50
54
 
51
55
  ### 2. Read the shared snapshot-composition partial
52
56
 
53
- Read the partial at the absolute path:
54
-
55
- ```text
56
- ${REPO}/claude-plugin/commands/_snapshot-protocol.md
57
- ```
57
+ Invoke the /thrum:snapshot-protocol skill and follow it.
58
58
 
59
59
  It carries the CRITICAL DISCIPLINE block and BOTH the standard 11-section
60
60
  structure and the EXTENDED 16-section structure with per-section guidance.
@@ -64,8 +64,36 @@ structure and the EXTENDED 16-section structure with per-section guidance.
64
64
 
65
65
  ### 3. Write the continuation
66
66
 
67
- Per Step 3 of the partial, use the Write tool to save your composed continuation
68
- to `${REPO}/.thrum/restart/${AGENT}.md`. `thrum prime` auto-injects this file on
67
+ First run this PREP block (it mints the path and a per-run nonce):
68
+
69
+ ```bash
70
+ # The Bash tool does NOT persist shell state across calls, so this block
71
+ # re-resolves identity itself. Fail closed: no cwd/git fallback.
72
+ REPO=$(
73
+ thrum agent worktree --authoritative 2>/dev/null
74
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently saving this snapshot to the wrong repo. Check daemon connectivity and retry."; exit 1; }
75
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
76
+ AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
77
+ [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
78
+ mkdir -p "${REPO}/.thrum/restart"
79
+ SNAPSHOT="${REPO}/.thrum/restart/${AGENT}.md"
80
+ NONCE="zn6fx-$(date +%s)-$$-${RANDOM}${RANDOM}"
81
+ echo "SNAPSHOT_PATH=${SNAPSHOT}"
82
+ echo "SNAPSHOT_NONCE=${NONCE}"
83
+ ```
84
+
85
+ Write to EXACTLY the printed `SNAPSHOT_PATH` (not a path you re-derive), and make
86
+ sure the file contains the marker below (as its final line), with the printed `SNAPSHOT_NONCE` — verify searches the whole file for it:
87
+
88
+ ```text
89
+ <!-- snapshot-nonce: <SNAPSHOT_NONCE> -->
90
+ ```
91
+
92
+ **Record both values** — the verify step pastes them into its verify block, which
93
+ fails closed unless the file at that exact path carries that nonce.
94
+
95
+ Then, per Step 3 of the partial, use the Write tool to save your composed
96
+ continuation to that exact path. `thrum prime` auto-injects this file on
69
97
  next session start regardless of whether wake comes from `thrum tmux restart` or
70
98
  operator-initiated `thrum tmux create`.
71
99
 
@@ -91,13 +119,44 @@ to preserve — strictly worse than idling. Verify the EFFECT, not the action: a
91
119
  vague "I wrote it" is not verification; check the FILE.
92
120
 
93
121
  ```bash
94
- SNAPSHOT="${REPO}/.thrum/restart/${AGENT}.md"
122
+ SNAPSHOT="<PASTE SNAPSHOT_PATH FROM STEP 3>"
123
+ NONCE="<PASTE SNAPSHOT_NONCE FROM STEP 3>"
124
+ # Fresh authoritative re-resolution (shell state does NOT persist across Bash
125
+ # calls); it must agree with the path you actually wrote in Step 3.
126
+ REPO=$(
127
+ thrum agent worktree --authoritative 2>/dev/null
128
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently verifying the wrong repo's snapshot. Check daemon connectivity and retry."; exit 1; }
129
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
130
+ AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
131
+ [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
132
+ SESSION_RAW=$(thrum whoami --field tmux_session)
133
+ SESSION=${SESSION_RAW%%:*}
134
+ EXPECTED="${REPO}/.thrum/restart/${AGENT}.md"
135
+ BIND_OK=1
136
+ if [ -z "$SNAPSHOT" ] || [ -z "$NONCE" ] || [[ "$SNAPSHOT$NONCE" == *"<PASTE"* ]]; then
137
+ echo "VERIFY FAILED: SNAPSHOT/NONCE not filled in — paste SNAPSHOT_PATH and SNAPSHOT_NONCE from Step 3. SNAPSHOT_OK=0"; BIND_OK=0
138
+ elif [ "$SNAPSHOT" != "$EXPECTED" ]; then
139
+ echo "VERIFY FAILED: identity/worktree DRIFT between write time and verify time. Written path: $SNAPSHOT — re-resolved path: $EXPECTED. SNAPSHOT_OK=0"; BIND_OK=0
140
+ fi
95
141
  NOW=$(date +%s)
96
- MTIME=$(stat -f %m "$SNAPSHOT" 2>/dev/null || stat -c %Y "$SNAPSHOT" 2>/dev/null)
142
+ # mtime: choose the stat dialect explicitly. GNU `stat -f` means --file-system
143
+ # (succeeds with multiline garbage), so the `stat -f %m || stat -c %Y` one-liner
144
+ # breaks on Linux — branch on which dialect this box speaks.
145
+ if stat -f %m . >/dev/null 2>&1; then
146
+ MTIME=$(
147
+ stat -f %m "$SNAPSHOT" 2>/dev/null
148
+ )
149
+ else
150
+ MTIME=$(
151
+ stat -c %Y "$SNAPSHOT" 2>/dev/null
152
+ )
153
+ fi
97
154
  AGE=$(( NOW - ${MTIME:-0} ))
98
155
  SNAPSHOT_OK=1
99
156
 
100
- if [ ! -f "$SNAPSHOT" ]; then
157
+ if [ "$BIND_OK" = 0 ]; then
158
+ SNAPSHOT_OK=0 # binding failure already reported above
159
+ elif [ ! -f "$SNAPSHOT" ]; then
101
160
  echo "VERIFY FAILED: no snapshot at $SNAPSHOT"; SNAPSHOT_OK=0
102
161
  elif [ ! -s "$SNAPSHOT" ]; then
103
162
  echo "VERIFY FAILED: snapshot at $SNAPSHOT is empty"; SNAPSHOT_OK=0
@@ -105,6 +164,8 @@ elif [ ! -r "$SNAPSHOT" ]; then
105
164
  echo "VERIFY FAILED: snapshot at $SNAPSHOT is not readable"; SNAPSHOT_OK=0
106
165
  elif [ "$AGE" -gt 300 ]; then
107
166
  echo "VERIFY FAILED: snapshot at $SNAPSHOT is ${AGE}s old — stale (from a prior restart), not this session's write"; SNAPSHOT_OK=0
167
+ elif ! grep -qF -- "$NONCE" "$SNAPSHOT"; then
168
+ echo "VERIFY FAILED: nonce not found in $SNAPSHOT — file at that path is not this session's write"; SNAPSHOT_OK=0
108
169
  else
109
170
  echo "VERIFY OK: snapshot exists, non-empty, readable, written ${AGE}s ago"
110
171
  fi
@@ -120,8 +181,16 @@ else
120
181
  echo "VERIFY FAILED: tmux session '$SESSION' (stripped from '$SESSION_RAW') does not resolve to a live tmux session"
121
182
  SESSION_OK=0
122
183
  fi
184
+ echo "SNAPSHOT_OK=${SNAPSHOT_OK} SESSION_OK=${SESSION_OK}"
123
185
  ```
124
186
 
187
+ **Fail-closed binding rule:** the block passes only for the exact path and nonce
188
+ the Step 3 PREP block printed (paste them into its first two lines). Empty or
189
+ placeholder values, identity/worktree drift between write and verify time, or a
190
+ file at that path without the nonce all mean VERIFY FAILED — a fresh file merely
191
+ sitting at the re-derived path is NOT proof you wrote it. Use the
192
+ `SNAPSHOT_OK`/`SESSION_OK` values it prints in Step 6.
193
+
125
194
  **"Properly" means, explicitly:** snapshot exists at your OWN worktree's
126
195
  `.thrum/restart/<your-agent-id>.md`, non-empty, readable, AND recently written
127
196
  by Step 3 of THIS session (not a leftover from a previous restart that Step 3
@@ -15,12 +15,17 @@ orchestrate the handoff.
15
15
 
16
16
  ```bash
17
17
  # $REPO must be YOUR worktree — the directory `thrum prime` reads the restart
18
- # snapshot back from. Resolve it from the daemon's authoritative identity, NOT
19
- # `git rev-parse` (which keys off the current shell CWD and would write to the
20
- # wrong .thrum/restart/ if a bash step left your worktree). Fall back to git
21
- # only if whoami can't answer.
22
- REPO=$(thrum whoami --field worktree 2>/dev/null)
23
- [ -n "$REPO" ] || REPO=$(git rev-parse --show-toplevel) || { echo "ERROR: cannot resolve your worktree"; exit 1; }
18
+ # snapshot back from. Resolve it ONLY via the daemon's authoritative,
19
+ # peercred-resolved identity (`thrum agent worktree --authoritative`) — NEVER
20
+ # via `thrum whoami --field worktree` or `git rev-parse --show-toplevel`
21
+ # (both cwd-resolved). A cwd-derived pattern could silently
22
+ # resolve to a DIFFERENT agent's worktree, or the main repo, saving this
23
+ # snapshot where nothing ever reads it back. There is NO git fallback — if
24
+ # the daemon can't answer, refuse and stop rather than guess.
25
+ REPO=$(
26
+ thrum agent worktree --authoritative 2>/dev/null
27
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently saving this snapshot to the wrong repo. Check daemon connectivity and retry."; exit 1; }
28
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
24
29
  AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
25
30
  [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
26
31
  mkdir -p "${REPO}/.thrum/restart"
@@ -65,7 +70,7 @@ shapes everything else you write below it.
65
70
 
66
71
  **Stamp your snapshot with the base it was authored against.**
67
72
  Derive and emit the **Authored-against** stamp per
68
- `claude-plugin/commands/_stamp-protocol.md` and place it at the very top of your
73
+ the /thrum:stamp-protocol skill and place it at the very top of your
69
74
  §1 section (that protocol computes the SHA and merge_target for you — never
70
75
  hand-type them). This is what lets `thrum prime` diff your snapshot at the next
71
76
  wake and flag which cited files have MOVED; an unstamped snapshot reads as
@@ -130,12 +135,36 @@ always be present so future-you can scan for what's covered and what isn't.
130
135
 
131
136
  ### 3. Write the continuation directly to your restart file
132
137
 
133
- Use your Write tool to save the composed continuation to:
138
+ First run this PREP block (it mints the path and a per-run nonce):
139
+
140
+ ```bash
141
+ # The Bash tool does NOT persist shell state across calls, so this block
142
+ # re-resolves identity itself. Fail closed: no cwd/git fallback.
143
+ REPO=$(
144
+ thrum agent worktree --authoritative 2>/dev/null
145
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently saving this snapshot to the wrong repo. Check daemon connectivity and retry."; exit 1; }
146
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
147
+ AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
148
+ [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
149
+ mkdir -p "${REPO}/.thrum/restart"
150
+ SNAPSHOT="${REPO}/.thrum/restart/${AGENT}.md"
151
+ NONCE="zn6fx-$(date +%s)-$$-${RANDOM}${RANDOM}"
152
+ echo "SNAPSHOT_PATH=${SNAPSHOT}"
153
+ echo "SNAPSHOT_NONCE=${NONCE}"
154
+ ```
155
+
156
+ Write to EXACTLY the printed `SNAPSHOT_PATH` (not a path you re-derive), and make
157
+ sure the file contains the marker below (as its final line), with the printed `SNAPSHOT_NONCE` — verify searches the whole file for it:
134
158
 
135
159
  ```text
136
- ${REPO}/.thrum/restart/${AGENT}.md
160
+ <!-- snapshot-nonce: <SNAPSHOT_NONCE> -->
137
161
  ```
138
162
 
163
+ **Record both values** — the verify step pastes them into its verify block, which
164
+ fails closed unless the file at that exact path carries that nonce.
165
+
166
+ Then use your Write tool to save the composed continuation to that exact path.
167
+
139
168
  `thrum prime` will auto-inject this file at next session start. No bash heredoc
140
169
  or `cat <<EOF` redirection is needed — write the file directly.
141
170
 
@@ -161,13 +190,44 @@ to preserve — strictly worse than idling. Verify the EFFECT, not the action: a
161
190
  vague "I wrote it" is not verification; check the FILE.
162
191
 
163
192
  ```bash
164
- SNAPSHOT="${REPO}/.thrum/restart/${AGENT}.md"
193
+ SNAPSHOT="<PASTE SNAPSHOT_PATH FROM STEP 3>"
194
+ NONCE="<PASTE SNAPSHOT_NONCE FROM STEP 3>"
195
+ # Fresh authoritative re-resolution (shell state does NOT persist across Bash
196
+ # calls); it must agree with the path you actually wrote in Step 3.
197
+ REPO=$(
198
+ thrum agent worktree --authoritative 2>/dev/null
199
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently verifying the wrong repo's snapshot. Check daemon connectivity and retry."; exit 1; }
200
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
201
+ AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
202
+ [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
203
+ SESSION_RAW=$(thrum whoami --field tmux_session)
204
+ SESSION=${SESSION_RAW%%:*}
205
+ EXPECTED="${REPO}/.thrum/restart/${AGENT}.md"
206
+ BIND_OK=1
207
+ if [ -z "$SNAPSHOT" ] || [ -z "$NONCE" ] || [[ "$SNAPSHOT$NONCE" == *"<PASTE"* ]]; then
208
+ echo "VERIFY FAILED: SNAPSHOT/NONCE not filled in — paste SNAPSHOT_PATH and SNAPSHOT_NONCE from Step 3. SNAPSHOT_OK=0"; BIND_OK=0
209
+ elif [ "$SNAPSHOT" != "$EXPECTED" ]; then
210
+ echo "VERIFY FAILED: identity/worktree DRIFT between write time and verify time. Written path: $SNAPSHOT — re-resolved path: $EXPECTED. SNAPSHOT_OK=0"; BIND_OK=0
211
+ fi
165
212
  NOW=$(date +%s)
166
- MTIME=$(stat -f %m "$SNAPSHOT" 2>/dev/null || stat -c %Y "$SNAPSHOT" 2>/dev/null)
213
+ # mtime: choose the stat dialect explicitly. GNU `stat -f` means --file-system
214
+ # (succeeds with multiline garbage), so the `stat -f %m || stat -c %Y` one-liner
215
+ # breaks on Linux — branch on which dialect this box speaks.
216
+ if stat -f %m . >/dev/null 2>&1; then
217
+ MTIME=$(
218
+ stat -f %m "$SNAPSHOT" 2>/dev/null
219
+ )
220
+ else
221
+ MTIME=$(
222
+ stat -c %Y "$SNAPSHOT" 2>/dev/null
223
+ )
224
+ fi
167
225
  AGE=$(( NOW - ${MTIME:-0} ))
168
226
  SNAPSHOT_OK=1
169
227
 
170
- if [ ! -f "$SNAPSHOT" ]; then
228
+ if [ "$BIND_OK" = 0 ]; then
229
+ SNAPSHOT_OK=0 # binding failure already reported above
230
+ elif [ ! -f "$SNAPSHOT" ]; then
171
231
  echo "VERIFY FAILED: no snapshot at $SNAPSHOT"; SNAPSHOT_OK=0
172
232
  elif [ ! -s "$SNAPSHOT" ]; then
173
233
  echo "VERIFY FAILED: snapshot at $SNAPSHOT is empty"; SNAPSHOT_OK=0
@@ -175,6 +235,8 @@ elif [ ! -r "$SNAPSHOT" ]; then
175
235
  echo "VERIFY FAILED: snapshot at $SNAPSHOT is not readable"; SNAPSHOT_OK=0
176
236
  elif [ "$AGE" -gt 300 ]; then
177
237
  echo "VERIFY FAILED: snapshot at $SNAPSHOT is ${AGE}s old — stale (from a prior restart), not this session's write"; SNAPSHOT_OK=0
238
+ elif ! grep -qF -- "$NONCE" "$SNAPSHOT"; then
239
+ echo "VERIFY FAILED: nonce not found in $SNAPSHOT — file at that path is not this session's write"; SNAPSHOT_OK=0
178
240
  else
179
241
  echo "VERIFY OK: snapshot exists, non-empty, readable, written ${AGE}s ago"
180
242
  fi
@@ -190,8 +252,16 @@ else
190
252
  echo "VERIFY FAILED: tmux session '$SESSION' (stripped from '$SESSION_RAW') does not resolve to a live tmux session"
191
253
  SESSION_OK=0
192
254
  fi
255
+ echo "SNAPSHOT_OK=${SNAPSHOT_OK} SESSION_OK=${SESSION_OK}"
193
256
  ```
194
257
 
258
+ **Fail-closed binding rule:** the block passes only for the exact path and nonce
259
+ the Step 3 PREP block printed (paste them into its first two lines). Empty or
260
+ placeholder values, identity/worktree drift between write and verify time, or a
261
+ file at that path without the nonce all mean VERIFY FAILED — a fresh file merely
262
+ sitting at the re-derived path is NOT proof you wrote it. Use the
263
+ `SNAPSHOT_OK`/`SESSION_OK` values it prints in Step 6.
264
+
195
265
  **"Properly" means, explicitly:** snapshot exists at your OWN worktree's
196
266
  `.thrum/restart/<your-agent-id>.md`, non-empty, readable, AND recently written
197
267
  by Step 3 of THIS session (not a leftover from a previous restart that Step 3
@@ -267,9 +337,17 @@ the operator:
267
337
 
268
338
  ## After Restart: Session Archive
269
339
 
270
- After restart, your snapshot doesn't disappear — it moves to
340
+ After restart, IF you saved a snapshot within the graceful window, it moves to
271
341
  `.thrum/agents/<your-agent-id>/sessions/` and stays there as a persistent log
272
- entry. Browse the archive with:
342
+ entry. If the graceful save window elapsed before you saved (e.g. you were
343
+ mid-task and missed the interrupt), the daemon writes NO snapshot and records
344
+ a `.timeout-marker` sibling file instead — `thrum tmux restart` reports this
345
+ via `snapshot_outcome: timeout` (CLI shows a 🔴 warning) rather than silently
346
+ looking like success. Do not infer success from an empty `.thrum/restart/`
347
+ directory alone — an empty source directory is consistent with either a
348
+ genuine successful archive OR a bug that resolved to the wrong destination,
349
+ silently aliased another agent's archive, or wrote truncated content. Browse
350
+ the archive with:
273
351
 
274
352
  ```bash
275
353
  thrum agent sessions list # default: this agent
@@ -278,6 +356,20 @@ thrum agent sessions list --json # NDJSON for scripts
278
356
  thrum agent sessions list --all # every agent, grouped
279
357
  ```
280
358
 
359
+ **Verify the RESOLVED DESTINATION's identity, not just its existence.**
360
+ Every archive already self-verifies: `sessionarchive.Archive` computes a
361
+ SHA-256 checksum of the snapshot content it read *before* the move, then
362
+ re-reads the file it just wrote at the destination and confirms the
363
+ checksums match (`sessionarchive.VerifyArchivedIdentity` /
364
+ `ChecksumContent` — see `internal/daemon/sessionarchive/verify.go`) before
365
+ reporting success. A destination that exists but doesn't hash-match its
366
+ own pre-move content is treated as a hard failure of the archive call, not
367
+ a warning. This is what actually proves "the file at
368
+ `.thrum/agents/<id>/sessions/<ts>-restart.md` IS the artifact that was
369
+ captured," as opposed to merely "a file with a plausible name is sitting
370
+ there" — the latter is what `thrum agent sessions list`'s presence-only
371
+ view alone would let you assume.
372
+
281
373
  Permissions are user-only (`0600` for each snapshot file, `0700` for the
282
374
  sessions folder). Operators on multi-user machines must copy explicitly to share
283
375
  archives with another account.
@@ -11,7 +11,7 @@ content.
11
11
  thrum send --to @agent_name --stdin <<'EOF' # Direct message (quoted heredoc body)
12
12
  message here
13
13
  EOF
14
- thrum send --to @everyone --stdin <<'EOF' # Broadcast to all agents
14
+ thrum send --to @agent_a --to @agent_b --stdin <<'EOF' # Multiple explicit recipients
15
15
  message here
16
16
  EOF
17
17
  ```
@@ -19,6 +19,9 @@ EOF
19
19
  Unknown recipients are a hard error. Use `thrum team` to verify agent names
20
20
  before sending.
21
21
 
22
+ `@everyone` was removed by design (P0 ruling); do not restore it — name
23
+ every recipient explicitly, or address a scoped agent-type group.
24
+
22
25
  If the body has backticks, `$(...)`, `$VAR`, or quotes, pass it via a quoted
23
26
  heredoc so the shell doesn't corrupt it:
24
27
 
@@ -30,12 +30,16 @@ to sleep until the operator wakes it later. Same termination semantics as
30
30
  ```bash
31
31
  # Resolve identity + your worktree (needed before reading the partial).
32
32
  # $REPO must be YOUR worktree — the directory `thrum prime` reads the restart
33
- # snapshot back from. Resolve it from the daemon's authoritative identity, NOT
34
- # `git rev-parse` (which keys off the current shell CWD and would write to the
35
- # wrong .thrum/restart/ if a bash step left your worktree). Fall back to git
36
- # only if whoami can't answer.
37
- REPO=$(thrum whoami --field worktree 2>/dev/null)
38
- [ -n "$REPO" ] || REPO=$(git rev-parse --show-toplevel) || { echo "ERROR: cannot resolve your worktree"; exit 1; }
33
+ # snapshot back from. Resolve it ONLY via the daemon's authoritative,
34
+ # peercred-resolved identity (`thrum agent worktree --authoritative`) — NEVER
35
+ # via `thrum whoami --field worktree` or `git rev-parse --show-toplevel`
36
+ # (both cwd-resolved). A cwd-derived pattern could silently
37
+ # resolve to a DIFFERENT agent's worktree, or the main repo. There is NO git
38
+ # fallback — if the daemon can't answer, refuse and stop rather than guess.
39
+ REPO=$(
40
+ thrum agent worktree --authoritative 2>/dev/null
41
+ ) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently saving this snapshot to the wrong repo. Check daemon connectivity and retry."; exit 1; }
42
+ [ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
39
43
  AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
40
44
  [ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
41
45
  mkdir -p "${REPO}/.thrum/restart"
@@ -54,11 +58,7 @@ no session end. Exit code 1.
54
58
 
55
59
  ### 2. Read the shared snapshot-composition partial
56
60
 
57
- Read the partial at the absolute path:
58
-
59
- ```text
60
- ${REPO}/claude-plugin/commands/_snapshot-protocol.md
61
- ```
61
+ Invoke the /thrum:snapshot-protocol skill and follow it.
62
62
 
63
63
  Apply its Step 2 (compose your continuation) per the structure guidance.
64
64
 
@@ -76,44 +76,43 @@ Per Step 3 of the partial, use the Write tool to save your composed continuation
76
76
  to `${REPO}/.thrum/restart/${AGENT}.md`. On next boot, `thrum prime`
77
77
  auto-injects this file — same mechanism as restart wake.
78
78
 
79
- ### 4. Back up important files to your durable agents folder (survives worktree teardown)
79
+ ### 4. Back up OTHER important artifacts to your durable agents folder (survives worktree teardown)
80
80
 
81
- **Your worktree may be torn down while you sleep — and if it is, YOUR SNAPSHOT
82
- GOES WITH IT unless you do this step.**
81
+ **Your restart snapshot itself no longer needs this step.** Step 6
82
+ (`thrum agent sleep`) now relocates `<worktree>/.thrum/restart/<agent-id>.md`
83
+ to the canonical `<main-repo>/.thrum/agents/<agent-id>/sessions/<ts>-restart.md`
84
+ synchronously, BEFORE it kills your pane — that is the fix for the bug this
85
+ step used to exist to work around (the snapshot previously was archived only
86
+ at your NEXT WAKE, so a worktree reap between sleep and wake destroyed it
87
+ permanently with no warning).
83
88
 
84
89
  🔴 **READ THIS CAREFULLY.** A worktree's `.thrum/` is a REAL LOCAL DIRECTORY. The
85
90
  `redirect` inside it is just a plain text FILE containing a path — a pointer
86
91
  thrum's code consults. The filesystem redirects nothing. Your worktree keeps its
87
92
  own local `agents/`, `context/`, `identities/`, and `restart/`.
88
93
 
89
- **THE ACTUAL MECHANISM:** your snapshot at `<worktree>/.thrum/restart/<agent-id>.md`
90
- is **worktree-local**. It is copied into the main repo **only when you WAKE** —
91
- `thrum prime` reads it from the worktree and archives it to
92
- `<main-repo>/.thrum/agents/<agent-id>/sessions/`. **Then and only then.** So a
93
- teardown that happens BEFORE your wake destroys the snapshot, permanently, and
94
- nothing warns you. This matters more for an extended snapshot: you are parking
95
- wire contracts, a capability matrix, and design rationale that are expensive to
96
- reconstruct and exist nowhere else.
97
-
98
- **Leave the snapshot where it is** — `thrum prime` reads it from the worktree on
99
- wake, so that path is correct and required. **Additionally**, copy anything you
100
- need to survive teardown to the MAIN REPO PATH, resolved explicitly:
94
+ **What THIS step is still for:** anything OTHER than the restart snapshot that
95
+ you need to survive a worktree teardown while parked — reports / findings you
96
+ authored, important uncommitted artifacts, and anything your resume plan
97
+ references. This matters more for an extended snapshot: you are parking wire
98
+ contracts, a capability matrix, and design rationale that are expensive to
99
+ reconstruct and exist nowhere else — but that content lives IN the snapshot
100
+ itself, which Step 6 already relocates; use this step for anything ELSE.
101
+ Copy those to the MAIN REPO PATH, resolved explicitly:
101
102
 
102
103
  ```bash
103
104
  # Resolve the main repo's .thrum from the redirect FILE (do not assume a path).
104
- MAIN_THRUM=$(cat "${REPO}/.thrum/redirect" 2>/dev/null) || MAIN_THRUM="${REPO}/.thrum"
105
+ MAIN_THRUM=$(
106
+ cat "${REPO}/.thrum/redirect" 2>/dev/null
107
+ ) || MAIN_THRUM="${REPO}/.thrum"
105
108
  mkdir -p "${MAIN_THRUM}/agents/${AGENT}"
106
- cp "${REPO}/.thrum/restart/${AGENT}.md" "${MAIN_THRUM}/agents/${AGENT}/last-sleep-snapshot.md"
107
- # ...and any other artifact you need on wake.
108
- ls -la "${MAIN_THRUM}/agents/${AGENT}/last-sleep-snapshot.md" # VERIFY IT LANDED
109
+ cp <artifact> "${MAIN_THRUM}/agents/${AGENT}/<name>"
110
+ ls -la "${MAIN_THRUM}/agents/${AGENT}/<name>" # VERIFY IT LANDED
109
111
  ```
110
112
 
111
- Copy there: reports / findings you authored, important uncommitted artifacts, and
112
- anything your resume plan references.
113
-
114
- **VERIFY THE COPY EXISTS AT THE MAIN PATH before ending your session.** Do not
115
- trust `cp`'s exit code — list the destination file. This step is your ONLY
116
- recoverability guarantee against teardown-while-asleep; the redirect is not one.
113
+ If you have nothing beyond the restart snapshot to preserve, this step is a
114
+ no-op — proceed to Step 5. Do not trust `cp`'s exit code for anything you do
115
+ copy — list the destination file to confirm it landed.
117
116
 
118
117
  ### 5. Mark agent operational status idle
119
118
 
@@ -121,29 +120,47 @@ recoverability guarantee against teardown-while-asleep; the redirect is not one.
121
120
  thrum agent set-status idle
122
121
  ```
123
122
 
124
- `idle` is the signal that this agent has parked. NO new "sleeping" state was
125
- added — `idle` covers both "no active work" and "parked for operator wake."
123
+ `idle` is the operational/presence-status signal, separate from the durable
124
+ `agents.phase` field. Step 6 (`thrum agent sleep`) is what transitions phase
125
+ to `sleeping` — a real, distinct phase, not folded into `idle`. This step
126
+ only covers the lighter-weight status field.
126
127
 
127
128
  If `thrum agent set-status` returns an error, continue to Step 6 — the snapshot
128
129
  on disk is the load-bearing artifact.
129
130
 
130
- ### 6. End session cleanly
131
-
132
- ```bash
133
- thrum session end
134
- ```
135
-
136
- This emits `agent.session.end` cleanly so the dead-agent sweeper does NOT treat
137
- the disconnect as a crash.
138
-
139
- ### 7. Kill own tmux session
131
+ ### 6. Sleep the agent (relocate snapshot, teardown, phase transition)
140
132
 
141
133
  ```bash
142
- thrum tmux kill "$SESSION"
134
+ thrum agent sleep --agent "$AGENT"
143
135
  ```
144
136
 
145
- Tmux pane terminates → runtime process exits → no further activity until
146
- operator wakes the agent.
137
+ This calls the daemon's `agent.sleep` RPC (CLI alias `park`), which runs the
138
+ full park ceremony in the correct order: it relocates your snapshot from the
139
+ worktree-local `.thrum/restart/` into the canonical
140
+ `agents/<id>/sessions/` directory, *then* kills your own tmux session, *then*
141
+ writes the `agent.phase.transition {to: "sleeping"}` event. This is the fix
142
+ for the bug a raw `thrum session end` + `thrum tmux kill` sequence had: it
143
+ killed the pane before the snapshot was ever relocated (permanent data-loss
144
+ risk if the worktree was reaped first) and left the roster row reading
145
+ `active` after the pane was already gone — a "ghost-active" agent.
146
+
147
+ **Critical: the daemon kills YOUR OWN pane mid-call, as part of this same
148
+ RPC's teardown stage.** A successful self-park is therefore observed, from
149
+ inside this pane, as the CLI call's transport dying — connection reset, EOF,
150
+ or broken pipe — NOT a clean JSON response; the pane is gone before a
151
+ response can reach it. **Do not retry** `thrum agent sleep` on transport
152
+ loss after dispatch, and **do not treat transport loss as a failure.** It is
153
+ the expected signal that self-park succeeded. A real failure (a gate,
154
+ relocation, or teardown error) is signaled by the RPC returning an explicit
155
+ error message BEFORE the pane dies — that is the only failure signal
156
+ observable from inside this pane. If independent confirmation is needed, it
157
+ must come from a DIFFERENT caller (this pane will be gone) checking
158
+ `thrum agent list` / `thrum team list` afterward.
159
+
160
+ **Do not fall back to a raw `tmux kill-session`.** A raw kill bypasses
161
+ `agent.sleep`'s relocate-before-kill ordering and daemon bookkeeping
162
+ entirely — reintroducing the exact data-loss and ghost-active bug this
163
+ ceremony exists to close.
147
164
 
148
165
  ## How wake works
149
166