opencode-thrum-pro 0.1.7 → 0.1.8
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-restart.md +1 -1
- package/assets/skills/brainstorm-queue-aware/SKILL.md +1 -1
- package/assets/skills/brainstorm-steward/SKILL.md +3 -3
- package/assets/skills/choosing-subagent-models/SKILL.md +14 -21
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +11 -10
- package/assets/skills/coordinator-context-monitoring/SKILL.md +11 -12
- package/assets/skills/coordinator-deploying-a-node/SKILL.md +3 -1
- package/assets/skills/coordinator-dispatching-work/SKILL.md +3 -1
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +6 -6
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +9 -1
- package/assets/skills/coordinator-merging-code/SKILL.md +5 -5
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +3 -3
- package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +1 -0
- package/assets/skills/efficient-multi-agent-research/SKILL.md +2 -2
- package/assets/skills/memory-audit-recover/SKILL.md +7 -5
- package/assets/skills/memory-curate/SKILL.md +2 -2
- package/assets/skills/memory-maintenance/SKILL.md +2 -0
- package/assets/skills/orchestrate/SKILL.md +1 -1
- package/assets/skills/persistent-watcher-archetype/SKILL.md +2 -2
- package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +5 -5
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +5 -5
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +2 -2
- package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +1 -1
- package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +3 -3
- package/assets/skills/project-philosophy/SKILL.md +1 -1
- package/assets/skills/project-setup/SKILL.md +2 -2
- package/assets/skills/researcher-maintaining-memory/SKILL.md +2 -0
- package/assets/skills/thrum/SKILL.md +9 -4
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +32 -27
- package/assets/skills/thrum-memory/SKILL.md +1 -1
- package/assets/skills/using-monitors/SKILL.md +198 -0
- package/assets/skills/using-the-queue/SKILL.md +2 -3
- package/assets/skills/using-thrum-state/SKILL.md +1 -1
- package/dist/hooks/block-sync-cd.js +1 -1
- package/dist/hooks/block-sync-cd.test.js +1 -1
- package/package.json +1 -1
|
@@ -361,7 +361,7 @@ Every archive already self-verifies: `sessionarchive.Archive` computes a
|
|
|
361
361
|
SHA-256 checksum of the snapshot content it read *before* the move, then
|
|
362
362
|
re-reads the file it just wrote at the destination and confirms the
|
|
363
363
|
checksums match (`sessionarchive.VerifyArchivedIdentity` /
|
|
364
|
-
`ChecksumContent`
|
|
364
|
+
`ChecksumContent` in the daemon's session-archive package) before
|
|
365
365
|
reporting success. A destination that exists but doesn't hash-match its
|
|
366
366
|
own pre-move content is treated as a hard failure of the archive call, not
|
|
367
367
|
a warning. This is what actually proves "the file at
|
|
@@ -32,7 +32,7 @@ answer," do this instead:
|
|
|
32
32
|
clear question, plus 2-3 concrete options (A/B/C) so the Steward has
|
|
33
33
|
something to decide between, not an open-ended essay prompt.
|
|
34
34
|
2. Send it as a `NEED` to your steward, per the NEED/ANSWER convention
|
|
35
|
-
(
|
|
35
|
+
(the NEED/ANSWER convention spec in the project's design docs):
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
thrum send --to @<steward> --stdin <<'EOF'
|
|
@@ -72,7 +72,7 @@ thread:
|
|
|
72
72
|
- **Push — inbox scan.** Each cycle, read your inbox (`thrum inbox`) for
|
|
73
73
|
messages from a roster brainstormer whose first line matches
|
|
74
74
|
`NEED [<topic>]:` (the convention from
|
|
75
|
-
|
|
75
|
+
the NEED/ANSWER convention spec in the project's design docs). Read
|
|
76
76
|
the topic, question, and options straight from the prose — there is no
|
|
77
77
|
structured payload to parse; you are a judgment agent reading a message
|
|
78
78
|
the way you'd read any message. The sender identity tells you which
|
|
@@ -242,9 +242,9 @@ state as soon as the brainstormer advances past it.
|
|
|
242
242
|
|
|
243
243
|
## Reuse note
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
The brainstorm-steward watch-params template in the project's design docs is the canonical
|
|
246
246
|
starting config for a new Brainstorm Steward — it uses the schema documented
|
|
247
|
-
in
|
|
247
|
+
in the watch-params schema spec unchanged, with an
|
|
248
248
|
empty `roster` to be populated as brainstormers are launched under it. Its
|
|
249
249
|
`parent` field ships pre-filled with this fleet's own escalation target —
|
|
250
250
|
when copying the template to a different fleet or box, replace that value
|
|
@@ -36,14 +36,8 @@ Every subagent you spawn MUST pass an explicit, resolved `model:` (plus effort
|
|
|
36
36
|
where the runtime supports it). Omitting it runs the subagent on YOUR model —
|
|
37
37
|
the single biggest avoidable cost leak in agent work.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
mechanical-task carve-out that reaches for it; lint runs, grep-and-collect,
|
|
42
|
-
file maps, config edits, and all other "simple" work dispatch at the
|
|
43
|
-
mechanical tier's Claude resolution (sonnet-low), not Haiku. Never select
|
|
44
|
-
Haiku on own judgment on Claude — use the mechanical tier's resolved value
|
|
45
|
-
instead. Other runtimes floor at their own mechanical-tier value
|
|
46
|
-
(`RuntimePreset.TierModels`), not at a Claude name.
|
|
39
|
+
Haiku 5.5 is eligible for summarization and mechanical work.
|
|
40
|
+
Read the effective model and effort at use from `runtime.role_models`.
|
|
47
41
|
|
|
48
42
|
### Read the tiers at the moment of use
|
|
49
43
|
|
|
@@ -75,9 +69,10 @@ same tier from your own runtime's preset (`RuntimePreset.TierModels`) or
|
|
|
75
69
|
| **Sub-agent** (investigation, grep, mechanical) | `mechanical` | **`sonnet` / low** |
|
|
76
70
|
|
|
77
71
|
- **`mechanical` tier (sub-agents ONLY)** — investigation, grep-and-collect,
|
|
78
|
-
file maps, lint runs, mechanical tasks.
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
file maps, lint runs, mechanical tasks. Read the effective model and effort
|
|
73
|
+
at use from `runtime.role_models`. For other runtimes, use their own
|
|
74
|
+
mechanical-tier value (`RuntimePreset.TierModels`), not a Claude name.
|
|
75
|
+
The mechanical tier is for sub-agents only, never for an implementer or a reviewer.
|
|
81
76
|
- **`judgment` tier (implementers, verifiers, reviewers)** — resolves to
|
|
82
77
|
`sonnet` @ MEDIUM effort on Claude. Not low. A reviewer on low effort is a
|
|
83
78
|
rubber stamp with extra steps, and rubber-stamped reviews are how a merge
|
|
@@ -159,9 +154,8 @@ reviewer "from memory", and never dispatch one at the mechanical tier.
|
|
|
159
154
|
**AND VERIFY, DO NOT ASSERT:** any claim about what a tool does or does not expose
|
|
160
155
|
must be checked against the schema in front of you.
|
|
161
156
|
|
|
162
|
-
Never select the `orchestration` tier
|
|
163
|
-
|
|
164
|
-
floor of that tier's lowest effort.
|
|
157
|
+
Never select the `orchestration` tier on your own judgment — use the
|
|
158
|
+
`judgment` tier's configured value from `runtime.role_models`.
|
|
165
159
|
|
|
166
160
|
## Where the tier-model mapping lives
|
|
167
161
|
|
|
@@ -238,9 +232,8 @@ that stops at depth 1 is absent where the work happens.
|
|
|
238
232
|
work, wait for the completion notification.
|
|
239
233
|
- Every sub-agent YOU spawn gets an explicit `model:`, resolved for your
|
|
240
234
|
runtime's tier — `mechanical` (rote/find-replace) or `judgment` (real
|
|
241
|
-
analysis).
|
|
242
|
-
|
|
243
|
-
`orchestration` tier is never yours to spawn.
|
|
235
|
+
analysis). Read the effective model and effort from `runtime.role_models` at
|
|
236
|
+
use. The `orchestration` tier is never yours to spawn.
|
|
244
237
|
- READ-ONLY git outside your own worktree. NEVER `checkout`/`reset`/`restore`/
|
|
245
238
|
`stash`/`clean` in ANY directory — `stash` is one shared stack across all
|
|
246
239
|
worktrees and the shared tree holds live agents' uncommitted state.
|
|
@@ -252,10 +245,10 @@ signal this condition produces.
|
|
|
252
245
|
|
|
253
246
|
### Cheap subagents → fan out, don't pile up
|
|
254
247
|
|
|
255
|
-
Because
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
has independent parts, partition
|
|
248
|
+
Because Haiku 5.5 supports summarization and mechanical sub-agent work, prefer
|
|
249
|
+
MANY small parallel subagents over one agent handed a pile of tasks. Read the
|
|
250
|
+
effective model and effort from `runtime.role_models` at dispatch. When research or
|
|
251
|
+
investigation has independent parts, partition them in parallel (use the
|
|
259
252
|
`efficient-multi-agent-research` skill) — smaller scopes are cheaper, run
|
|
260
253
|
concurrently (faster), and keep each subagent's context tight. One subagent
|
|
261
254
|
given ten tasks is the anti-pattern.
|
|
@@ -176,11 +176,10 @@ salvaged files distinct names and land the salvage somewhere durable (the main r
|
|
|
176
176
|
|
|
177
177
|
## 🔴 The reap is worktree-removal-only — and TEST every step on ONE item first
|
|
178
178
|
|
|
179
|
-
**`thrum agent delete
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
then remove PLAIN (no `--force`):
|
|
179
|
+
**`thrum agent delete` refuses an agent whose linked worktree still exists** (and any agent
|
|
180
|
+
with a live pid/tmux process, and coordinators); `--force` does NOT bypass these guards. So
|
|
181
|
+
the reap that frees load is worktree removal first — salvage BEFORE you remove, then remove
|
|
182
|
+
PLAIN (no `--force`), then `thrum agent delete`:
|
|
184
183
|
|
|
185
184
|
1. Enumerate untracked+ignored `.thrum` content — BOTH flags, one flag alone misses a class:
|
|
186
185
|
`git status --untracked-files=all --ignored -- .thrum`
|
|
@@ -200,9 +199,10 @@ then remove PLAIN (no `--force`):
|
|
|
200
199
|
mistake it for protecting the ignored-file class. (Not a pipe — a pipe returns the last
|
|
201
200
|
command's rc, not `git`'s.)
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
Then REAP the agent row with `thrum agent delete <agent>` once the worktree is gone and no
|
|
203
|
+
process is live: orphan rows must be reaped, not left. Never use `thrum agent cleanup
|
|
204
|
+
--force` (it can flag live fleet agents). A refused reap on a dead, worktree-less agent is a
|
|
205
|
+
bug: report it, do not route around it.
|
|
206
206
|
|
|
207
207
|
⚠️ **After a large reap you may see a `DeadAgentSweeper` skip alert + elevated write-RPC
|
|
208
208
|
latency — do NOT assume the reap caused it.** This box carries a standing single-writer
|
|
@@ -302,8 +302,9 @@ RSS-MB.
|
|
|
302
302
|
list AND disk), not just the exit code.
|
|
303
303
|
- **Rank candidates by last-message CONTENT, not elapsed time** — your own
|
|
304
304
|
broadcasts reset every recipient's idle clock.
|
|
305
|
-
- **`--force` on agent-delete does NOT bypass
|
|
306
|
-
|
|
305
|
+
- **`--force` on agent-delete does NOT bypass its guards** — the worktree must go first
|
|
306
|
+
(teardown with salvage or plain `git worktree remove`), then `thrum agent delete` reaps
|
|
307
|
+
the row.
|
|
307
308
|
- **Reap order is `thrum tmux kill <session>` FIRST, THEN `git worktree remove`** —
|
|
308
309
|
removing the worktree alone leaves the agent's tmux session alive with a
|
|
309
310
|
now-deleted cwd, which the fleet flags as recurring "cwd drift" and whose runtime
|
|
@@ -30,13 +30,13 @@ What's forbidden is a script that bypasses this skill and fires
|
|
|
30
30
|
|
|
31
31
|
This skill's tier ladder (Steps 3–5) governs how the coordinator restarts
|
|
32
32
|
**other** agents. When the **coordinator restarts ITSELF**, a different rule
|
|
33
|
-
applies and it is absolute — but
|
|
33
|
+
applies and it is absolute — but the current ruling
|
|
34
34
|
REVERSES which command that is:
|
|
35
35
|
|
|
36
36
|
> **The coordinator ALWAYS uses the EXTENDED snapshot grade for itself — never
|
|
37
37
|
> the standard/compact form. Which command carries that grade is
|
|
38
38
|
> runtime-directed: `/thrum:compact-extended` on any runtime with a verified
|
|
39
|
-
> compact-equivalent skill (
|
|
39
|
+
> compact-equivalent skill (the default), `/thrum:restart-extended` ONLY
|
|
40
40
|
> as the fallback on a runtime without one (today: muse, copilot).**
|
|
41
41
|
|
|
42
42
|
This is a REVERSAL of the prior absolute rule ("ALWAYS restart-extended, NEVER
|
|
@@ -62,7 +62,7 @@ Practical trigger points for coordinator self-restart/compact:
|
|
|
62
62
|
In every one of these: run `/thrum:compact-extended` if the coordinator's
|
|
63
63
|
runtime has a compact equivalent (the default); `/thrum:restart-extended` only
|
|
64
64
|
if it does not. (The role-rule `coord-always-restart-extended` — `Coordinator
|
|
65
|
-
ALWAYS uses /thrum:restart-extended` — is RETIRED
|
|
65
|
+
ALWAYS uses /thrum:restart-extended` — is RETIRED; see
|
|
66
66
|
`thrum memory` for its retirement record. This skill is the always-loaded home
|
|
67
67
|
so the CURRENT rule survives even when memory isn't consulted.)
|
|
68
68
|
|
|
@@ -163,7 +163,7 @@ is a single monitor for all lenses in v1, not one per lens.
|
|
|
163
163
|
| `idle_mid_task` | ON | L2 — 30-min idle-with-open-task detection (bead cross-ref). RECOMMEND `/thrum:sleep-extended`; a no-task variant REPORTs `idle-no-task` at lower priority. |
|
|
164
164
|
| `snapshot_awaiting_restart` | ON | L3 — pane text + fresh-snapshot two-signal heuristic. RECOMMEND `thrum tmux restart <agent>` (never autonomous). |
|
|
165
165
|
| `blocked_on_human_modal` | ON | L4 — detects spend-limit/permission/consent modal prompts. DETECTION ONLY, never auto-answers; routes into the L5 ledger. |
|
|
166
|
-
| `pending_human_ledger` | ON | L5 — flat JSONL ledger of items awaiting a human. EXEMPT from
|
|
166
|
+
| `pending_human_ledger` | ON | L5 — flat JSONL ledger of items awaiting a human. EXEMPT from progressive backoff — surfaces every tick. `hb_ledger_resolve` (the resolution path) is defined but has zero call sites — nothing marks an entry resolved, so the ledger is append-only-forever, not "until resolved." |
|
|
167
167
|
| `waiting_on_coord` | ON | L9 — 21-rule pattern match (folded in from the standalone waiting-on-coord sweep) + warm-hold exemption. RECOMMEND coordinator answer. |
|
|
168
168
|
|
|
169
169
|
### Default-OFF lenses (E8, flag-gated — NOT YET IMPLEMENTED as of E7)
|
|
@@ -183,8 +183,7 @@ produce no output until enabled.
|
|
|
183
183
|
## Configuration
|
|
184
184
|
|
|
185
185
|
The heartbeat system is configured under the `heartbeat` key in
|
|
186
|
-
`.thrum/config.json` (Go type: `HeartbeatConfig`
|
|
187
|
-
`internal/config/heartbeat.go`). Example:
|
|
186
|
+
`.thrum/config.json` (Go type: `HeartbeatConfig`). Example:
|
|
188
187
|
|
|
189
188
|
```json
|
|
190
189
|
{
|
|
@@ -222,10 +221,10 @@ The heartbeat system is configured under the `heartbeat` key in
|
|
|
222
221
|
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
223
222
|
| `cadence_minutes_day` | Daytime cadence target (default 30 min). The underlying monitor runs `@every 30m` and self-gates so only busy-fleet daytime ticks fire. |
|
|
224
223
|
| `cadence_minutes_overnight` | Overnight cadence target (default 60 min). The monitor fires every other `@every 30m` tick overnight (effective 60-min rate). |
|
|
225
|
-
| `busy_only_daytime` | When `true`, daytime ticks are skipped unless any agent has `agent_status=working` (
|
|
224
|
+
| `busy_only_daytime` | When `true`, daytime ticks are skipped unless any agent has `agent_status=working` (busy-signal gate). |
|
|
226
225
|
| `busy_signal` | Algorithm for "is the fleet busy?" Default `working_or_activity_30m`: any agent working OR activity in the last 30 min. |
|
|
227
226
|
| `overnight_window.tz` | **MUST be an explicit IANA timezone** (e.g. `"America/New_York"`). If unset, the sweep warns and falls back to always-daytime-rate rather than silently guessing. |
|
|
228
|
-
| `backoff.start_after` | Number of consecutive unchanged detections of the same problem before
|
|
227
|
+
| `backoff.start_after` | Number of consecutive unchanged detections of the same problem before progressive backoff begins. |
|
|
229
228
|
| `backoff.multiplier` | Each backoff step multiplies the surface interval by this factor (default 2×). |
|
|
230
229
|
| `backoff.floor_every` | The surface interval is capped at this many ticks — the problem is never fully silenced (always surfaces at least once per `floor_every` ticks). |
|
|
231
230
|
| `lenses.<name>.enabled` | Enable/disable a lens individually. |
|
|
@@ -238,8 +237,8 @@ The heartbeat system is configured under the `heartbeat` key in
|
|
|
238
237
|
`.thrum/config.json`** to configure the heartbeat. Two gotchas make this
|
|
239
238
|
non-obvious; both bite silently.
|
|
240
239
|
|
|
241
|
-
**1. All-or-nothing merge (the sharp footgun).** The
|
|
242
|
-
(`
|
|
240
|
+
**1. All-or-nothing merge (the sharp footgun).** The config
|
|
241
|
+
loader (`LoadThrumConfig`) applies
|
|
243
242
|
`DefaultHeartbeatConfig()` **only when the stanza is effectively absent**
|
|
244
243
|
(`schema_version == 0` AND zero lenses):
|
|
245
244
|
|
|
@@ -254,7 +253,7 @@ There is **no field-level deep merge.** The moment you write a stanza with
|
|
|
254
253
|
`cadence_minutes_day: 0`, `enabled: false`, and **all 12 lenses disabled**. So
|
|
255
254
|
"I'll just set the timezone" with a three-line stanza silently turns the whole
|
|
256
255
|
sweep off. **Always write the COMPLETE stanza** — copy
|
|
257
|
-
`DefaultHeartbeatConfig()`
|
|
256
|
+
`DefaultHeartbeatConfig()` in full, then change
|
|
258
257
|
only what you need.
|
|
259
258
|
|
|
260
259
|
**2. `overnight_window.tz` MUST be an explicit IANA zone.** Default is `""` (the
|
|
@@ -332,7 +331,7 @@ repeals fix-while-warm.
|
|
|
332
331
|
restart is **under one turn** once context is meaningfully above the re-prime
|
|
333
332
|
floor.
|
|
334
333
|
- **Orchestrators re-prime far cheaper than coordinators, and it is structural:**
|
|
335
|
-
|
|
334
|
+
the prime output filter gives ONLY `role=="coordinator"` the full
|
|
336
335
|
project-state passthrough. Measured orchestrator re-prime ≈ 30k tokens.
|
|
337
336
|
- Coordinators keep the ladder below for a stated reason — larger re-entry cost,
|
|
338
337
|
plus decision-dense context a snapshot cannot faithfully reconstruct.
|
|
@@ -48,7 +48,9 @@ If the project has no WAL/SHM, this section is N/A — skip and note.
|
|
|
48
48
|
|
|
49
49
|
## 4. Never wrap service start/restart in a blind timeout
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
🔴 **On macOS, the restart command is NOT `service.restart_command` — it is `service.mac_restart_command`** (Thrum: `scripts/mac-daemon-restart-via-cron.sh`, run from inside the repo). A daemon restarted from a terminal or agent pane gets its Local Network (TCC) permission attributed to that terminal and is silently denied LAN peer dials (`no route to host`): peers go dark while the node itself looks healthy, and `PPID 1` does NOT prove it was re-parented correctly. Check the node's OS before choosing the command; this has been forgotten on primary repeatedly. After a mac restart, verify the peers on the LAN reconnect, not just the version.
|
|
52
|
+
|
|
53
|
+
Run the project's service restart command (`fleet.deployment.thrum.service.restart_command`, or `service.mac_restart_command` on macOS, or product equivalent) without a blind timeout wrapper. A timeout that kills a migrating restart mid-migration manufactures a half-migrated store.
|
|
52
54
|
|
|
53
55
|
- Adapter flag `service.never_timeout` is true.
|
|
54
56
|
- Signal-window rules are adapter data: `migration.signal_rules` (`SIGQUIT` fatal-always, `SIGUSR1` fatal-before-socket). Respect them — do not send fatal signals in the forbidden window.
|
|
@@ -26,7 +26,9 @@ send. See `coordinator-managing-state-and-lifecycle` § "Destroy an agent before
|
|
|
26
26
|
tearing down its worktree" for the canonical sequence (`thrum tmux kill` BEFORE
|
|
27
27
|
`thrum worktree teardown`). The dispatching skill mentions it here so you find
|
|
28
28
|
the cross-reference when planning a wave recycle, not just when reading the
|
|
29
|
-
lifecycle skill end-to-end.
|
|
29
|
+
lifecycle skill end-to-end. Teardown does not delete the agent row: it stays
|
|
30
|
+
retired until a separate `thrum agent delete <agent>` (after the worktree is
|
|
31
|
+
gone) or the sweeper reaps it.
|
|
30
32
|
|
|
31
33
|
## Never spawn sub-agents into worktrees where Thrum agents are running
|
|
32
34
|
|
|
@@ -90,7 +90,7 @@ the one resource it cannot replace and leaves no durable verdict artifact.
|
|
|
90
90
|
explicitly in the merge record so the gap is visible rather than invisible.
|
|
91
91
|
|
|
92
92
|
**Verdicts are artifacts:** the runner saves them to
|
|
93
|
-
|
|
93
|
+
`<gate-reports-dir>/<ISO-week>/<date>/<gate-slug>/` (the project's gate-reports directory) and commits them BEFORE
|
|
94
94
|
removing any worktree. A verdict that exists only in a message cannot be audited
|
|
95
95
|
later.
|
|
96
96
|
|
|
@@ -99,7 +99,7 @@ later.
|
|
|
99
99
|
Mandatory checklist handed to the GATE RUNNER (and to any sub-agent it spawns in
|
|
100
100
|
turn) at dispatch time. Each
|
|
101
101
|
rule below currently lives only as prose warnings scattered through
|
|
102
|
-
|
|
102
|
+
the hotpath-gate efficacy log and gets re-learned per session — hand this
|
|
103
103
|
list to the sub-agent verbatim at dispatch time instead of relying on it
|
|
104
104
|
rediscovering these the hard way:
|
|
105
105
|
|
|
@@ -570,7 +570,7 @@ diff — don't bounce reflexively.
|
|
|
570
570
|
|
|
571
571
|
## Efficacy tracking (v1 acceptance requirement)
|
|
572
572
|
|
|
573
|
-
After every gate run, record the outcome in
|
|
573
|
+
After every gate run, record the outcome in the hotpath-gate efficacy log
|
|
574
574
|
(created lazily on first run). This is a durable, committed, grep-queryable log
|
|
575
575
|
that measures v1's real-world efficacy so we can iterate to v2 with data.
|
|
576
576
|
|
|
@@ -609,7 +609,7 @@ rows.
|
|
|
609
609
|
| `CLEAN-WITH-GATE-FIXES` | Gate found only mechanical bookkeeping gaps, fixed them itself as a GATE-AUTHORED commit, re-ran only the affected checks, merge proceeded |
|
|
610
610
|
|
|
611
611
|
**Retrospective reclassification:** When triaging a new wedge or incident, grep
|
|
612
|
-
|
|
612
|
+
the hotpath-gate efficacy log for the merged branch/date. If the gate
|
|
613
613
|
previously logged `CLEAN` for that branch, append a follow-up line referencing
|
|
614
614
|
the original entry:
|
|
615
615
|
`- YYYY-MM-DD | branch=<branch> | RECLASSIFIED: CLEAN->SLIPPED | notes=<what the gate missed + which lens should have caught it>`.
|
|
@@ -625,13 +625,13 @@ gaps and lens improvements for the next version.
|
|
|
625
625
|
patterns, reference functions, incident prose)
|
|
626
626
|
- `project-hotpath-gate` — the companion builder skill that generates/reconciles
|
|
627
627
|
the config (analog of `project-philosophy`)
|
|
628
|
-
-
|
|
628
|
+
- the hotpath-merge-gate brainstorm record — the locked brainstorm (evidence,
|
|
629
629
|
gap analysis, lens details, decisions)
|
|
630
630
|
- `coordinator-philosophy-merge-gate` — the companion general-structural gate
|
|
631
631
|
(runs in parallel)
|
|
632
632
|
- `.thrum/philosophy.md` — canonical source for general anti-patterns (cross-
|
|
633
633
|
referenced by Lenses 1–2, 6–7 where they overlap)
|
|
634
|
-
-
|
|
634
|
+
- the hotpath-gate efficacy log — running efficacy log (created lazily on
|
|
635
635
|
first gate run, appended after each run)
|
|
636
636
|
|
|
637
637
|
<!-- THRUM-GATE: stage=skill next=review -->
|
|
@@ -130,9 +130,17 @@ run the two-command destroy sequence **in this order**:
|
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
132
|
thrum tmux kill <session> # kills tmux + runtime first
|
|
133
|
-
thrum worktree teardown <name> # then removes worktree
|
|
133
|
+
thrum worktree teardown <name> # then retires the agent + removes the worktree (salvage first)
|
|
134
|
+
thrum agent delete <agent> # SEPARATE reap, after the worktree is gone
|
|
134
135
|
```
|
|
135
136
|
|
|
137
|
+
`worktree teardown` does NOT delete the agent: it is preparation for deleting
|
|
138
|
+
one. The row stays `retired` (hidden from default `agent list` / `team` /
|
|
139
|
+
overview / UI; visible with `--include-inactive` / `--phase retired`) until
|
|
140
|
+
`thrum agent delete` or the sweeper reaps it. `agent delete` refuses an agent
|
|
141
|
+
whose linked worktree still exists or whose process (pid/tmux) is live, and
|
|
142
|
+
refuses coordinators; correct order is teardown (with salvage) THEN delete.
|
|
143
|
+
|
|
136
144
|
Sanity-check: after teardown, `tmux list-sessions` should NOT show the agent's
|
|
137
145
|
session. If it does, the runtime was never killed and step 1 was skipped.
|
|
138
146
|
|
|
@@ -169,7 +169,7 @@ The §3 lens gates (hotpath + philosophy) are the judgement half. The per-merge
|
|
|
169
169
|
executed half is **scoped to the change**:
|
|
170
170
|
|
|
171
171
|
- Build the merged tree (`go build ./...`).
|
|
172
|
-
- 🔴 **RULING
|
|
172
|
+
- 🔴 **RULING:** the full sharded race suite —
|
|
173
173
|
`scripts/test-run.sh race all` (~15-20 min) — on the **merged tree**, every
|
|
174
174
|
Pass-3 verdict. This replaces the prior "changed packages only (+ rpc shard
|
|
175
175
|
sweep)" race scoping below the ruling; a targeted `-run` is still never
|
|
@@ -227,7 +227,7 @@ the UI lane, the full tripwire/boxstate reconciliation — is the **periodic
|
|
|
227
227
|
full-gate cadence**, an agent run on the dedicated isolation box, and it is its own
|
|
228
228
|
skill: **`coordinator-full-gate`**. A merge never triggers it, and its merge-base
|
|
229
229
|
replay is not a per-merge step. Running it per merge is ~2h that on a common-mode-
|
|
230
|
-
red trunk fails on the baseline every time. The RULING
|
|
230
|
+
red trunk fails on the baseline every time. The RULING above
|
|
231
231
|
widens only the **race lane** to full-sharded per merge — it does not pull the
|
|
232
232
|
other lanes (integration, UI, mutation-check) into the per-merge path.
|
|
233
233
|
|
|
@@ -288,12 +288,12 @@ agreeing (`rev-parse origin/<branch>` and `ls-remote`). **Assert the branch TIP*
|
|
|
288
288
|
## 7. AFTER THE MERGE — four things, none optional
|
|
289
289
|
|
|
290
290
|
1. **The verdict artifact.** The runner saves verdicts to
|
|
291
|
-
|
|
291
|
+
`<gate-reports-dir>/<ISO-week>/<date>/<gate-slug>/` (the project's gate-reports directory) and **commits them
|
|
292
292
|
BEFORE removing any worktree.** Save → commit → then destroy. A verdict that
|
|
293
293
|
exists only in a message cannot be audited later. The artifact must be able to
|
|
294
294
|
carry a `CLEAN-WITH-GATE-FIXES` outcome, and when one applies it cites the
|
|
295
295
|
`GATE-AUTHORED` commit SHA.
|
|
296
|
-
2. **The efficacy row**, in
|
|
296
|
+
2. **The efficacy row**, in the hotpath-gate efficacy log — required for
|
|
297
297
|
**every** trigger-dir merge, including `SKIPPED_WITH_EVIDENCE` rows and
|
|
298
298
|
orchestrator-run merges. The log measures gate **coverage**, not just outcomes.
|
|
299
299
|
Record `FALSE_POSITIVE=0` explicitly on a clean run so the zero is a
|
|
@@ -364,7 +364,7 @@ hides staleness.
|
|
|
364
364
|
- `coordinator-hotpath-merge-gate` · `coordinator-philosophy-merge-gate` — the lenses
|
|
365
365
|
- `coordinator-running-review-cycles` — the dual-review cycle feeding §1
|
|
366
366
|
- `coordinator-branch-split-on-block` — when a gate blocks one concern of a bundled branch
|
|
367
|
-
-
|
|
367
|
+
- the gate-reports README — the artifact contract
|
|
368
368
|
- `.thrum/hotpath-gate.json` · `.thrum/philosophy.md` — the configs; read these, not prose
|
|
369
369
|
|
|
370
370
|
## Project-specific rules (already loaded)
|
|
@@ -63,7 +63,7 @@ the one resource it cannot replace and leaves no durable verdict artifact.
|
|
|
63
63
|
explicitly in the merge record so the gap is visible rather than invisible.
|
|
64
64
|
|
|
65
65
|
**Verdicts are artifacts:** the runner saves them to
|
|
66
|
-
|
|
66
|
+
`<gate-reports-dir>/<ISO-week>/<date>/<gate-slug>/` (the project's gate-reports directory) and commits them BEFORE
|
|
67
67
|
removing any worktree. A verdict that exists only in a message cannot be audited
|
|
68
68
|
later.
|
|
69
69
|
|
|
@@ -204,7 +204,7 @@ detection is the 10a/10b/10d probe set, not a single grep.
|
|
|
204
204
|
## Lens 10 — Conflated multi-responsibility service (Anti-Pattern #10)
|
|
205
205
|
|
|
206
206
|
This lens uses three sub-procedures from the single-responsibility standing rule
|
|
207
|
-
(
|
|
207
|
+
(the project's single-responsibility decision record).
|
|
208
208
|
|
|
209
209
|
### 10a — Structural backstop (grep before you trust prose review)
|
|
210
210
|
|
|
@@ -453,7 +453,7 @@ diff — don't bounce reflexively.
|
|
|
453
453
|
|
|
454
454
|
- `.thrum/philosophy.md` — canonical source for all anti-patterns and red flags
|
|
455
455
|
checked by this gate
|
|
456
|
-
-
|
|
456
|
+
- the single-responsibility decision record — full
|
|
457
457
|
standing rule with the three-class scope model, enforcement architecture
|
|
458
458
|
(3a/3b/3c/3d), and worked examples of previously-traced conflated services
|
|
459
459
|
|
|
@@ -90,3 +90,4 @@ The node's local runbook is `coordinator-deploying-a-node` — reference that sk
|
|
|
90
90
|
- If `fleet.deployment.thrum.products` exists (vestigial), STOP — products live only at `fleet.deployment.products`.
|
|
91
91
|
- If a node reports success by exit code alone without observed-effect verification, treat as not-verified.
|
|
92
92
|
- Never wrap a service restart in a blind timeout on the node (see node skill `service.never_timeout` and `migration.signal_rules`).
|
|
93
|
+
- 🔴 A **macOS** node (including the build host / primary) restarted with the plain `service.restart_command` instead of `service.mac_restart_command` (Thrum: `scripts/mac-daemon-restart-via-cron.sh`). The daemon loses its Local Network permission and its LAN peers go dark while it looks healthy. Put the mac command in every macOS node's dispatch, and use it yourself when you roll the build host last.
|
|
@@ -59,7 +59,7 @@ read-only scoping) are in "Core Pattern" below.
|
|
|
59
59
|
**Every prompt MUST scope the read-only restriction around its own output
|
|
60
60
|
file, in the same breath:** `READ-ONLY EVERYWHERE EXCEPT YOUR OUTPUT FILE —
|
|
61
61
|
do not modify source, tests, config, git state, or the issue tracker; you
|
|
62
|
-
MUST write exactly
|
|
62
|
+
MUST write exactly <topic-dir>/findings_N.md, and that write is
|
|
63
63
|
expected and authorized.` Omitting the exception is the most common cause of
|
|
64
64
|
a fan-out that returns nothing — see Common Mistakes.
|
|
65
65
|
|
|
@@ -134,7 +134,7 @@ Prefer `cat >> <path> <<'EOF'` to append on later rounds.
|
|
|
134
134
|
```text
|
|
135
135
|
READ-ONLY EVERYWHERE EXCEPT YOUR OUTPUT FILE.
|
|
136
136
|
Do not modify source, tests, config, git state, or the issue tracker.
|
|
137
|
-
You MUST write your findings to exactly:
|
|
137
|
+
You MUST write your findings to exactly: <topic-dir>/findings_N.md
|
|
138
138
|
Write is fine for creating it. To UPDATE it on a later round, Read it first or
|
|
139
139
|
append with a heredoc (cat >> <path> <<'EOF' ... EOF) — Write refuses to
|
|
140
140
|
overwrite a file it has not Read. That single write is expected and authorized.
|
|
@@ -44,7 +44,7 @@ monotonic-ish and globally comparable; the larger ULID wins.
|
|
|
44
44
|
| Concurrent edits to the SAME field | Larger-ULID event wins. Loser preserved in JSONL. |
|
|
45
45
|
| Concurrent tombstones | Idempotent. One delete-event wins by ts; status=purged. |
|
|
46
46
|
| Edit + delete, delete-ULID > edit-ULID | Final `status=purged`. |
|
|
47
|
-
| Edit + delete, edit-ULID > delete-ULID AND edit explicitly sets `status=active` | Edit wins → `status=active` (undelete).
|
|
47
|
+
| Edit + delete, edit-ULID > delete-ULID AND edit explicitly sets `status=active` | Edit wins → `status=active` (undelete; needs a license). |
|
|
48
48
|
| Edit + delete, edit-ULID > delete-ULID AND edit does NOT touch `status` | Patch applies to body/tags but `status=purged` persists. |
|
|
49
49
|
|
|
50
50
|
LWW is acceptable here because memories aren't a high-write-conflict surface
|
|
@@ -94,6 +94,8 @@ Undelete by writing `status=active`:
|
|
|
94
94
|
thrum memory edit <id> --status active
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
Note: `memory edit` (including `--status active` undelete) requires a license (memory.substrate); soft delete (`memory delete`) and reads stay open.
|
|
98
|
+
|
|
97
99
|
Undelete works regardless of who won the concurrent edit/delete race (see LWW
|
|
98
100
|
table above). The audit history is intact, so the recovered memory carries its
|
|
99
101
|
full provenance.
|
|
@@ -116,10 +118,10 @@ Type DELETE <id> to confirm: DELETE <id>
|
|
|
116
118
|
The exact token (`DELETE <id>`) must be typed back — matches beads'
|
|
117
119
|
`DESTROY-<prefix>` pattern. There is no `--yes` bypass.
|
|
118
120
|
|
|
119
|
-
| Mode | Mechanism | Recovery
|
|
120
|
-
| --------------- | -------------------------------------------------------------- |
|
|
121
|
-
| Soft (default) | `memory.delete` → projection `status=purged` | `thrum memory edit <id> --status active` |
|
|
122
|
-
| Hard (`--hard`) | `memory.purge_executed` → physical JSONL removal at compaction | Not recoverable post-compaction
|
|
121
|
+
| Mode | Mechanism | Recovery |
|
|
122
|
+
| --------------- | -------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
123
|
+
| Soft (default) | `memory.delete` → projection `status=purged` | `thrum memory edit <id> --status active` (license required) |
|
|
124
|
+
| Hard (`--hard`) | `memory.purge_executed` → physical JSONL removal at compaction | Not recoverable post-compaction |
|
|
123
125
|
|
|
124
126
|
## When NOT to hard-delete
|
|
125
127
|
|
|
@@ -25,7 +25,7 @@ Edge kind is an open string (same hybrid pattern as memory kind). Future edges
|
|
|
25
25
|
like `blocks`, `duplicates`, or `parent-child` can be added without schema
|
|
26
26
|
change.
|
|
27
27
|
|
|
28
|
-
Add and remove edges
|
|
28
|
+
Add and remove edges. `memory link` and `memory edit` require a license (memory.substrate); `memory unlink` does not.
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
thrum memory link <a> supersedes <b>
|
|
@@ -59,7 +59,7 @@ thrum memory unlink <new-id> supersedes <old-id>
|
|
|
59
59
|
thrum memory edit <old-id> --status active
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Both steps are required. If you only need to keep the historical link but
|
|
62
|
+
The `--status active` step needs a license; without one the superseded memory stays superseded. Both steps are required. If you only need to keep the historical link but
|
|
63
63
|
re-surface the old memory, just run the `--status active` edit and leave the
|
|
64
64
|
edge in place.
|
|
65
65
|
|
|
@@ -21,6 +21,8 @@ thrum memory edit <id> --title "<updated title>"
|
|
|
21
21
|
thrum memory edit <id> --add-tag <new-tag> # appends (repeatable); --rm-tag removes
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
`memory edit` requires a license (memory.substrate); `delete` and `unlink` do not.
|
|
25
|
+
|
|
24
26
|
Edits create a **history record** — the daemon retains the prior version. View
|
|
25
27
|
edit history:
|
|
26
28
|
|
|
@@ -653,7 +653,7 @@ thrum worktree teardown <name>
|
|
|
653
653
|
```
|
|
654
654
|
|
|
655
655
|
> After set-phase=retired, `thrum team` and the active UI views no longer show
|
|
656
|
-
> this agent (sidebar, pickers, badge, kbd-nav). It remains in the Retired tab.
|
|
656
|
+
> this agent (sidebar, pickers, badge, kbd-nav). It remains in the Retired tab (teardown retires, it does not delete the row).
|
|
657
657
|
> Its messages + runtime transcript persist for audit. `thrum agent delete` is
|
|
658
658
|
> operator-only (gated to the coordinator) and is NOT an orchestrator tool —
|
|
659
659
|
> `set-phase retired` is the orchestrator's retirement command for both normal
|
|
@@ -131,7 +131,7 @@ The procedure, every time, no shortcuts:
|
|
|
131
131
|
another peer's panes read-only regardless of capability.
|
|
132
132
|
|
|
133
133
|
**Worked example** — the standard two-variant Claude Code tool-confirmation
|
|
134
|
-
modal (
|
|
134
|
+
modal (the daemon's permission patterns, pattern `tool_confirmation`,
|
|
135
135
|
dialect `claude-tui`), captured verbatim:
|
|
136
136
|
|
|
137
137
|
```
|
|
@@ -149,7 +149,7 @@ dialect `claude-tui`), captured verbatim:
|
|
|
149
149
|
allow-rule beyond this one command; the modal judgment rubric above is a
|
|
150
150
|
per-command judgment, not a license to mint blanket permissions. This is
|
|
151
151
|
pinned by `ApproveKey: "1", // Yes (once) — NEVER "2"` in
|
|
152
|
-
|
|
152
|
+
the daemon's permission patterns and tested by
|
|
153
153
|
`TestApproveKeyNeverForeverAllow`.
|
|
154
154
|
|
|
155
155
|
## Your own restart (auto-restart-at-ctx-threshold)
|
|
@@ -45,8 +45,8 @@ NOTIFY_CLASSES = VALID_CLASSES - {"ordinary"}
|
|
|
45
45
|
REDACTION_TOKEN = "[REDACTED_SECRET]"
|
|
46
46
|
LOCAL_PATH_REDACTION_TOKEN = "[REDACTED_LOCAL_PATH]"
|
|
47
47
|
SECRET_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
|
|
48
|
-
("private_key_block", re.compile(r"-----BEGIN [
|
|
49
|
-
("env_secret_assignment", re.compile(r"(?im)^\s*(?:export\s+)?[
|
|
48
|
+
("private_key_block", re.compile(r"-----BEGIN [0-9A-Z ]*PRIVATE KEY-----.*?-----END [0-9A-Z ]*PRIVATE KEY-----", re.S)),
|
|
49
|
+
("env_secret_assignment", re.compile(r"(?im)^\s*(?:export\s+)?[0-9A-Z_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PRIVATE[_-]?KEY|AUTH[_-]?KEY)[0-9A-Z_]*\s*=\s*([^\s#]+)")),
|
|
50
50
|
("bearer_token", re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]{16,}")),
|
|
51
51
|
("openai_key", re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b")),
|
|
52
52
|
("thrum_key", re.compile(r"\btskey-[A-Za-z0-9_-]{16,}\b")),
|
|
@@ -318,7 +318,7 @@ def prompt_payload(state: dict[str, Any], provider: str = TYPESAFE_PROVIDER) ->
|
|
|
318
318
|
},
|
|
319
319
|
}
|
|
320
320
|
if provider == OPENROUTER_PROVIDER:
|
|
321
|
-
payload["user"] = "
|
|
321
|
+
payload["user"] = "roster-watch-filter"
|
|
322
322
|
return payload
|
|
323
323
|
|
|
324
324
|
|
|
@@ -462,8 +462,8 @@ def classify_remote(state: dict[str, Any], provider: str, api_key: str, timeout:
|
|
|
462
462
|
}
|
|
463
463
|
if provider == OPENROUTER_PROVIDER:
|
|
464
464
|
headers.update({
|
|
465
|
-
"HTTP-Referer": "https://github.com/
|
|
466
|
-
"X-Title": "
|
|
465
|
+
"HTTP-Referer": "https://github.com/leonletto/thrum-pro.git",
|
|
466
|
+
"X-Title": "roster-watch-filter",
|
|
467
467
|
})
|
|
468
468
|
last_error = None
|
|
469
469
|
last_category = None
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# GHOST-TIP DETECTION: Claude Code's TUI renders an unaccepted inline
|
|
33
33
|
# autocomplete suggestion in the input box using the literal SGR
|
|
34
34
|
# "dim/faint" escape code (ESC[2m ... ESC[0m) — confirmed by direct byte
|
|
35
|
-
# inspection (
|
|
35
|
+
# inspection (a reconciler agent's "Stop the sweep loop
|
|
36
36
|
# for now" tip). Real typed/submitted text never carries that code. `thrum
|
|
37
37
|
# tmux capture` returns plain text with all styling stripped, so a script
|
|
38
38
|
# parsing ONLY that output cannot tell a live ghost-tip apart from real
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
#
|
|
51
51
|
# REDIRECT FIX (the actual bug this reconciliation targets):
|
|
52
52
|
# `watch_params.json` lives under the SHARED, redirect-resolved `agents/`
|
|
53
|
-
# tree (
|
|
53
|
+
# tree (the AgentDir path helper: "the agents/ tree is shared,
|
|
54
54
|
# not per-worktree"), not under this script's own worktree. Both source
|
|
55
55
|
# scripts read it as if it were always reachable via a hardcoded/relative
|
|
56
56
|
# main-repo path (`cd /Users/you/dev/thrum` +
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
# feature worktree. `resolve_watch_params()` below replaces that implicit
|
|
61
61
|
# assumption with the same explicit `.thrum/redirect`-following idiom
|
|
62
62
|
# already used by `scripts/heartbeat-lib.sh:225-232` and
|
|
63
|
-
#
|
|
63
|
+
# the check-inbox hook script.
|
|
64
64
|
#
|
|
65
65
|
# PORTABILITY FIX: both source scripts also hardcoded a single operator's
|
|
66
66
|
# absolute paths (`cd /Users/you/dev/thrum`,
|
|
@@ -105,7 +105,7 @@ set -uo pipefail
|
|
|
105
105
|
# ---------------------------------------------------------------------------
|
|
106
106
|
# Args: worktree_root. Prints the redirect-resolved absolute .thrum/ dir.
|
|
107
107
|
# Matches the idiom already used by scripts/heartbeat-lib.sh's
|
|
108
|
-
# hb_sessions_dir() and
|
|
108
|
+
# hb_sessions_dir() and the check-inbox hook script's SPOOL_THRUM
|
|
109
109
|
# resolution: no redirect file present (main repo) -> use the local .thrum/
|
|
110
110
|
# as-is; redirect file present (feature worktree) -> follow it to the main
|
|
111
111
|
# repo's .thrum/.
|
|
@@ -648,7 +648,7 @@ except: print(0)
|
|
|
648
648
|
# entry resolves to a real agent_id via `thrum team --offline --json`, and
|
|
649
649
|
# log ROSTER-CHECK OK/FAIL. Cheap and valuable regardless of execution
|
|
650
650
|
# model (catches the exact "bare `<box>`-as-hostname" class of stale-
|
|
651
|
-
# roster-entry bug watcher-primary hit
|
|
651
|
+
# roster-entry bug watcher-primary hit). Runs AFTER
|
|
652
652
|
# the dynamic roster is loaded from watch_params.json, not against a
|
|
653
653
|
# hardcoded array.
|
|
654
654
|
# ---------------------------------------------------------------------------
|
|
@@ -69,8 +69,8 @@ AGENT="watcher_primary"
|
|
|
69
69
|
# Scenario 1 (MANDATORY, cmain-required regression guard): a worktree that
|
|
70
70
|
# redirects to a main-repo tree. The "roster-editing agent's Edit-tool write"
|
|
71
71
|
# lands in the REDIRECT TARGET (main-repo) copy -- that's the physically
|
|
72
|
-
# correct, shared, redirect-resolved location per
|
|
73
|
-
#
|
|
72
|
+
# correct, shared, redirect-resolved location per the AgentDir path helper
|
|
73
|
+
# ("the agents/ tree is shared, not per-worktree"), and it's what
|
|
74
74
|
# resolve_watch_params must read. A worktree-LOCAL copy at the naive,
|
|
75
75
|
# unresolved path is a decoy: it's what a broken script (SCRIPT_DIR-relative,
|
|
76
76
|
# no redirect-follow) would read instead, and it must be ignored.
|
|
@@ -94,7 +94,7 @@ run_ssh() {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
# ssh-add retry: if the SSH hop itself fails at the connection layer (not a
|
|
97
|
-
#
|
|
97
|
+
# CLI-level error), the caller's identities may need re-adding to the
|
|
98
98
|
# keychain-backed ssh-agent. No default keys ship here -- this is a
|
|
99
99
|
# generic plugin script, and baking in one operator's specific key
|
|
100
100
|
# filenames would be wrong for every other fleet/operator that copies it.
|
|
@@ -74,7 +74,7 @@ AGENT_NAME="$(basename -- "${SCRIPT_DIR}")"
|
|
|
74
74
|
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../../.." >/dev/null 2>&1 && pwd)"
|
|
75
75
|
# Resolve watch_params.json through .thrum/redirect. The
|
|
76
76
|
# agents/ tree is the SHARED, redirect-resolved (main-repo) tree, not
|
|
77
|
-
# per-worktree (see
|
|
77
|
+
# per-worktree (see the AgentDir path helper) -- reading it
|
|
78
78
|
# straight off SCRIPT_DIR (this copy's own worktree-local location) reads
|
|
79
79
|
# the wrong, stale file whenever this script is deployed into a feature
|
|
80
80
|
# worktree that redirects: a roster-editing agent's Edit-tool write lands
|
|
@@ -185,7 +185,7 @@ log "roster: ${ROSTER[*]}"
|
|
|
185
185
|
#
|
|
186
186
|
# NOTE ON THE TWO-STEP TOPOLOGY LOOKUP: `thrum state list --kind topology
|
|
187
187
|
# --json`'s CLI output does NOT include each entry's `.value` (verified
|
|
188
|
-
# against
|
|
188
|
+
# against the CLI's stateListEntry struct, which has no Value
|
|
189
189
|
# field, and confirmed live -- `state list --json` returns only
|
|
190
190
|
# kind/scope/method/as_of/established_by). Only `state get`/`state show`
|
|
191
191
|
# emit the full entry including `.value`. So topology resolution is
|
|
@@ -338,7 +338,7 @@ for agent in "${ROSTER[@]}"; do
|
|
|
338
338
|
STATUS="clean"
|
|
339
339
|
MATCH_NAME=""
|
|
340
340
|
|
|
341
|
-
# thrum detect exit-code contract
|
|
341
|
+
# thrum detect exit-code contract:
|
|
342
342
|
# exit 0 + matched entry name on stdout -> match
|
|
343
343
|
# exit 1, silent -> no match
|
|
344
344
|
# `if VAR=$(...); then` is exempt from `set -e` semantics for this
|
|
@@ -231,7 +231,7 @@ undetected or unprovided. Fill in:
|
|
|
231
231
|
|
|
232
232
|
### Step 6a: MANDATORY — wire the project's security/threat model into the Decision Framework
|
|
233
233
|
|
|
234
|
-
If the project has a written threat model (search
|
|
234
|
+
If the project has a written threat model (search the project's security-model directory,
|
|
235
235
|
`docs/security*.md`, `SECURITY.md`), the rendered philosophy MUST carry a
|
|
236
236
|
Decision Framework row pointing at it — and that row MUST NOT be keyed on the
|
|
237
237
|
word "attacker".
|
|
@@ -260,8 +260,8 @@ components, data flow, and interfaces.
|
|
|
260
260
|
|
|
261
261
|
### 🔴 Phase 1a — MANDATORY security-frame check (do this BEFORE decomposing)
|
|
262
262
|
|
|
263
|
-
**Read the project's threat model** (
|
|
264
|
-
|
|
263
|
+
**Read the project's threat model** (its security-model document, or
|
|
264
|
+
`docs/security*.md` / `SECURITY.md`) **whenever the plan
|
|
265
265
|
contains ANY of the following — and note that NONE of them need name an
|
|
266
266
|
attacker:**
|
|
267
267
|
|
|
@@ -86,14 +86,19 @@ thrum tmux launch <session> [--runtime <r>] Start runtime in session (REQUIRED
|
|
|
86
86
|
### Monitor Jobs
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
-
thrum monitor
|
|
90
|
-
thrum monitor list
|
|
91
|
-
thrum monitor show <name> Show job details
|
|
89
|
+
thrum monitor start --name <n> --match <re> --to @agent [--schedule "<cron>"] [--notify-on-success] -- <cmd> [args...]
|
|
90
|
+
thrum monitor list [--all] List monitor jobs (running; --all adds stopped)
|
|
91
|
+
thrum monitor show <name> Show job details
|
|
92
|
+
thrum monitor logs <name> Recent matched lines
|
|
93
|
+
thrum monitor update <name> Change match, schedule, target or debounce in place
|
|
92
94
|
thrum monitor stop <name> Stop a monitor job
|
|
93
|
-
thrum monitor
|
|
95
|
+
thrum monitor delete <name> Remove a job and free its name
|
|
94
96
|
thrum monitor restart <name> Restart a stopped job
|
|
95
97
|
```
|
|
96
98
|
|
|
99
|
+
Reminders, periodic script checks and delivery verification: see the
|
|
100
|
+
`using-monitors` skill.
|
|
101
|
+
|
|
97
102
|
### Daemon & Sync
|
|
98
103
|
|
|
99
104
|
```bash
|
|
@@ -255,7 +255,7 @@ Flags:
|
|
|
255
255
|
--runtime string Runtime preset: claude, codex, cursor, gemini, opencode, auggie, cli-only
|
|
256
256
|
--preamble-file string Custom preamble file to compose with default preamble
|
|
257
257
|
--no-init Skip runtime config generation, just register agent
|
|
258
|
-
--force
|
|
258
|
+
--force Force runtime config reinitialization; project instruction files are preserved
|
|
259
259
|
--dry-run Preview changes without writing files or registering
|
|
260
260
|
--no-agent-pid Persist agent_pid=0 instead of detecting the runtime ancestor; defers PID claim to first /thrum:prime (used for inline tmux quickstart)
|
|
261
261
|
```
|
|
@@ -379,6 +379,10 @@ thrum agent delete <name>
|
|
|
379
379
|
thrum agent delete coordinator_1B9K --force
|
|
380
380
|
```
|
|
381
381
|
|
|
382
|
+
Refuses an agent whose linked worktree still exists (run `worktree teardown`
|
|
383
|
+
first), a live pid/tmux process, and coordinators. Order: teardown (with
|
|
384
|
+
salvage), then delete.
|
|
385
|
+
|
|
382
386
|
Flags:
|
|
383
387
|
|
|
384
388
|
```text
|
|
@@ -858,7 +862,7 @@ thrum tmux cancel <command-id> # Cancel a queued or active comma
|
|
|
858
862
|
--intent string Initial work intent description
|
|
859
863
|
--runtime string Runtime preset: claude, codex, cursor, gemini, opencode, auggie
|
|
860
864
|
--no-agent Skip agent registration (create session only)
|
|
861
|
-
--force
|
|
865
|
+
--force Re-register even if agent exists; kill and recreate an existing session
|
|
862
866
|
```
|
|
863
867
|
|
|
864
868
|
Without `--no-agent`, the command errors if `--name`, `--role`, and `--module`
|
|
@@ -900,7 +904,7 @@ thrum worktree create <name> --detach # Detached HEAD worktree
|
|
|
900
904
|
thrum worktree create <name> \
|
|
901
905
|
--name <agent> --role <role> --module <mod> # Create worktree + register agent
|
|
902
906
|
thrum worktree setup <name> # Alias for worktree create
|
|
903
|
-
thrum worktree teardown <name> #
|
|
907
|
+
thrum worktree teardown <name> # Retire agent + remove worktree, keep branch (does NOT delete the agent; reap with `thrum agent delete` after)
|
|
904
908
|
thrum worktree teardown <name> --delete-branch # Also delete the worktree's branch
|
|
905
909
|
thrum worktree list # List worktrees with agent info
|
|
906
910
|
```
|
|
@@ -939,51 +943,52 @@ later.
|
|
|
939
943
|
|
|
940
944
|
## Monitor Jobs
|
|
941
945
|
|
|
942
|
-
Run a
|
|
943
|
-
|
|
944
|
-
|
|
946
|
+
Run a command, filter its output through a regex, and deliver matching lines as
|
|
947
|
+
Thrum messages. Without `--schedule` the command runs continuously and restarts
|
|
948
|
+
with backoff; with `--schedule` it runs once per cron tick. Jobs persist across
|
|
949
|
+
daemon restarts. Max 100 concurrent. The command must follow `--`.
|
|
945
950
|
|
|
946
951
|
```bash
|
|
947
952
|
thrum monitor start --name <n> --match <re> --to @agent -- <cmd> [args...]
|
|
948
953
|
thrum monitor start --name <n> --match <re> --to @agent \
|
|
949
|
-
--debounce 120s --env KEY=VALUE -- <cmd>
|
|
954
|
+
--schedule "*/5 * * * *" --notify-on-success --debounce 120s --env KEY=VALUE -- <cmd>
|
|
950
955
|
thrum monitor list # Running jobs only
|
|
951
956
|
thrum monitor list --all # Include stopped/dead (<1 week)
|
|
952
957
|
thrum monitor show <id|name> # Full detail (env values redacted)
|
|
953
|
-
thrum monitor
|
|
954
|
-
thrum monitor
|
|
955
|
-
thrum monitor
|
|
958
|
+
thrum monitor logs <id|name> -n 50 # Last 50 matched lines (default 20)
|
|
959
|
+
thrum monitor update <id|name> --match <re> # Change match/schedule/target/debounce in place
|
|
960
|
+
thrum monitor stop <id|name> # SIGTERM, 5s, SIGKILL
|
|
956
961
|
thrum monitor restart <id|name> # Restart dead/stopped monitor
|
|
962
|
+
thrum monitor delete <id|name> # Remove the record and free the name
|
|
963
|
+
thrum monitor adopt <id|name> # Claim a monitor for this daemon
|
|
957
964
|
```
|
|
958
965
|
|
|
959
|
-
|
|
966
|
+
`monitor add` is an alias for `monitor start`.
|
|
960
967
|
|
|
961
968
|
`monitor start` flags:
|
|
962
969
|
|
|
963
970
|
```text
|
|
964
971
|
--name string Unique monitor name (required)
|
|
965
|
-
--match string Regex pattern
|
|
972
|
+
--match string Regex pattern - matching lines trigger a message (required)
|
|
966
973
|
--to string Target agent or group, e.g. @coordinator (required)
|
|
967
|
-
--
|
|
968
|
-
--
|
|
974
|
+
--schedule string 5-field cron expression; runs the command once per tick
|
|
975
|
+
--notify-on-success Deliver a generic [OK] message for a successful run in which no line matched
|
|
976
|
+
--debounce duration Leading-edge debounce window, minimum 30s (default 1m)
|
|
977
|
+
--env stringArray Environment variable in KEY=VALUE form (repeatable)
|
|
969
978
|
--cwd string Working directory for the command (default: current dir)
|
|
970
979
|
```
|
|
971
980
|
|
|
972
|
-
`monitor
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
```
|
|
981
|
+
`monitor update` flags: `--match`, `--to`, `--schedule`, `--debounce`.
|
|
982
|
+
`--schedule` changes the cron of a scheduled monitor at any time; setting or
|
|
983
|
+
removing a schedule switches the mode, which is refused while the monitor is
|
|
984
|
+
running and allowed while it is stopped.
|
|
977
985
|
|
|
978
|
-
`monitor logs`
|
|
979
|
-
|
|
980
|
-
```text
|
|
981
|
-
-n, --limit int Max number of matches to return (default 20)
|
|
982
|
-
```
|
|
986
|
+
`monitor logs` flag: `-n, --limit int` (default 20).
|
|
983
987
|
|
|
984
|
-
Key constraints: local Unix socket only,
|
|
985
|
-
|
|
986
|
-
|
|
988
|
+
Key constraints: local Unix socket only, lines over 2KB are truncated with a
|
|
989
|
+
marker, one matching line is delivered per debounce window, and a message is
|
|
990
|
+
sent when the child exits non-zero. See the `using-monitors` skill for
|
|
991
|
+
reminders, script checks and delivery rules.
|
|
987
992
|
|
|
988
993
|
---
|
|
989
994
|
|
|
@@ -70,7 +70,7 @@ thrum memory edit <id> --short @newshort.md
|
|
|
70
70
|
thrum memory edit <id> --add-tag v0.11 --rm-tag draft
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
Link an edge
|
|
73
|
+
Link an edge. `memory create`, `edit` and `link` need a license (memory.substrate); reads, `delete`, `purge` and `unlink` do not.
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
thrum memory link <new-id> supersedes <old-id>
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-monitors
|
|
3
|
+
description: "Use when scheduling a reminder, watching a log, or running a periodic check that must reach an agent - thrum monitor start, cron --schedule, --match, --notify-on-success, --debounce, recurring nudge, script check with exit codes, why a monitor message did not arrive, how to verify a monitor delivered."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Thrum Monitors - Reminders And Script Checks
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
A monitor runs a command, keeps the output lines that match a regex, and
|
|
11
|
+
delivers them as Thrum messages to a target: a continuous monitor sends each
|
|
12
|
+
kept line, a scheduled monitor sends all the lines of a run in one message. With
|
|
13
|
+
`--schedule` it runs the command once per cron tick; without it the command runs continuously
|
|
14
|
+
and restarts with backoff when it exits. Monitors persist across daemon
|
|
15
|
+
restarts and run only on the daemon that owns them.
|
|
16
|
+
|
|
17
|
+
Every flag below appears in `thrum monitor start --help`. Re-read the help
|
|
18
|
+
before relying on a flag not listed here.
|
|
19
|
+
|
|
20
|
+
## Command Surface
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
thrum monitor start --name <n> --match <re> --to @agent [--schedule "<cron>"] \
|
|
24
|
+
[--notify-on-success] [--debounce 60s] [--env K=V] [--cwd <dir>] -- <cmd> [args...]
|
|
25
|
+
thrum monitor list [--all]
|
|
26
|
+
thrum monitor show <name|id>
|
|
27
|
+
thrum monitor logs <name|id> [-n 50]
|
|
28
|
+
thrum monitor update <name|id> [--match <re>] [--to <t>] [--debounce <d>] [--schedule "<cron>"]
|
|
29
|
+
thrum monitor stop <name|id>
|
|
30
|
+
thrum monitor restart <name|id>
|
|
31
|
+
thrum monitor delete <name|id>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `--name`, `--match` and `--to` are required; the command follows `--`.
|
|
35
|
+
- `--schedule` takes a 5-field cron expression. `update --schedule` changes the
|
|
36
|
+
cron of a scheduled monitor at any time. Setting a first schedule or removing
|
|
37
|
+
it (`--schedule ""`) switches the mode, which is refused while the monitor is
|
|
38
|
+
running and allowed while it is stopped.
|
|
39
|
+
- `--debounce` is leading-edge with a 30s minimum (default 1m).
|
|
40
|
+
- `stop` leaves a stopped record that still holds the name; `delete` frees the
|
|
41
|
+
name. Use a unique name per monitor.
|
|
42
|
+
|
|
43
|
+
## What Is Delivered
|
|
44
|
+
|
|
45
|
+
Each output line is tested against `--match` on its own. A line is truncated at
|
|
46
|
+
2048 bytes with a marker appended.
|
|
47
|
+
|
|
48
|
+
A scheduled (`--schedule`) job is a one-shot run, so its exit code is
|
|
49
|
+
authoritative. It delivers ONE message at exit that carries every matched line,
|
|
50
|
+
in order. A continuous job has no exit code per line, so it classifies and
|
|
51
|
+
delivers and classifies each matched line on its own, by its words.
|
|
52
|
+
|
|
53
|
+
| Scheduled job | Delivered as |
|
|
54
|
+
| --------------------------------------------------------------- | --------------------------------------------- |
|
|
55
|
+
| Non-zero exit, or the command failed to start | `[FAIL] <name>: exit code N` with the output tail, then the matched lines |
|
|
56
|
+
| Exit 0, a matched line starts `ERROR:`, `FATAL:` or `FAIL:` | `[FAIL] <name>: matched error pattern` + all matched lines |
|
|
57
|
+
| Exit 0, highest prefix is `WARN:`, `WARNING:` or `ALERT:` | `[WARN] <name>: matched WARNING` + all matched lines |
|
|
58
|
+
| Exit 0, lines matched, no such prefix | All matched lines, in order, verbatim |
|
|
59
|
+
| Exit 0, nothing matched, `--notify-on-success` | `[OK] <name>: succeeded` |
|
|
60
|
+
| Exit 0, nothing matched | Nothing (logged only) |
|
|
61
|
+
|
|
62
|
+
Cadence: the first non-zero scheduled run sends its `[FAIL]` notice; repeats of
|
|
63
|
+
that failure are not sent again until the outcome changes or the re-escalation
|
|
64
|
+
interval (30 minutes) passes. An exit-0 run that matched lines sends its message
|
|
65
|
+
on every run.
|
|
66
|
+
|
|
67
|
+
On a scheduled job only these prefixes, at the very start of a line, label the
|
|
68
|
+
output, with the colon directly after the word: `ERROR:`, `FATAL:` and `FAIL:`
|
|
69
|
+
fail it; `WARN:`, `WARNING:` and `ALERT:` warn. Any other prefix, including
|
|
70
|
+
`ALERT-<NAME>:`, `ERROR :`, `INFO:`, `OK:`, `NOT_OK:` and lowercase forms, is
|
|
71
|
+
neutral. They are read before any wording in the body, and
|
|
72
|
+
the highest one decides the label. Words such as `done`, `ok`, `complete`, `failures` or `error` neither
|
|
73
|
+
drop nor relabel a line, and `WARN: batch not complete` stays a warning.
|
|
74
|
+
`--notify-on-success` adds the generic `[OK]` only for a run in which nothing
|
|
75
|
+
matched; an empty matched line counts as a match and is announced as
|
|
76
|
+
`[matched N empty line(s)]`.
|
|
77
|
+
|
|
78
|
+
The whole message, including its label and framing, never exceeds the daemon's
|
|
79
|
+
configured message-body limit. Matched lines are sent whole while they fit both
|
|
80
|
+
budgets: the in-memory retention budget (each line costs its bytes plus a small
|
|
81
|
+
fixed overhead, so a line close to the limit, or many short lines, can be cut
|
|
82
|
+
or omitted earlier than their plain total would suggest) and the final rendered
|
|
83
|
+
message including its label. Lines that do not fit are replaced by `[N more
|
|
84
|
+
matched lines omitted]`, and a first line that does not fit is cut and
|
|
85
|
+
marked. The limit is never below 1024 bytes (the configuration floor), enough for
|
|
86
|
+
the framing and an omission marker with ordinary job names; when variable
|
|
87
|
+
framing such as a very long job name does not fit, the monitor sends a bounded
|
|
88
|
+
summary that starts with `+N omitted` instead of the lines. A run keeps only a bounded prefix of its lines in memory (at most
|
|
89
|
+
the delivery limit, or 1 MiB when the limit is disabled, counting a small fixed
|
|
90
|
+
overhead per line) but
|
|
91
|
+
counts every match, so the omitted count is exact and an `ERROR:`/`WARN:` line
|
|
92
|
+
past the retained prefix still sets the label.
|
|
93
|
+
|
|
94
|
+
| Continuous job (no `--schedule`) | Delivered as |
|
|
95
|
+
| --------------------------------------------------------------- | --------------------------------------------- |
|
|
96
|
+
| Contains `error`, `fatal`, `fail`, `permission denied` | `[FAIL] <name>: matched error pattern` + line |
|
|
97
|
+
| Contains `warn` or `warning` | `[WARN] <name>: matched WARNING` + line |
|
|
98
|
+
| Contains a whole word `success`, `succeeded`, `complete`, `completed`, `done`, `ok` | Not delivered (logged only) |
|
|
99
|
+
| Any other matched line | The line, verbatim |
|
|
100
|
+
|
|
101
|
+
A continuous job checks a line for a negated completion word (`not complete`)
|
|
102
|
+
first, then for a level prefix (`ERROR:`, `WARN:`, `INFO:`, optionally after a
|
|
103
|
+
timestamp), then for the words above. Tokens such as `fail=0` are not failures;
|
|
104
|
+
the phrase `0 errors` still contains `error`.
|
|
105
|
+
|
|
106
|
+
Delivery limits:
|
|
107
|
+
|
|
108
|
+
- A continuous job delivers the first matching line in each debounce window at
|
|
109
|
+
once and holds later ones for a trailing summary. A scheduled job has no
|
|
110
|
+
debounce; its lines are aggregated as above.
|
|
111
|
+
- The `[FAIL]` body for a non-zero exit carries only the last 500 bytes of
|
|
112
|
+
output, so the message that matters goes last.
|
|
113
|
+
|
|
114
|
+
## Prompt-Only Reminder
|
|
115
|
+
|
|
116
|
+
A reminder is a monitor whose command only prints a fixed line. Give the line a
|
|
117
|
+
stable prefix and match exactly that prefix.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
thrum monitor start --name standup-nudge --to @me \
|
|
121
|
+
--match '^REMINDER-STANDUP:' --schedule "0 9 * * 1-5" -- \
|
|
122
|
+
echo "REMINDER-STANDUP: post the status update, then reply to the open review"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- Schedule the reminder; words in its text, including "failures", "error" and
|
|
126
|
+
"done", then neither relabel nor drop it, and the exit code decides success.
|
|
127
|
+
- Make the body an instruction with the next concrete step; a bare "reminder"
|
|
128
|
+
carries no action.
|
|
129
|
+
- Add `--notify-on-success` only when a delivered line must be guaranteed for a
|
|
130
|
+
run that matched nothing.
|
|
131
|
+
- A delivered reminder shows the schedule fired. It does not show that the work
|
|
132
|
+
moved. Check the work itself (commits, queue, state) before treating the
|
|
133
|
+
reminder as progress.
|
|
134
|
+
|
|
135
|
+
## Script Check
|
|
136
|
+
|
|
137
|
+
A script check runs a bounded, synchronous probe and reports through its exit
|
|
138
|
+
code and one structured line.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
thrum monitor start --name disk-check --to @me \
|
|
142
|
+
--match '^CHECK-DISK:' --schedule "*/15 * * * *" -- \
|
|
143
|
+
bash -c 'u=$(df --output=pcent / | tail -1 | tr -dc 0-9)
|
|
144
|
+
[ "$u" -lt 90 ] && exit 0
|
|
145
|
+
echo "CHECK-DISK: root volume at ${u} percent used, free space then rerun df -h /"
|
|
146
|
+
exit 3'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Rules:
|
|
150
|
+
|
|
151
|
+
1. Bound every probe with `timeout <secs>`; a hung probe holds the tick.
|
|
152
|
+
2. Exit 0 for healthy and print nothing matching, which keeps the run quiet.
|
|
153
|
+
3. Exit non-zero for a failure; the daemon sends the `[FAIL]` notice with the
|
|
154
|
+
exit code and output tail.
|
|
155
|
+
4. Print a matching line that names the subject, the observed value and the
|
|
156
|
+
next action. A generic `OK` or `done` line carries no information.
|
|
157
|
+
5. Use a distinct stable prefix per outcome (`CHECK-DISK:` for a finding,
|
|
158
|
+
`REMINDER-DISK:` for a nudge) so `--match` selects what must reach the
|
|
159
|
+
target.
|
|
160
|
+
|
|
161
|
+
| Outcome | Script does | Target receives |
|
|
162
|
+
| -------- | ---------------------------------------- | ---------------------------------- |
|
|
163
|
+
| Quiet | Exit 0, no matching line | Nothing |
|
|
164
|
+
| Failure | Non-zero exit, finding line last | One `[FAIL] <name>: exit code N` message with the tail and the finding line |
|
|
165
|
+
| Finding | Exit 0, matching line(s) | The line(s), verbatim (labeled only by an `ERROR:`/`FATAL:`/`FAIL:` or `WARN:`/`WARNING:`/`ALERT:` prefix) |
|
|
166
|
+
| Reminder | Exit 0, one stable-prefix instruction | The line, verbatim |
|
|
167
|
+
|
|
168
|
+
A `--match` that never matches the script's real output delivers nothing and
|
|
169
|
+
raises no error. Test the regex against the exact output before starting.
|
|
170
|
+
|
|
171
|
+
## Verify By Delivery
|
|
172
|
+
|
|
173
|
+
Start the monitor with a unique name, `--to` the verifying agent, and a
|
|
174
|
+
one-minute schedule, then read the delivered message back in full.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
thrum monitor start --name verify-<unique> --to @me --match '^VERIFY-<unique>:' \
|
|
178
|
+
--schedule "* * * * *" -- echo "VERIFY-<unique>: delivery body check"
|
|
179
|
+
thrum inbox --unread
|
|
180
|
+
thrum message get <message-id> # the complete delivered body
|
|
181
|
+
thrum monitor logs verify-<unique> # the matches the daemon recorded
|
|
182
|
+
thrum monitor delete verify-<unique>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Confirm the body carries the prefix, the wording and no unexpected `[FAIL]`
|
|
186
|
+
or `[WARN]` label. Delete the monitor when done; never alter a monitor another
|
|
187
|
+
agent owns.
|
|
188
|
+
|
|
189
|
+
## Troubleshooting
|
|
190
|
+
|
|
191
|
+
- Nothing arrived: `thrum monitor show` for state and last exit, `thrum
|
|
192
|
+
monitor logs` for recorded matches. No recorded match means the regex did not
|
|
193
|
+
match, or (continuous job) the line was completion-classified and suppressed;
|
|
194
|
+
run the command by hand and compare its output with the regex.
|
|
195
|
+
- Message labeled `[FAIL]` unexpectedly: the job exited non-zero, the line starts
|
|
196
|
+
with `ERROR:`/`FATAL:`/`FAIL:`, or (continuous job) the line contains a fail or
|
|
197
|
+
error substring.
|
|
198
|
+
- Name already in use: a stopped record holds it. `thrum monitor delete` it.
|
|
@@ -191,7 +191,7 @@ so this wrapper is defense-in-depth, not load-bearing on its own.
|
|
|
191
191
|
non-peercred transport (browser/WS, unit tests) - but that is a general,
|
|
192
192
|
already-documented property of the RPC layer (identity trust depends on
|
|
193
193
|
transport), not something unique to `state.set`, and it is explicitly out
|
|
194
|
-
of scope per
|
|
194
|
+
of scope per the project's security model: "Thrum does not attempt
|
|
195
195
|
to prevent agent identity forgery" is an owner ruling, stated as
|
|
196
196
|
[INTENDED] and permanent, not tracked as debt.
|
|
197
197
|
|
|
@@ -204,8 +204,7 @@ shapes, chosen by domain, not an oversight.
|
|
|
204
204
|
|
|
205
205
|
Non-goal: fail-closed reject-symmetry for `state.set` (mirroring queue's
|
|
206
206
|
`RejectIdentityKeysHandler`) is a possible future non-security hardening
|
|
207
|
-
preference, not built here - out of scope per
|
|
208
|
-
§1.5.
|
|
207
|
+
preference, not built here - out of scope per the project's security model.
|
|
209
208
|
|
|
210
209
|
## Common Mistakes
|
|
211
210
|
|
|
@@ -73,7 +73,7 @@ yourself.
|
|
|
73
73
|
|
|
74
74
|
## Field Mutability Matrix
|
|
75
75
|
|
|
76
|
-
`Entry`
|
|
76
|
+
The state `Entry` type has five mutability classes, not one
|
|
77
77
|
uniform "immutable field" guard:
|
|
78
78
|
|
|
79
79
|
- **immutable-identity** — part of the `(Kind, Scope)` addressing key, or
|
|
@@ -29,7 +29,7 @@ const SYNC = String.raw `\.git/thrum-sync/a-sync`;
|
|
|
29
29
|
// Command-position anchor: line start or immediately after a shell separator
|
|
30
30
|
// (;, &&, ||), then optional whitespace. This matches `cd`/`git` only when they
|
|
31
31
|
// are the actual command — NOT when those words appear inside a quoted argument
|
|
32
|
-
// preceded by a bare space (the
|
|
32
|
+
// preceded by a bare space (the quoted-argument false-positive: `bd remember "... cd
|
|
33
33
|
// .git/thrum-sync/a-sync ..."` must NOT be denied). Match op-shape, not the mere
|
|
34
34
|
// presence of the path string.
|
|
35
35
|
const CMDPOS = String.raw `(^|;|&&|\|\|)\s*`;
|
|
@@ -31,7 +31,7 @@ const ALLOW = [
|
|
|
31
31
|
["git -C a-sync push", `git -C ${ASYNC} push`],
|
|
32
32
|
["git -C a-sync log (not branch-changing)", `git -C ${ASYNC} log --oneline`],
|
|
33
33
|
["ls the path", `ls ${ASYNC}/`],
|
|
34
|
-
//
|
|
34
|
+
// quoted-argument false-positive guard: cd inside a quoted argument (bare space before it),
|
|
35
35
|
// not at command position -> must be ALLOWED.
|
|
36
36
|
["quoted cd inside bd remember", `bd remember "note: do not cd ${ASYNC} directly"`],
|
|
37
37
|
];
|