opencode-thrum-pro 0.1.5 → 0.1.6
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.
- package/assets/commands/thrum-_compact-runtime-commands.md +61 -0
- package/assets/commands/thrum-_snapshot-protocol.md +17 -6
- package/assets/commands/thrum-_stamp-protocol.md +9 -2
- package/assets/commands/thrum-compact-extended.md +352 -0
- package/assets/commands/thrum-compact.md +300 -0
- package/assets/commands/thrum-inbox.md +1 -1
- package/assets/commands/thrum-restart-extended.md +84 -11
- package/assets/commands/thrum-restart.md +105 -13
- package/assets/commands/thrum-send.md +4 -1
- package/assets/commands/thrum-sleep-extended.md +67 -46
- package/assets/commands/thrum-sleep.md +67 -58
- package/assets/skills/adversarial-critique/SKILL.md +5 -8
- package/assets/skills/brainstorm-steward/SKILL.md +65 -11
- package/assets/skills/brainstormer-staying-current/SKILL.md +1 -4
- package/assets/skills/choosing-subagent-models/SKILL.md +14 -0
- package/assets/skills/conversation-only-context/SKILL.md +11 -11
- package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
- package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
- package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
- package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
- package/assets/skills/coordinator-dispatching-work/SKILL.md +24 -11
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +62 -20
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +6 -6
- package/assets/skills/coordinator-merging-code/SKILL.md +25 -11
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +28 -7
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +3 -3
- package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
- package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +21 -20
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +15 -4
- package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
- package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -3
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +11 -14
- package/assets/skills/orchestrate/SKILL.md +47 -35
- package/assets/skills/persistent-watcher-archetype/SKILL.md +172 -25
- package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
- package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
- package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
- package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
- package/assets/skills/prime-agent/SKILL.md +10 -0
- package/assets/skills/project-setup/SKILL.md +22 -16
- package/assets/skills/project-setup/resources/implementation-agent.md +56 -35
- package/assets/skills/researcher-investigating/SKILL.md +2 -5
- package/assets/skills/thrum/SKILL.md +14 -7
- package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
- package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
- package/assets/skills/thrum/resources/MESSAGING.md +18 -11
- package/assets/skills/thrum/resources/WORKTREES.md +1 -1
- package/assets/skills/thrum-memory/SKILL.md +1 -1
- package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
- package/assets/skills/update-queue/SKILL.md +6 -8
- package/assets/skills/using-the-dashboard/SKILL.md +30 -0
- package/assets/skills/using-the-queue/SKILL.md +76 -5
- package/assets/skills/using-thrum-state/SKILL.md +83 -0
- package/assets/skills/verify-against-plan/SKILL.md +120 -5
- package/assets/skills/watcher-archetype/SKILL.md +18 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
- package/dist/installer.js +4 -4
- package/dist/permission-fixture.integration.test.js +4 -4
- package/package.json +1 -1
- package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
- package/dist/allowlist-sync.test.d.ts +0 -1
- package/dist/allowlist-sync.test.js +0 -44
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
description:
|
|
3
|
+
Stage a standard 11-section snapshot, verify it landed, then send /compact to
|
|
4
|
+
this agent's OWN tmux pane so it stays alive (subagents, loops, crons survive)
|
|
5
|
+
and resumes at low context. Use for in-place context relief when you do NOT
|
|
6
|
+
want to kill the session.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Compact — Snapshot Then Compact In Place
|
|
10
|
+
|
|
11
|
+
Compose a standard 11-section prose continuation, write it directly to your
|
|
12
|
+
restart file, verify it landed, then send `/compact` to your own tmux pane.
|
|
13
|
+
Unlike `/thrum:restart` and `/thrum:sleep`, compact does NOT end your session and
|
|
14
|
+
does NOT kill your tmux pane — the agent stays ALIVE. Any sub-agents, background
|
|
15
|
+
loops, and cron schedules you own keep running across the compaction (confirmed
|
|
16
|
+
empirically). After compaction you Read your snapshot by hand and re-prime lean.
|
|
17
|
+
|
|
18
|
+
## When to Use
|
|
19
|
+
|
|
20
|
+
- Your context is high and you want relief WITHOUT losing the live session —
|
|
21
|
+
you own sub-agents, a background loop, or a cron that must survive.
|
|
22
|
+
- You are mid-task and a fresh session (which loses in-flight sub-agent handles
|
|
23
|
+
and scheduler state) would be more expensive than a compaction.
|
|
24
|
+
|
|
25
|
+
For context-exhaustion or stuck-state cases where a FRESH session is the right
|
|
26
|
+
answer — or where the coordinator should move you — use `/thrum:restart`. For
|
|
27
|
+
operator-shutdown parking that kills the session, use `/thrum:sleep`. Compact is
|
|
28
|
+
the only one of the three that keeps the runtime process alive.
|
|
29
|
+
|
|
30
|
+
## Steps
|
|
31
|
+
|
|
32
|
+
### 1. Verify tmux session (Tier 1 pre-check; run BEFORE composing anything)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# compact SENDS /compact to your own pane, so a non-tmux agent has no pane to
|
|
36
|
+
# target. Fail fast before you spend a snapshot composing.
|
|
37
|
+
SESSION_RAW=$(thrum whoami --field tmux_session)
|
|
38
|
+
if [ -z "$SESSION_RAW" ]; then
|
|
39
|
+
echo "ERROR: the compact command requires a tmux-managed agent session (tmux_session field is empty)."
|
|
40
|
+
echo "For a non-tmux agent, run /compact yourself and Read your snapshot by hand afterward."
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If `tmux_session` is empty: ABORT before composing. Exit code 1.
|
|
46
|
+
|
|
47
|
+
### 2. Read the shared snapshot-composition partial
|
|
48
|
+
|
|
49
|
+
**First, persist state + queue.** Update your personal state
|
|
50
|
+
(`thrum state set --kind personal_state ...` — see the `using-thrum-state`
|
|
51
|
+
skill) and your committed work (`thrum queue` — see the `using-the-queue`
|
|
52
|
+
skill) BEFORE composing your snapshot below — these survive compaction
|
|
53
|
+
independently of the prose continuation file and are what a post-compact
|
|
54
|
+
render reads back.
|
|
55
|
+
|
|
56
|
+
Read the partial at the absolute path (resolve `$REPO` yourself first via
|
|
57
|
+
`thrum agent worktree --authoritative` — NEVER `thrum whoami --field worktree`
|
|
58
|
+
or `git rev-parse --show-toplevel`, both cwd-resolved and the root cause of
|
|
59
|
+
a documented snapshot-save loss class; there is no git fallback):
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
${REPO}/claude-plugin/commands/_snapshot-protocol.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Apply its Step 2 (compose your continuation) per the structure guidance.
|
|
66
|
+
|
|
67
|
+
**Use the STANDARD 11-section structure.** For comprehensive
|
|
68
|
+
designer/architect-grade snapshots, use `/thrum:compact-extended` instead.
|
|
69
|
+
|
|
70
|
+
**Note on §1 framing:** For a compact snapshot, §1 frames as "where work stands
|
|
71
|
+
at compaction" — you are continuing in place, not completing and not parking.
|
|
72
|
+
|
|
73
|
+
**Your snapshot's FIRST line MUST be the compact resume header**, above §1. It
|
|
74
|
+
survives even if post-compact orientation fails, so it is read-first and
|
|
75
|
+
self-contained:
|
|
76
|
+
|
|
77
|
+
> 🔴 ON RESUME — you were COMPACTED, not restarted. Daemon binding is intact
|
|
78
|
+
> (subagents/loops/crons survived). Resume LEAN: this file is delivered inline in the injected briefing (read it FIRST). Expect a
|
|
79
|
+
> `thrum prime --light` briefing already auto-injected by the SessionStart
|
|
80
|
+
> hook; only run `thrum:prime-agent` if it did NOT appear. Do NOT manually run
|
|
81
|
+
> full `thrum prime`. Then continue from §9.
|
|
82
|
+
|
|
83
|
+
Then §1…§11 as normal, with §9 (Numbered resume plan) actionable from a
|
|
84
|
+
compacted (not cold) start.
|
|
85
|
+
|
|
86
|
+
### 3. Write the continuation
|
|
87
|
+
|
|
88
|
+
First run this PREP block (it mints the path and a per-run nonce), then use the
|
|
89
|
+
Write tool per Step 3 of the partial:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# The Bash tool does NOT persist shell state across calls, so this block
|
|
93
|
+
# re-resolves identity itself. Fail closed: no cwd/git fallback.
|
|
94
|
+
REPO=$(
|
|
95
|
+
thrum agent worktree --authoritative 2>/dev/null
|
|
96
|
+
) || { 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; }
|
|
97
|
+
[ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
|
|
98
|
+
AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
|
|
99
|
+
[ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
|
|
100
|
+
mkdir -p "${REPO}/.thrum/restart"
|
|
101
|
+
SNAPSHOT="${REPO}/.thrum/restart/${AGENT}.md"
|
|
102
|
+
NONCE="zn6fx-$(date +%s)-$$-${RANDOM}${RANDOM}"
|
|
103
|
+
echo "SNAPSHOT_PATH=${SNAPSHOT}"
|
|
104
|
+
echo "SNAPSHOT_NONCE=${NONCE}"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Write to EXACTLY the printed `SNAPSHOT_PATH` (not a path you re-derive), and make
|
|
108
|
+
sure the file contains the marker below (as its final line), with the printed `SNAPSHOT_NONCE` — verify searches the whole file for it:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
<!-- snapshot-nonce: <SNAPSHOT_NONCE> -->
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Record both values** — the verify step pastes them into its verify block, which
|
|
115
|
+
fails closed unless the file at that exact path carries that nonce.
|
|
116
|
+
|
|
117
|
+
This file lives on disk and is UNAFFECTED by compaction — compaction rewrites
|
|
118
|
+
your conversation context, never your worktree. So the snapshot you write now is
|
|
119
|
+
readable after `/compact` fires. There is no durable-backup step (the worktree is
|
|
120
|
+
not torn down, so `/thrum:sleep`'s teardown-loss risk does not apply here).
|
|
121
|
+
|
|
122
|
+
### 4. Verify your snapshot, then fire /compact (ONE block)
|
|
123
|
+
|
|
124
|
+
> ✅ **Use the SINGLE-call self-send form below — it is required, not stylistic.**
|
|
125
|
+
> `thrum tmux send` (OD-5 = B; supersedes `thrum tmux key`,
|
|
126
|
+
> which is being retired) takes exactly ONE client call: the daemon
|
|
127
|
+
> types the text, waits a settle gap, checks for a pending dialog, and only then
|
|
128
|
+
> sends the confirming Enter — all server-side, in that order, within the SAME
|
|
129
|
+
> call. There is no separate `key --type` + `key Enter` pair to race, because
|
|
130
|
+
> there is no second client call: a client-side race is structurally impossible
|
|
131
|
+
> here, not merely avoided by convention.
|
|
132
|
+
> 🔴 **Never issue two separate calls (one for the text, one for Enter)** — that
|
|
133
|
+
> shape is what the retired `tmux key` two-step form did, and it is what
|
|
134
|
+
> produced a truncated `/compac` → invalid command → silent no-op (measured).
|
|
135
|
+
> A single `thrum tmux send "$SESSION" "<command>"` call has no such split to
|
|
136
|
+
> make.
|
|
137
|
+
|
|
138
|
+
On a non-Claude runtime this block sends that runtime's own compact-equivalent
|
|
139
|
+
command in place of `/compact` — see
|
|
140
|
+
`${REPO}/claude-plugin/commands/_compact-runtime-commands.md` for the cited
|
|
141
|
+
per-runtime table; `scripts/sync-skills.sh` applies the substitution when
|
|
142
|
+
syncing this file to each runtime's plugin tree.
|
|
143
|
+
|
|
144
|
+
Firing `/compact` before the Step 3 Write lands compacts a pre-write context and
|
|
145
|
+
resumes from a stale/blank file — the exact failure this command prevents. This
|
|
146
|
+
block (paste the two values printed by the Step 3 PREP block into its first two
|
|
147
|
+
lines) re-resolves identity (the Bash tool does NOT persist shell state across
|
|
148
|
+
calls, so earlier steps' variables are gone), verifies the snapshot on disk, and
|
|
149
|
+
only then fires `/compact` — all in ONE call so nothing depends on a prior block.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
SNAPSHOT="<PASTE SNAPSHOT_PATH FROM STEP 3>"
|
|
153
|
+
NONCE="<PASTE SNAPSHOT_NONCE FROM STEP 3>"
|
|
154
|
+
# Fresh authoritative re-resolution; must agree with the path you actually wrote.
|
|
155
|
+
REPO=$(
|
|
156
|
+
thrum agent worktree --authoritative 2>/dev/null
|
|
157
|
+
) || { echo "ERROR: cannot authoritatively resolve your worktree via the daemon. Refusing to fall back to cwd/git-toplevel — that would risk silently resuming from the wrong repo's snapshot. Check daemon connectivity and retry."; exit 1; }
|
|
158
|
+
[ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
|
|
159
|
+
AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
|
|
160
|
+
[ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
|
|
161
|
+
SESSION_RAW=$(thrum whoami --field tmux_session)
|
|
162
|
+
SESSION=${SESSION_RAW%%:*}
|
|
163
|
+
EXPECTED="${REPO}/.thrum/restart/${AGENT}.md"
|
|
164
|
+
BIND_OK=1
|
|
165
|
+
if [ -z "$SNAPSHOT" ] || [ -z "$NONCE" ] || [[ "$SNAPSHOT$NONCE" == *"<PASTE"* ]]; then
|
|
166
|
+
echo "VERIFY FAILED: SNAPSHOT/NONCE not filled in — paste SNAPSHOT_PATH and SNAPSHOT_NONCE from Step 3. SNAPSHOT_OK=0"; BIND_OK=0
|
|
167
|
+
elif [ "$SNAPSHOT" != "$EXPECTED" ]; then
|
|
168
|
+
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
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# mtime: choose the stat dialect explicitly. GNU `stat -f` means --file-system
|
|
172
|
+
# (wrong, multiline) — the `stat -f %m . || stat -c %Y` one-liner is fragile on
|
|
173
|
+
# Linux, so branch on which dialect this box speaks.
|
|
174
|
+
if stat -f %m . >/dev/null 2>&1; then
|
|
175
|
+
MTIME=$(
|
|
176
|
+
stat -f %m "$SNAPSHOT" 2>/dev/null
|
|
177
|
+
)
|
|
178
|
+
else
|
|
179
|
+
MTIME=$(
|
|
180
|
+
stat -c %Y "$SNAPSHOT" 2>/dev/null
|
|
181
|
+
)
|
|
182
|
+
fi
|
|
183
|
+
NOW=$(date +%s)
|
|
184
|
+
AGE=$(( NOW - ${MTIME:-0} ))
|
|
185
|
+
|
|
186
|
+
SNAPSHOT_OK=1
|
|
187
|
+
if [ "$BIND_OK" = 0 ]; then
|
|
188
|
+
SNAPSHOT_OK=0 # binding failure already reported above
|
|
189
|
+
elif [ ! -s "$SNAPSHOT" ] || [ ! -r "$SNAPSHOT" ]; then
|
|
190
|
+
echo "VERIFY FAILED: snapshot missing/empty/unreadable at $SNAPSHOT"; SNAPSHOT_OK=0
|
|
191
|
+
elif [ "$AGE" -gt 300 ]; then
|
|
192
|
+
echo "VERIFY FAILED: snapshot is ${AGE}s old — stale (prior write), not this session's"; SNAPSHOT_OK=0
|
|
193
|
+
elif ! grep -qF -- "$NONCE" "$SNAPSHOT"; then
|
|
194
|
+
echo "VERIFY FAILED: nonce not found in $SNAPSHOT — file at that path is not this session's write"; SNAPSHOT_OK=0
|
|
195
|
+
else
|
|
196
|
+
echo "VERIFY OK: snapshot non-empty, readable, written ${AGE}s ago"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
SESSION_OK=1
|
|
200
|
+
if [ -n "$SESSION" ] && tmux has-session -t "$SESSION" 2>/dev/null; then
|
|
201
|
+
echo "VERIFY OK: tmux session '$SESSION' resolves"
|
|
202
|
+
else
|
|
203
|
+
echo "VERIFY FAILED: tmux session '$SESSION' (from '$SESSION_RAW') does not resolve"; SESSION_OK=0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
if [ "$SNAPSHOT_OK" = 1 ] && [ "$SESSION_OK" = 1 ]; then
|
|
207
|
+
# ONE call: `thrum tmux send` queues this text+Enter delivery on the daemon
|
|
208
|
+
# (internal/daemon/rpc.HandleQueue -> sendQueuedCommand). The daemon waits
|
|
209
|
+
# for your own pane to go idle (this turn ending is what makes it idle —
|
|
210
|
+
# emit nothing further after this call), types the command, pauses a settle
|
|
211
|
+
# gap, checks the pane for a pending dialog, and ONLY THEN sends Enter. If a
|
|
212
|
+
# dialog is showing at that moment, the daemon withholds Enter itself
|
|
213
|
+
# (queuedPaneShowsDialogFn / ErrPaneShowsDialog) — a HOLD happens
|
|
214
|
+
# server-side, automatically, with no separate client-side check needed
|
|
215
|
+
# here. There is no second client call for Enter, so there is nothing to
|
|
216
|
+
# race: the old two-step `key --type` + `key Enter` truncation class
|
|
217
|
+
# (measured: a timed-out type left a separate Enter to submit "/compac")
|
|
218
|
+
# cannot occur when text+Enter are ONE call.
|
|
219
|
+
#
|
|
220
|
+
# This dispatch is ASYNC, not instant: it typically fires within a few
|
|
221
|
+
# seconds of your pane going idle, not in the same instant this call
|
|
222
|
+
# returns. That is expected — you are not emitting anything further this
|
|
223
|
+
# turn regardless, so the delay costs nothing.
|
|
224
|
+
thrum tmux send "$SESSION" "/compact"
|
|
225
|
+
SEND_RC=$?
|
|
226
|
+
# Exit 3 = HELD (Enter withheld), 4 = VERDICT UNKNOWN: "/compact" is already
|
|
227
|
+
# typed in the pane. Do NOT retype it (that yields "/compact/compact"); report
|
|
228
|
+
# and stop so a human/coordinator can inspect the pane.
|
|
229
|
+
if [ "$SEND_RC" -eq 3 ] || [ "$SEND_RC" -eq 4 ]; then
|
|
230
|
+
echo "HOLDING: thrum tmux send exited ${SEND_RC} (3=Enter WITHHELD, 4=verdict unknown) — /compact is typed but not confirmed submitted. NOT retrying."
|
|
231
|
+
echo "Report this to your coordinator (or the operator if you are top-level) and stop."
|
|
232
|
+
exit 1
|
|
233
|
+
elif [ "$SEND_RC" -ne 0 ]; then
|
|
234
|
+
echo "ERROR: thrum tmux send exited ${SEND_RC} — /compact was not queued. Report and stop."
|
|
235
|
+
exit 1
|
|
236
|
+
fi
|
|
237
|
+
# thrum-xyz: queue the RESUME PROMPT right behind /compact, on the same
|
|
238
|
+
# daemon queue. Hook output is context only — it does not start a turn — so
|
|
239
|
+
# without this an idle pane with an empty inbox can sit at 0% context, empty
|
|
240
|
+
# composer, until a human types. The daemon dispatches it once the pane is
|
|
241
|
+
# idle again AFTER the compaction (FIFO behind /compact), so it is not a
|
|
242
|
+
# timer and it fires exactly once.
|
|
243
|
+
RESUME_PROMPT="Compaction complete - please continue: your Resume Plan is in the auto-injected briefing (# Previous Session Context). If it is missing, read ${SNAPSHOT} or the newest *-restart.md in .thrum/agents/${AGENT}/sessions/ of the main repo, then execute its numbered resume plan."
|
|
244
|
+
thrum tmux send "$SESSION" "$RESUME_PROMPT"
|
|
245
|
+
RESUME_RC=$?
|
|
246
|
+
if [ "$RESUME_RC" -eq 0 ]; then
|
|
247
|
+
:
|
|
248
|
+
else
|
|
249
|
+
echo "WARNING: thrum tmux send exited ${RESUME_RC} — the post-compact resume prompt was NOT queued (exit 3/4 = already typed, do NOT retype). If the pane sits idle after compaction, send it a resume prompt by hand."
|
|
250
|
+
fi
|
|
251
|
+
else
|
|
252
|
+
echo "HOLDING: not firing /compact. Report the VERIFY FAILED line(s) above to"
|
|
253
|
+
echo "your coordinator (or the operator if you are top-level) and stop."
|
|
254
|
+
exit 1
|
|
255
|
+
fi
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Fail-closed binding rule:** the block only passes for the exact path and nonce
|
|
259
|
+
the Step 3 PREP block printed. It refuses (VERIFY FAILED, no `/compact`) if the
|
|
260
|
+
values are empty or still placeholders, if a fresh re-resolution of your identity
|
|
261
|
+
yields a different path (drift), or if the file at that path lacks the nonce — a
|
|
262
|
+
fresh file merely sitting at the re-derived path is NOT proof you wrote it.
|
|
263
|
+
|
|
264
|
+
If EITHER check fails, the block HOLDS and does not fire `/compact` — holding is
|
|
265
|
+
safe; compacting against an unverified snapshot loses the context this command
|
|
266
|
+
exists to preserve. After a successful send, the turn ends; emit no further tool
|
|
267
|
+
calls or prose this turn. The send is queued, not synchronous — it fires once
|
|
268
|
+
your pane goes idle, which only happens if you stop acting now.
|
|
269
|
+
|
|
270
|
+
## How resume works
|
|
271
|
+
|
|
272
|
+
After `/compact` the SAME process and session survive — the daemon binding is
|
|
273
|
+
intact (that is why your sub-agents, loops, and crons kept running). Full
|
|
274
|
+
`thrum prime` rebuilds identity + binding + full briefing for a NEW session; none
|
|
275
|
+
of that is needed here. Resume LEAN, in this order:
|
|
276
|
+
|
|
277
|
+
1. **Your Resume Plan arrives inline — do not go looking for the file.** The
|
|
278
|
+
`SessionStart` hook auto-injects a `thrum prime --light` briefing zero-turn,
|
|
279
|
+
and its `# Previous Session Context` section carries your full snapshot,
|
|
280
|
+
including the `ON RESUME` header; execute §9 from it. That prime ARCHIVES the
|
|
281
|
+
snapshot (moves `${REPO}/.thrum/restart/${AGENT}.md` into the main repo's
|
|
282
|
+
`.thrum/agents/${AGENT}/sessions/`), so the original path is normally GONE
|
|
283
|
+
by the time you look — that is by design, not loss. Only if no briefing
|
|
284
|
+
appeared (daemon unreachable), read the newest `*-restart.md` in that
|
|
285
|
+
`sessions/` directory (or `${REPO}/.thrum/restart/${AGENT}.md` if it still
|
|
286
|
+
exists) and run `thrum:prime-agent` manually as the fallback. Never manually
|
|
287
|
+
run full `thrum prime` when the briefing is present.
|
|
288
|
+
2. **A resume prompt starts your first turn.** Step 4 queued a "Compaction
|
|
289
|
+
complete - please continue" prompt on the daemon right behind `/compact`; it
|
|
290
|
+
is delivered once the pane is idle after compaction, so a turn starts even
|
|
291
|
+
with an empty inbox.
|
|
292
|
+
3. **Continue** from §9. Reconnect to your still-live sub-agents, loops, and
|
|
293
|
+
crons rather than re-dispatching.
|
|
294
|
+
|
|
295
|
+
Runtime-specific compaction-recovery hooks (if this runtime has any) are
|
|
296
|
+
documented in that runtime's own plugin tree (its hooks manifest and
|
|
297
|
+
the scripts it points to), not here.
|
|
298
|
+
|
|
299
|
+
**Follow the Resume Plan in your injected `# Previous Session Context` (snapshot
|
|
300
|
+
saved at `${REPO}/.thrum/restart/${AGENT}.md`, archived by the prime) post-compact.**
|
|
@@ -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
|
|
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
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
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"
|
|
@@ -64,8 +68,36 @@ structure and the EXTENDED 16-section structure with per-section guidance.
|
|
|
64
68
|
|
|
65
69
|
### 3. Write the continuation
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
First run this PREP block (it mints the path and a per-run nonce):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# The Bash tool does NOT persist shell state across calls, so this block
|
|
75
|
+
# re-resolves identity itself. Fail closed: no cwd/git fallback.
|
|
76
|
+
REPO=$(
|
|
77
|
+
thrum agent worktree --authoritative 2>/dev/null
|
|
78
|
+
) || { 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; }
|
|
79
|
+
[ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
|
|
80
|
+
AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
|
|
81
|
+
[ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
|
|
82
|
+
mkdir -p "${REPO}/.thrum/restart"
|
|
83
|
+
SNAPSHOT="${REPO}/.thrum/restart/${AGENT}.md"
|
|
84
|
+
NONCE="zn6fx-$(date +%s)-$$-${RANDOM}${RANDOM}"
|
|
85
|
+
echo "SNAPSHOT_PATH=${SNAPSHOT}"
|
|
86
|
+
echo "SNAPSHOT_NONCE=${NONCE}"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Write to EXACTLY the printed `SNAPSHOT_PATH` (not a path you re-derive), and make
|
|
90
|
+
sure the file contains the marker below (as its final line), with the printed `SNAPSHOT_NONCE` — verify searches the whole file for it:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
<!-- snapshot-nonce: <SNAPSHOT_NONCE> -->
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Record both values** — the verify step pastes them into its verify block, which
|
|
97
|
+
fails closed unless the file at that exact path carries that nonce.
|
|
98
|
+
|
|
99
|
+
Then, per Step 3 of the partial, use the Write tool to save your composed
|
|
100
|
+
continuation to that exact path. `thrum prime` auto-injects this file on
|
|
69
101
|
next session start regardless of whether wake comes from `thrum tmux restart` or
|
|
70
102
|
operator-initiated `thrum tmux create`.
|
|
71
103
|
|
|
@@ -91,13 +123,44 @@ to preserve — strictly worse than idling. Verify the EFFECT, not the action: a
|
|
|
91
123
|
vague "I wrote it" is not verification; check the FILE.
|
|
92
124
|
|
|
93
125
|
```bash
|
|
94
|
-
SNAPSHOT="
|
|
126
|
+
SNAPSHOT="<PASTE SNAPSHOT_PATH FROM STEP 3>"
|
|
127
|
+
NONCE="<PASTE SNAPSHOT_NONCE FROM STEP 3>"
|
|
128
|
+
# Fresh authoritative re-resolution (shell state does NOT persist across Bash
|
|
129
|
+
# calls); it must agree with the path you actually wrote in Step 3.
|
|
130
|
+
REPO=$(
|
|
131
|
+
thrum agent worktree --authoritative 2>/dev/null
|
|
132
|
+
) || { 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; }
|
|
133
|
+
[ -n "$REPO" ] || { echo "ERROR: thrum agent worktree --authoritative returned empty"; exit 1; }
|
|
134
|
+
AGENT=$(thrum whoami --field agent_id) || { echo "ERROR: agent not registered"; exit 1; }
|
|
135
|
+
[ -n "$AGENT" ] || { echo "ERROR: empty agent_id"; exit 1; }
|
|
136
|
+
SESSION_RAW=$(thrum whoami --field tmux_session)
|
|
137
|
+
SESSION=${SESSION_RAW%%:*}
|
|
138
|
+
EXPECTED="${REPO}/.thrum/restart/${AGENT}.md"
|
|
139
|
+
BIND_OK=1
|
|
140
|
+
if [ -z "$SNAPSHOT" ] || [ -z "$NONCE" ] || [[ "$SNAPSHOT$NONCE" == *"<PASTE"* ]]; then
|
|
141
|
+
echo "VERIFY FAILED: SNAPSHOT/NONCE not filled in — paste SNAPSHOT_PATH and SNAPSHOT_NONCE from Step 3. SNAPSHOT_OK=0"; BIND_OK=0
|
|
142
|
+
elif [ "$SNAPSHOT" != "$EXPECTED" ]; then
|
|
143
|
+
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
|
|
144
|
+
fi
|
|
95
145
|
NOW=$(date +%s)
|
|
96
|
-
|
|
146
|
+
# mtime: choose the stat dialect explicitly. GNU `stat -f` means --file-system
|
|
147
|
+
# (succeeds with multiline garbage), so the `stat -f %m || stat -c %Y` one-liner
|
|
148
|
+
# breaks on Linux — branch on which dialect this box speaks.
|
|
149
|
+
if stat -f %m . >/dev/null 2>&1; then
|
|
150
|
+
MTIME=$(
|
|
151
|
+
stat -f %m "$SNAPSHOT" 2>/dev/null
|
|
152
|
+
)
|
|
153
|
+
else
|
|
154
|
+
MTIME=$(
|
|
155
|
+
stat -c %Y "$SNAPSHOT" 2>/dev/null
|
|
156
|
+
)
|
|
157
|
+
fi
|
|
97
158
|
AGE=$(( NOW - ${MTIME:-0} ))
|
|
98
159
|
SNAPSHOT_OK=1
|
|
99
160
|
|
|
100
|
-
if [
|
|
161
|
+
if [ "$BIND_OK" = 0 ]; then
|
|
162
|
+
SNAPSHOT_OK=0 # binding failure already reported above
|
|
163
|
+
elif [ ! -f "$SNAPSHOT" ]; then
|
|
101
164
|
echo "VERIFY FAILED: no snapshot at $SNAPSHOT"; SNAPSHOT_OK=0
|
|
102
165
|
elif [ ! -s "$SNAPSHOT" ]; then
|
|
103
166
|
echo "VERIFY FAILED: snapshot at $SNAPSHOT is empty"; SNAPSHOT_OK=0
|
|
@@ -105,6 +168,8 @@ elif [ ! -r "$SNAPSHOT" ]; then
|
|
|
105
168
|
echo "VERIFY FAILED: snapshot at $SNAPSHOT is not readable"; SNAPSHOT_OK=0
|
|
106
169
|
elif [ "$AGE" -gt 300 ]; then
|
|
107
170
|
echo "VERIFY FAILED: snapshot at $SNAPSHOT is ${AGE}s old — stale (from a prior restart), not this session's write"; SNAPSHOT_OK=0
|
|
171
|
+
elif ! grep -qF -- "$NONCE" "$SNAPSHOT"; then
|
|
172
|
+
echo "VERIFY FAILED: nonce not found in $SNAPSHOT — file at that path is not this session's write"; SNAPSHOT_OK=0
|
|
108
173
|
else
|
|
109
174
|
echo "VERIFY OK: snapshot exists, non-empty, readable, written ${AGE}s ago"
|
|
110
175
|
fi
|
|
@@ -120,8 +185,16 @@ else
|
|
|
120
185
|
echo "VERIFY FAILED: tmux session '$SESSION' (stripped from '$SESSION_RAW') does not resolve to a live tmux session"
|
|
121
186
|
SESSION_OK=0
|
|
122
187
|
fi
|
|
188
|
+
echo "SNAPSHOT_OK=${SNAPSHOT_OK} SESSION_OK=${SESSION_OK}"
|
|
123
189
|
```
|
|
124
190
|
|
|
191
|
+
**Fail-closed binding rule:** the block passes only for the exact path and nonce
|
|
192
|
+
the Step 3 PREP block printed (paste them into its first two lines). Empty or
|
|
193
|
+
placeholder values, identity/worktree drift between write and verify time, or a
|
|
194
|
+
file at that path without the nonce all mean VERIFY FAILED — a fresh file merely
|
|
195
|
+
sitting at the re-derived path is NOT proof you wrote it. Use the
|
|
196
|
+
`SNAPSHOT_OK`/`SESSION_OK` values it prints in Step 6.
|
|
197
|
+
|
|
125
198
|
**"Properly" means, explicitly:** snapshot exists at your OWN worktree's
|
|
126
199
|
`.thrum/restart/<your-agent-id>.md`, non-empty, readable, AND recently written
|
|
127
200
|
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
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
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"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
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 [
|
|
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,
|
|
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.
|
|
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 @
|
|
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
|
|