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
|
@@ -53,8 +53,11 @@ grep "## Review Gate:" <prompt-file-path>
|
|
|
53
53
|
# 4. Dependencies configured
|
|
54
54
|
bd dep tree <epic-id>
|
|
55
55
|
|
|
56
|
-
# 5. Merge target in config
|
|
57
|
-
|
|
56
|
+
# 5. Merge target in config (resolve through .thrum/redirect when present --
|
|
57
|
+
# a worktree's .thrum/ holds only a `redirect` file, not its own config.json)
|
|
58
|
+
CONFIG=.thrum/config.json
|
|
59
|
+
[ -f .thrum/redirect ] && CONFIG="$(cat .thrum/redirect)/config.json"
|
|
60
|
+
cat "$CONFIG" | grep merge_target
|
|
58
61
|
```
|
|
59
62
|
|
|
60
63
|
### Step 2b: Triage CANDIDATE beads (if the dispatch carries any)
|
|
@@ -79,8 +82,8 @@ by the result:
|
|
|
79
82
|
> `choosing-subagent-models` skill's job, not this one — see it for the tiers. What
|
|
80
83
|
> matters here is that you pass one at all: the `roleDefault()` backstop in the
|
|
81
84
|
> daemon covers `thrum tmux launch` of thrum agents, **not** Agent-tool sub-agents,
|
|
82
|
-
> which inherit the PARENT model when unspecified. An orchestrator
|
|
83
|
-
> omits `model:` gets
|
|
85
|
+
> which inherit the PARENT model when unspecified. An orchestrator whose own tier is
|
|
86
|
+
> costly that omits `model:` gets costly-priced triage.
|
|
84
87
|
|
|
85
88
|
| Result | Action |
|
|
86
89
|
|---|---|
|
|
@@ -181,13 +184,13 @@ thrum worktree create <name>
|
|
|
181
184
|
# Pin --model on BOTH create AND launch — create's persist to runtime_config.json
|
|
182
185
|
# is asynchronous and can lose the race to launch's resolution if launch omits
|
|
183
186
|
# the flag (see CLAUDE.md "Launching an Agent"). There is no create-only shortcut.
|
|
184
|
-
# Always launch implementers
|
|
185
|
-
# no lower tier exists.
|
|
187
|
+
# Always launch implementers pinned to the tier from runtime.role_models, per
|
|
188
|
+
# choosing-subagent-models; that tier is the floor, no lower tier exists.
|
|
186
189
|
thrum tmux create <name> --cwd <worktree-path> \
|
|
187
190
|
--name <agent_name> --role implementer --module <module> \
|
|
188
191
|
--mode ephemeral --identity ephemeral \
|
|
189
|
-
--model
|
|
190
|
-
thrum tmux launch <name> --runtime <runtime> --model
|
|
192
|
+
--model <from runtime.role_models, per choosing-subagent-models>
|
|
193
|
+
thrum tmux launch <name> --runtime <runtime> --model <from runtime.role_models, per choosing-subagent-models>
|
|
191
194
|
```
|
|
192
195
|
|
|
193
196
|
> **Implementer lifecycle:** All implementers are ephemeral+stateless (no agent
|
|
@@ -215,7 +218,7 @@ it fails again, escalate to the human.
|
|
|
215
218
|
### Step 3b: Verify the model pin took
|
|
216
219
|
|
|
217
220
|
If `--model` is dropped anywhere in the create/launch sequence, the implementer
|
|
218
|
-
launches UNPINNED and the runtime defaults to
|
|
221
|
+
launches UNPINNED and the runtime defaults to its own (costly) model — the exact expensive
|
|
219
222
|
failure this tier exists to prevent. Confirm the pin landed before assigning
|
|
220
223
|
work, using `thrum tmux capture --format=annotated` (see CLAUDE.md "Launching
|
|
221
224
|
an Agent — Verify the Model by Pane, Never by `runtime-config get`" for the
|
|
@@ -226,12 +229,12 @@ NBSP fallback trap, and the by-position read):
|
|
|
226
229
|
thrum tmux capture <agent-name> --format=annotated --lines 12
|
|
227
230
|
```
|
|
228
231
|
|
|
229
|
-
That prints the runtime's own footer line, e.g. `Model:
|
|
232
|
+
That prints the runtime's own footer line, e.g. `Model: <resolved model>`. **Read the
|
|
230
233
|
intended tier from `.thrum/config.json` → `runtime.role_models`, not from memory.**
|
|
231
|
-
If the footer shows a different tier (see the BLOCKED note under Step 2 —
|
|
232
|
-
|
|
233
|
-
`thrum tmux create ... --model
|
|
234
|
-
`thrum agent runtime-config set <agent_name> --model
|
|
234
|
+
If the footer shows a different tier (see the BLOCKED note under Step 2 — the model ban
|
|
235
|
+
list lives in `choosing-subagent-models`), the pin failed silently — re-pin with
|
|
236
|
+
`thrum tmux create ... --model <from runtime.role_models, per choosing-subagent-models>` (or
|
|
237
|
+
`thrum agent runtime-config set <agent_name> --model <from runtime.role_models, per choosing-subagent-models>`) and relaunch BEFORE
|
|
235
238
|
assigning work. **Do not dispatch an unpinned implementer** — the daemon's
|
|
236
239
|
role-default backstop (`jq -r '.runtime.role_models' .thrum/config.json`) is a
|
|
237
240
|
net for a forgotten pin, not a substitute for this check.
|
|
@@ -329,9 +332,9 @@ Handle each message type:
|
|
|
329
332
|
2. Dispatch BOTH reviewers IN PARALLEL (one Agent call per reviewer in the same
|
|
330
333
|
response). Block until BOTH return:
|
|
331
334
|
- Code-quality: `superpowers:code-reviewer` (or `feature-dev:code-reviewer`
|
|
332
|
-
if the project provides one) — `model:
|
|
335
|
+
if the project provides one) — `model: <per choosing-subagent-models>`
|
|
333
336
|
- Spec-compliance: `general-purpose` reviewer cross-referencing the plan/
|
|
334
|
-
spec — `model:
|
|
337
|
+
spec — `model: <per choosing-subagent-models>`
|
|
335
338
|
3. **Verify** every cited `file:line` claim against the actual source before
|
|
336
339
|
forwarding. Reviewers can misread files; forwarding unverified findings
|
|
337
340
|
wastes the implementer's time.
|
|
@@ -389,10 +392,7 @@ Handle each message type:
|
|
|
389
392
|
**Sub-agent model discipline (applies to ALL Agent tool calls in this phase and
|
|
390
393
|
Phase 5):**
|
|
391
394
|
|
|
392
|
-
> **Model tiers:** pass an explicit `model:` on every dispatch
|
|
393
|
-
> (low effort) mechanical, `sonnet` (medium effort) judgment, Opus only on
|
|
394
|
-
> operator-ask or a skill step that names it. See the
|
|
395
|
-
> `choosing-subagent-models` skill for the full policy.
|
|
395
|
+
> **Model tiers:** pass an explicit `model:` on every dispatch; choose model and effort per the `choosing-subagent-models` skill (read `runtime.role_models` at the moment of use). Never let a sub-agent inherit your own model.
|
|
396
396
|
|
|
397
397
|
**Blocker:**
|
|
398
398
|
|
|
@@ -508,7 +508,7 @@ in Scope Boundaries). Analyze the conflict using your plan/intent context to
|
|
|
508
508
|
determine the correct resolution. Then spawn a targeted edit sub-agent:
|
|
509
509
|
|
|
510
510
|
```text
|
|
511
|
-
Agent(subagent_type="general-purpose", model="
|
|
511
|
+
Agent(subagent_type="general-purpose", model="<per choosing-subagent-models>",
|
|
512
512
|
prompt="Resolve this specific merge conflict.
|
|
513
513
|
File: <file-path>
|
|
514
514
|
Conflicted section: <paste the <<<< ==== >>>> block>
|
|
@@ -618,18 +618,30 @@ can act on, which is how a guard gets disabled. Classify every hit — only a
|
|
|
618
618
|
classifier failure is a real only-copy:
|
|
619
619
|
|
|
620
620
|
```bash
|
|
621
|
-
# STAGE 2 —
|
|
622
|
-
#
|
|
623
|
-
#
|
|
624
|
-
#
|
|
625
|
-
#
|
|
626
|
-
git
|
|
627
|
-
|
|
628
|
-
#
|
|
629
|
-
|
|
630
|
-
#
|
|
631
|
-
|
|
632
|
-
|
|
621
|
+
# STAGE 2 — classify every stage-1 hit. Test that a BLOB IS RETRIEVABLE,
|
|
622
|
+
# never that the path merely appears in the log: `git log -- <path>` also
|
|
623
|
+
# matches the commit that DELETED the file, and `| head -1` returns exactly
|
|
624
|
+
# that for a trimmed file.
|
|
625
|
+
git log --all --oneline --name-status -- ".thrum/agents/<path>" # read the A/M/D column; do NOT head -1
|
|
626
|
+
git cat-file -e "<add-or-modify-sha>:.thrum/agents/<path>" # rc=0 proves the BLOB EXISTS
|
|
627
|
+
git show "<that-sha>:.thrum/agents/<path>" > /tmp/x && cmp /tmp/x "<worktree-copy>"
|
|
628
|
+
# blob retrievable AND byte-identical -> RECOVERABLE
|
|
629
|
+
# otherwise -> ONLY-COPY — SALVAGE BEFORE REMOVAL
|
|
630
|
+
# Always check rc explicitly on git show/cat-file; never infer from output size.
|
|
631
|
+
# Form the cmp control from two files KNOWN to differ — comparing an
|
|
632
|
+
# accidentally-empty extraction against /dev/null reports "identical" and
|
|
633
|
+
# reads as a passing control.
|
|
634
|
+
|
|
635
|
+
# CONTROLS — the classifier must be able to both affirm and refuse.
|
|
636
|
+
# Exercise BOTH halves of the decision: existence (cat-file -e) AND
|
|
637
|
+
# byte-identity (cmp). Controlling only existence leaves the cmp step
|
|
638
|
+
# unguarded — and cmp is where an accidentally-empty extraction reads
|
|
639
|
+
# as "identical" and passes.
|
|
640
|
+
git cat-file -e "<add-or-modify-sha>:.thrum/agents/<path>"; echo "rc=$? (must be 0)"
|
|
641
|
+
git cat-file -e "<add-or-modify-sha>:.thrum/agents/__nope__/x"; echo "rc=$? (must be NON-ZERO)"
|
|
642
|
+
printf 'x' > /tmp/ctl-differs
|
|
643
|
+
cmp -s /tmp/x /tmp/ctl-differs; echo "rc=$? (must be NON-ZERO — proves cmp CAN detect a difference)"
|
|
644
|
+
cmp -s /tmp/x /tmp/x; echo "rc=$? (must be 0 — proves cmp CAN affirm)"
|
|
633
645
|
```
|
|
634
646
|
|
|
635
647
|
```bash
|
|
@@ -719,7 +731,7 @@ Run review rounds; converge to zero BLOCKING:
|
|
|
719
731
|
**Round N:**
|
|
720
732
|
|
|
721
733
|
1. Dispatch BOTH reviewers IN PARALLEL — code-quality (`feature-dev:code-reviewer`,
|
|
722
|
-
`model:
|
|
734
|
+
`model: <per choosing-subagent-models>`) + spec-compliance (`general-purpose`, `model: <per choosing-subagent-models>`).
|
|
723
735
|
**In each reviewer dispatch prompt, mandate structured severity:**
|
|
724
736
|
> "Label every finding BLOCKING / IMPORTANT / MINOR. At least one label per
|
|
725
737
|
> finding, prefix each finding line."
|
|
@@ -775,7 +787,7 @@ them, forward to coordinator at merge-approval.
|
|
|
775
787
|
**Hard cap — 2 rounds max:**
|
|
776
788
|
If BLOCKING findings remain after round 2:
|
|
777
789
|
|
|
778
|
-
- Step A: REMOVED.
|
|
790
|
+
- Step A: REMOVED. The implementer tier is the floor, so there is no
|
|
779
791
|
lower tier to escalate from. A round-2 BLOCKING failure goes straight to
|
|
780
792
|
Step B.
|
|
781
793
|
- Step B (escalate up): if the implementer fails to converge, escalate to coordinator
|
|
@@ -42,25 +42,115 @@ every agent in your `roster`:
|
|
|
42
42
|
(your own pane capture) — see "Declared-intent reminders" below for the
|
|
43
43
|
full mechanics.
|
|
44
44
|
|
|
45
|
-
## The modal
|
|
46
|
-
|
|
47
|
-
- Approve
|
|
48
|
-
|
|
49
|
-
`parent
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
## The modal judgment rubric (non-negotiable)
|
|
46
|
+
|
|
47
|
+
- Approve a modal when you can explain what the command does, it fits what
|
|
48
|
+
the requesting agent is visibly doing in its pane, and a mistake would be
|
|
49
|
+
recoverable. Otherwise escalate to your `parent` with a one-line reason.
|
|
50
|
+
- Judge the TARGET, not the keyword: `rm -rf /tmp/<dir the agent just
|
|
51
|
+
created>` is routine cleanup; `rm -rf /tmp/*` is not. One `rm` of a stale
|
|
52
|
+
lock file by a coordinator mid-deploy is fine. A force-push, or a delete
|
|
53
|
+
whose target or purpose you can't tell, is an escalate.
|
|
54
|
+
- Escalating is always allowed. Refusing only because a keyword appeared is
|
|
55
|
+
not.
|
|
56
|
+
- Judge the COMMAND TEXT, not the modal's phrasing: read the command and
|
|
57
|
+
approve or refuse on what it actually does, whatever wording the modal
|
|
58
|
+
uses. Never key on fixed prompt phrases; never approve blind.
|
|
54
59
|
- Verify by re-capture after acting — never trust exit status alone.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
|
|
61
|
+
### Approving or dismissing a modal: keys only, never text
|
|
62
|
+
|
|
63
|
+
> 🔴 **NEVER answer a modal with a text payload — not even a single
|
|
64
|
+
> character, and never a bare positional argument to `thrum tmux send`.**
|
|
65
|
+
> Plain text TYPES INTO the agent's input box; it does not act as a
|
|
66
|
+
> keypress. If the modal isn't actually showing, or focus differs from what
|
|
67
|
+
> you expect, that text lands in the agent's next command line and can
|
|
68
|
+
> corrupt or execute something you never intended. The only correct
|
|
69
|
+
> primitive is `--keys`, on every call, with no exceptions.
|
|
70
|
+
|
|
71
|
+
The procedure, every time, no shortcuts:
|
|
72
|
+
|
|
73
|
+
1. **Capture the pane fresh.** Never act on a stale capture from an earlier
|
|
74
|
+
cycle.
|
|
75
|
+
2. **Recognize the modal and which option is currently focused** (`❯` or
|
|
76
|
+
equivalent marks the default). Judge it against the rubric above.
|
|
77
|
+
3. **Choose keys, never text:**
|
|
78
|
+
- Default/focused option → `thrum tmux send <agent> --keys Enter`.
|
|
79
|
+
- A specific numbered option → the digit, e.g. `thrum tmux send <agent>
|
|
80
|
+
--keys 1`. Named keys (`Enter`, `Escape`, `Up`, `Down`, `Left`, `Right`,
|
|
81
|
+
`Tab`, digits) deliver in order, comma-separated or repeated
|
|
82
|
+
(`--keys Down,Enter` or `--keys Down --keys Enter`) — confirm the exact
|
|
83
|
+
syntax with `thrum tmux send --help` if in doubt, never guess it.
|
|
84
|
+
- To dismiss without selecting → `thrum tmux send <agent> --keys Escape`.
|
|
85
|
+
- This send is QUEUED behind the conservative monitor-silence wait, not
|
|
86
|
+
instant — don't re-send while waiting it out. A send that exits 3 (Enter
|
|
87
|
+
WITHHELD) or 4 (verdict unknown) must NOT be retried blind — capture the
|
|
88
|
+
pane first.
|
|
89
|
+
4. **Re-capture and verify the modal is actually gone.** If it's still there,
|
|
90
|
+
don't give up and don't loop blindly retrying the same keys — work up an
|
|
91
|
+
escalating transport ladder, re-capturing to verify after EACH attempt:
|
|
92
|
+
a. `thrum tmux send <agent> --keys ...` — the daemon RPC you already tried
|
|
93
|
+
in step 3. It is peer-routed (works for a local member always, and for
|
|
94
|
+
a remote one when the `can_send_keys` capability is granted between
|
|
95
|
+
your box and theirs) — retry once in case the first send was dropped,
|
|
96
|
+
not blindly beyond that.
|
|
97
|
+
b. **Raw local tmux**, for a member on YOUR OWN box whose pane the daemon
|
|
98
|
+
RPC isn't reaching correctly: `tmux send-keys -t <session> <key>`, then
|
|
99
|
+
`tmux capture-pane -t <session> -p` to verify directly, bypassing the
|
|
100
|
+
daemon layer.
|
|
101
|
+
c. **SSH to the member's own box**, for a remote member or whenever (a)
|
|
102
|
+
fails with a proxy-failure signature (`rpcrouter: caller-peer lacks
|
|
103
|
+
required capability`, `peer unreachable`, `circuit open, dial skipped`)
|
|
104
|
+
— use the shipped pair of scripts, don't hand-roll the SSH call:
|
|
105
|
+
`resources/thrum-watch-pane-capture.sh`'s `build_topology_table` /
|
|
106
|
+
`resolve_roster_targets` resolves the target's SSH destination from
|
|
107
|
+
`thrum state get agent_pool:<agent>` (`.entry.value.box`) joined
|
|
108
|
+
against `thrum state show topology:<scope>` (`.entry.value.hostname`
|
|
109
|
+
/ `.entry.value.ssh.target` / `.entry.value.ssh.user` /
|
|
110
|
+
`.entry.value.repo_path`), then hands those already-resolved values to
|
|
111
|
+
`resources/thrum-capture-fallback.sh <capture|key> <agent> <ssh_target>
|
|
112
|
+
<ssh_user> <repo_path> [keys...]`, which is the piece that actually
|
|
113
|
+
runs `ssh "<ssh_user>@<ssh_target>" "cd '<repo_path>' && thrum tmux
|
|
114
|
+
send '<agent>' --keys <keys>"` — `thrum-capture-fallback.sh` does no
|
|
115
|
+
resolution of its own, so calling it directly requires you (or the
|
|
116
|
+
caller) to already have those three values. A topology gap fails
|
|
117
|
+
closed (no SSH fallback attempted) rather than guessing a wrong
|
|
118
|
+
destination. There is no per-agent SSH field in the roster/agent Go
|
|
119
|
+
schema — the destination comes from this `thrum state` topology data.
|
|
120
|
+
(There IS a real `.thrum/ssh/config` on fleet boxes — the underlying
|
|
121
|
+
host-alias file, included via `~/.ssh/config`, that makes a bare `ssh
|
|
122
|
+
<box>` resolve at all — but it is not what these scripts read; they
|
|
123
|
+
read the resolved `ssh.target`/`ssh.user` values out of `thrum state`
|
|
124
|
+
instead.)
|
|
125
|
+
Only escalate to your `parent` after exhausting this ladder, or
|
|
126
|
+
immediately on an UNRECOGNIZED modal — and when you do, include the fresh
|
|
127
|
+
capture.
|
|
128
|
+
- Cross-peer send-keys being capability-gated is a deliberate security
|
|
129
|
+
boundary (§ladder step a above), not a bug to route around by other means
|
|
130
|
+
without checking the failure signature first. A peer can always CAPTURE
|
|
131
|
+
another peer's panes read-only regardless of capability.
|
|
132
|
+
|
|
133
|
+
**Worked example** — the standard two-variant Claude Code tool-confirmation
|
|
134
|
+
modal (`internal/daemon/permission/patterns.go`, pattern `tool_confirmation`,
|
|
135
|
+
dialect `claude-tui`), captured verbatim:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
⏺ Bash(curl https://example.com)
|
|
139
|
+
⎿ Do you want to proceed?
|
|
140
|
+
1. Yes
|
|
141
|
+
2. Yes, and don't ask again for Bash(curl)
|
|
142
|
+
3. No, and tell Claude what to do differently (Esc)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- **Approve once** → `--keys 1`.
|
|
146
|
+
- **Deny, with feedback** → `--keys 3` (or `--keys Escape` on the two-option
|
|
147
|
+
Read-prompt variant, which has no "No, and tell Claude" line).
|
|
148
|
+
- **NEVER `--keys 2`** — "Yes, and don't ask again" grants a standing
|
|
149
|
+
allow-rule beyond this one command; the modal judgment rubric above is a
|
|
150
|
+
per-command judgment, not a license to mint blanket permissions. This is
|
|
151
|
+
pinned by `ApproveKey: "1", // Yes (once) — NEVER "2"` in
|
|
152
|
+
`internal/daemon/permission/patterns.go` and tested by
|
|
153
|
+
`TestApproveKeyNeverForeverAllow`.
|
|
64
154
|
|
|
65
155
|
## Your own restart (auto-restart-at-ctx-threshold)
|
|
66
156
|
|
|
@@ -121,6 +211,22 @@ is the entire reason the watch exists — and a blocked coordinator cannot
|
|
|
121
211
|
receive its own escalation. The escalation would arrive at the one inbox
|
|
122
212
|
guaranteed not to be read.
|
|
123
213
|
|
|
214
|
+
### 🔴 HUMAN-NEEDED escalation goes to the owner by message, never pane-only
|
|
215
|
+
|
|
216
|
+
When you need a human decision, resolve the owner from CONFIG — never hardcode
|
|
217
|
+
a name — and deliver it as a message, not pane text:
|
|
218
|
+
|
|
219
|
+
OWNER=$(jq -r '.permission_supervisors[] | select(startswith("@user:"))' .thrum/config.json | sed 's/^@//' | head -1)
|
|
220
|
+
thrum send --to "$OWNER" --stdin <<'EOF'
|
|
221
|
+
[ESCALATION] <what needs a decision>: <why>. Action needed: <what>.
|
|
222
|
+
EOF
|
|
223
|
+
|
|
224
|
+
Writing the escalation only into your own pane is not delivery — nobody reads
|
|
225
|
+
watcher panes. If you get no response within 15 minutes, resend and verify
|
|
226
|
+
delivery (`thrum sent --unread`) before assuming it was seen. SAFE recognized
|
|
227
|
+
modals stay approver-of-record and are approved directly, unchanged — this
|
|
228
|
+
rule applies only to escalations that need a human decision.
|
|
229
|
+
|
|
124
230
|
### An unwatchable roster member: escalate every cycle until it is removed
|
|
125
231
|
|
|
126
232
|
A roster member you **cannot watch at all** — capture fails by every path
|
|
@@ -217,8 +323,8 @@ Register the loop as a `thrum monitor` instead — a daemon-scheduled job,
|
|
|
217
323
|
independent of your session, that keeps running across your restarts.
|
|
218
324
|
|
|
219
325
|
**A ready-to-use script ships with this skill** — `resources/thrum-watch-
|
|
220
|
-
pane-capture.sh` (paired with `resources/thrum-capture-fallback.sh
|
|
221
|
-
|
|
326
|
+
pane-capture.sh` (paired with `resources/thrum-capture-fallback.sh`).
|
|
327
|
+
It is generic and roster-driven: it reads your own
|
|
222
328
|
`watch_params.json`, captures each roster member's pane (local `thrum tmux
|
|
223
329
|
capture` first, always — that already reaches remote agents by name via the
|
|
224
330
|
normal rpcrouter proxy; see "SSH fallback" below for what the second script
|
|
@@ -246,10 +352,10 @@ runtime-versioned path involved:
|
|
|
246
352
|
|
|
247
353
|
Then register the monitor against that absolute copy path —
|
|
248
354
|
**`--notify-on-success` is MANDATORY, not optional decoration**: a
|
|
249
|
-
`--schedule`d job delivers NOTHING on `--match` alone
|
|
250
|
-
|
|
355
|
+
`--schedule`d job delivers NOTHING on `--match` alone — this fails
|
|
356
|
+
completely silently, every status field reads healthy, and it
|
|
251
357
|
is exactly the trap a literal reading of the syntax below used to walk
|
|
252
|
-
readers into
|
|
358
|
+
readers into:
|
|
253
359
|
|
|
254
360
|
```bash
|
|
255
361
|
thrum monitor start --name <you>-pane-watch \
|
|
@@ -267,13 +373,54 @@ At the start of every cycle, confirm the monitor is still running
|
|
|
267
373
|
(`thrum monitor list`) — if it's missing or dead, re-`thrum monitor start`
|
|
268
374
|
it rather than assuming someone else will notice.
|
|
269
375
|
|
|
376
|
+
**Roster snapshot monitor:** copy `resources/roster-watch.sh`,
|
|
377
|
+
`resources/capture_archive.py`, and `resources/jev_watcher_filter.py` into `<worktree>/.thrum-watch/`, then
|
|
378
|
+
`chmod +x <worktree>/.thrum-watch/roster-watch.sh` — a mode-preserving copy of
|
|
379
|
+
this resource can land as `644` depending on the runtime plugin's own tree
|
|
380
|
+
(don't rely on the source file's mode alone) — then register the monitor
|
|
381
|
+
against that copied `roster-watch.sh`. Keep the helper beside the script; the
|
|
382
|
+
roster archive path must not depend on a plugin or research worktree path at
|
|
383
|
+
run time. Captures and their archive land under `.thrum/agents/<you>/watch-captures/`
|
|
384
|
+
(redirect-resolved), not beside the deployed script itself.
|
|
385
|
+
|
|
386
|
+
Register roster-watch.sh's own monitor the same way, matching its actual
|
|
387
|
+
output lines (`"roster capture ready:"` for a normal tick, `"ROSTER
|
|
388
|
+
INCIDENT:"` for a capture-failure incident):
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
thrum monitor start --name roster-watch-<you> \
|
|
392
|
+
--match "^(roster capture ready:|ROSTER INCIDENT:)" \
|
|
393
|
+
--to @<you> \
|
|
394
|
+
--env AGENT_NAME=<you> \
|
|
395
|
+
--notify-on-success \
|
|
396
|
+
--schedule '*/10 * * * *' \
|
|
397
|
+
-- <worktree>/.thrum-watch/roster-watch.sh
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Set `use_jev: true` in the watcher's redirect-resolved `watch_params.json`
|
|
401
|
+
to filter notifications. JEV is opt-in ONLY when a key is actually present:
|
|
402
|
+
`use_jev: true` alone does not engage it. The helper reads API keys from the
|
|
403
|
+
watcher worktree's repo-root `.env` (or `ROSTER_JEV_ENV_FILE` if set);
|
|
404
|
+
environment variables take precedence. `OPENROUTER_API_KEY` is an optional
|
|
405
|
+
provider fallback. If `use_jev: true` but neither `THRUM_TYPESAFE_KEY` nor
|
|
406
|
+
`OPENROUTER_API_KEY` is found (env or that `.env` file), the watcher logs
|
|
407
|
+
`JEV requested but no key found — skipped` and runs the plain non-JEV path
|
|
408
|
+
for that tick — no manifest filter, no decision directory, and
|
|
409
|
+
`jev_watcher_filter.py` never runs. Confident active permission prompts and
|
|
410
|
+
blocking user-choice dialogs wake the watcher. Failed or uncertain decisions use
|
|
411
|
+
the normal watcher path. Missing or false `use_jev` retains normal behavior.
|
|
412
|
+
|
|
413
|
+
Keep --notify-on-success when changing use_jev. A compatible daemon
|
|
414
|
+
suppresses the success confirmation only after a completed, confidently ordinary
|
|
415
|
+
JEV tick; changing the boolean needs no monitor re-registration.
|
|
416
|
+
|
|
270
417
|
### SSH fallback (resilience net, not the primary path)
|
|
271
418
|
|
|
272
419
|
`thrum tmux capture <agent>` already reaches remote agents by name via the
|
|
273
420
|
rpcrouter proxy — that is how fleet-wide watching works today for a healthy
|
|
274
421
|
peer. `thrum-capture-fallback.sh` exists for when that proxy path is
|
|
275
|
-
broken on the caller's side (known bug classes:
|
|
276
|
-
dual-daemon caller-identity collision;
|
|
422
|
+
broken on the caller's side (known bug classes: a same-host
|
|
423
|
+
dual-daemon caller-identity collision; a phantom-routing
|
|
277
424
|
regression on a daemon build predating `EnsureProxies`). It is a strict
|
|
278
425
|
FALLBACK, never primary and never SSH-first: `thrum-watch-pane-capture.sh`
|
|
279
426
|
always tries the local proxy path first, and only retries via an SSH hop —
|