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.
- package/assets/commands/thrum-compact-extended.md +367 -0
- package/assets/commands/thrum-compact.md +301 -0
- package/assets/commands/thrum-inbox.md +1 -1
- package/assets/commands/thrum-restart-extended.md +85 -16
- package/assets/commands/thrum-restart.md +106 -14
- package/assets/commands/thrum-send.md +4 -1
- package/assets/commands/thrum-sleep-extended.md +68 -51
- package/assets/commands/thrum-sleep.md +68 -63
- package/assets/commands/thrum-update-project.md +1 -1
- 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 +200 -70
- package/assets/skills/closing-findings-while-warm/SKILL.md +1 -2
- package/assets/skills/configure-roles/SKILL.md +2 -2
- 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-branch-split-on-block/SKILL.md +1 -2
- 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 +26 -14
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +65 -21
- package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +1 -2
- package/assets/skills/coordinator-maintaining-memory/SKILL.md +1 -1
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +7 -8
- package/assets/skills/coordinator-merging-code/SKILL.md +43 -22
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +29 -9
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +4 -5
- 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 +25 -25
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +16 -6
- package/assets/{commands/thrum-_deviations-protocol.md → skills/deviations-protocol/SKILL.md} +6 -12
- 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/generator-idempotency-protocol/SKILL.md +9 -0
- package/assets/skills/implementer-maintaining-memory/SKILL.md +3 -4
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +12 -16
- package/assets/skills/implementer-receiving-dispatch/SKILL.md +15 -17
- package/assets/skills/implementer-receiving-review-feedback/SKILL.md +3 -4
- package/assets/skills/implementer-status-and-handoff/SKILL.md +1 -14
- package/assets/skills/implementer-tdd-and-quality/SKILL.md +2 -2
- package/assets/skills/memory-curate/SKILL.md +1 -1
- package/assets/skills/memory-write-discipline/SKILL.md +1 -1
- package/assets/skills/orchestrate/SKILL.md +77 -36
- 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-hotpath-gate/SKILL.md +1 -2
- package/assets/skills/project-philosophy/SKILL.md +1 -2
- package/assets/skills/project-rules-protocol/SKILL.md +12 -0
- package/assets/skills/project-setup/SKILL.md +27 -22
- package/assets/skills/project-setup/resources/implementation-agent.md +55 -35
- package/assets/skills/researcher-answering-queries/SKILL.md +1 -2
- package/assets/skills/researcher-investigating/SKILL.md +8 -13
- package/assets/skills/researcher-maintaining-memory/SKILL.md +1 -1
- package/assets/{commands/thrum-_snapshot-protocol.md → skills/snapshot-protocol/SKILL.md} +27 -21
- package/assets/{commands/thrum-_stamp-protocol.md → skills/stamp-protocol/SKILL.md} +14 -8
- 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 +85 -2
- package/assets/skills/verify-against-plan/SKILL.md +122 -7
- package/assets/skills/watcher-archetype/SKILL.md +18 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
- package/dist/installer-assets.test.js +41 -0
- package/dist/installer.js +4 -4
- package/dist/permission-fixture.integration.test.js +4 -4
- package/package.json +1 -1
- package/assets/commands/thrum-_generator-idempotency-protocol.md +0 -15
- package/assets/commands/thrum-_project-rules-protocol.md +0 -18
- package/assets/commands/thrum-_verify-before-write-protocol.md +0 -27
- package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
- package/dist/allowlist-sync.test.js +0 -44
- /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
|
|
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"
|
|
@@ -50,11 +54,7 @@ mkdir -p "${REPO}/.thrum/restart"
|
|
|
50
54
|
|
|
51
55
|
### 2. Read the shared snapshot-composition partial
|
|
52
56
|
|
|
53
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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="
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
79
|
+
### 4. Back up OTHER important artifacts to your durable agents folder (survives worktree teardown)
|
|
80
80
|
|
|
81
|
-
**Your
|
|
82
|
-
|
|
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
|
-
**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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=$(
|
|
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
|
|
107
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
|
125
|
-
|
|
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.
|
|
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
|
|
134
|
+
thrum agent sleep --agent "$AGENT"
|
|
143
135
|
```
|
|
144
136
|
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|