opencode-thrum-pro 0.1.0
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-_deviations-protocol.md +87 -0
- package/assets/commands/thrum-_generator-idempotency-protocol.md +15 -0
- package/assets/commands/thrum-_project-rules-protocol.md +18 -0
- package/assets/commands/thrum-_snapshot-protocol.md +265 -0
- package/assets/commands/thrum-_stamp-protocol.md +112 -0
- package/assets/commands/thrum-_verify-before-write-protocol.md +27 -0
- package/assets/commands/thrum-inbox.md +22 -0
- package/assets/commands/thrum-load-context.md +17 -0
- package/assets/commands/thrum-overview.md +17 -0
- package/assets/commands/thrum-prime.md +30 -0
- package/assets/commands/thrum-quickstart.md +14 -0
- package/assets/commands/thrum-reply.md +28 -0
- package/assets/commands/thrum-restart-extended.md +195 -0
- package/assets/commands/thrum-restart.md +293 -0
- package/assets/commands/thrum-send.md +31 -0
- package/assets/commands/thrum-sleep-extended.md +186 -0
- package/assets/commands/thrum-sleep.md +212 -0
- package/assets/commands/thrum-team.md +12 -0
- package/assets/commands/thrum-update-project.md +259 -0
- package/assets/commands/thrum-wait.md +14 -0
- package/assets/skills/adversarial-critique/SKILL.md +242 -0
- package/assets/skills/brainstorm-queue-aware/SKILL.md +167 -0
- package/assets/skills/brainstorm-steward/SKILL.md +181 -0
- package/assets/skills/choosing-subagent-models/SKILL.md +244 -0
- package/assets/skills/closing-findings-while-warm/SKILL.md +104 -0
- package/assets/skills/configure-roles/SKILL.md +229 -0
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +205 -0
- package/assets/skills/coordinator-assessing-agent-completion/scan.py +117 -0
- package/assets/skills/coordinator-branch-split-on-block/SKILL.md +168 -0
- package/assets/skills/coordinator-context-monitoring/SKILL.md +674 -0
- package/assets/skills/coordinator-dispatching-work/SKILL.md +288 -0
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +522 -0
- package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +171 -0
- package/assets/skills/coordinator-maintaining-memory/SKILL.md +89 -0
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +192 -0
- package/assets/skills/coordinator-merging-code/SKILL.md +331 -0
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +442 -0
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +198 -0
- package/assets/skills/coordinator-post-restart-sweep/SKILL.md +135 -0
- package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +199 -0
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +654 -0
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +153 -0
- package/assets/skills/coordinator-single-responsibility-merge-gate/SKILL.md +14 -0
- package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +255 -0
- package/assets/skills/efficient-multi-agent-research/SKILL.md +177 -0
- package/assets/skills/fixing-documentation-errors/SKILL.md +258 -0
- package/assets/skills/implementer-maintaining-memory/SKILL.md +84 -0
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +125 -0
- package/assets/skills/implementer-receiving-dispatch/SKILL.md +127 -0
- package/assets/skills/implementer-receiving-review-feedback/SKILL.md +125 -0
- package/assets/skills/implementer-status-and-handoff/SKILL.md +150 -0
- package/assets/skills/implementer-tdd-and-quality/SKILL.md +99 -0
- package/assets/skills/memory-audit-recover/SKILL.md +153 -0
- package/assets/skills/memory-curate/SKILL.md +139 -0
- package/assets/skills/memory-maintenance/SKILL.md +87 -0
- package/assets/skills/memory-read-discipline/SKILL.md +164 -0
- package/assets/skills/memory-search-advanced/SKILL.md +217 -0
- package/assets/skills/memory-write-discipline/SKILL.md +135 -0
- package/assets/skills/orchestrate/SKILL.md +795 -0
- package/assets/skills/persistent-watcher-archetype/SKILL.md +225 -0
- package/assets/skills/prime-agent/SKILL.md +92 -0
- package/assets/skills/project-hotpath-gate/SKILL.md +255 -0
- package/assets/skills/project-philosophy/SKILL.md +418 -0
- package/assets/skills/project-philosophy/resources/philosophy-template.md +155 -0
- package/assets/skills/project-philosophy/resources/reliability-class-library.md +653 -0
- package/assets/skills/project-setup/SKILL.md +1007 -0
- package/assets/skills/project-setup/resources/implementation-agent.md +826 -0
- package/assets/skills/researcher-answering-queries/SKILL.md +100 -0
- package/assets/skills/researcher-investigating/SKILL.md +152 -0
- package/assets/skills/researcher-maintaining-memory/SKILL.md +176 -0
- package/assets/skills/state-migration/SKILL.md +54 -0
- package/assets/skills/thrum/SKILL.md +161 -0
- package/assets/skills/thrum/resources/ANTI_PATTERNS.md +132 -0
- package/assets/skills/thrum/resources/BOUNDARIES.md +56 -0
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +1066 -0
- package/assets/skills/thrum/resources/IDENTITY.md +49 -0
- package/assets/skills/thrum/resources/LISTENER_PATTERN.md +64 -0
- package/assets/skills/thrum/resources/MESSAGING.md +163 -0
- package/assets/skills/thrum/resources/TMUX_SESSIONS.md +118 -0
- package/assets/skills/thrum/resources/WORKTREES.md +117 -0
- package/assets/skills/thrum-memory/SKILL.md +138 -0
- package/assets/skills/trace-spec-to-plan/SKILL.md +104 -0
- package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +57 -0
- package/assets/skills/update-queue/SKILL.md +149 -0
- package/assets/skills/using-the-queue/SKILL.md +172 -0
- package/assets/skills/using-thrum-state/SKILL.md +104 -0
- package/assets/skills/verify-against-plan/SKILL.md +323 -0
- package/assets/skills/verify-against-source/SKILL.md +237 -0
- package/assets/skills/watcher-archetype/SKILL.md +84 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +66 -0
- package/dist/hooks/block-sync-cd.d.ts +9 -0
- package/dist/hooks/block-sync-cd.js +71 -0
- package/dist/hooks/block-sync-cd.test.d.ts +1 -0
- package/dist/hooks/block-sync-cd.test.js +61 -0
- package/dist/hooks/shell-env.d.ts +8 -0
- package/dist/hooks/shell-env.js +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +25 -0
- package/dist/installer.d.ts +2 -0
- package/dist/installer.js +86 -0
- package/package.json +49 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: persistent-watcher-archetype
|
|
3
|
+
description: "Use when running as a persistent watcher agent — a standing, judgment-capable Claude Code agent that watches other agents' tmux panes, auto-approves safe modals, warns on context, and escalates up a parent-reference tree. Distinct from watcher-archetype (the wake-run-exit scan/emit/report substrate) — this archetype stays alive."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Persistent Watcher Archetype
|
|
7
|
+
|
|
8
|
+
You are a **persistent watcher agent** — a real, standing Claude Code agent,
|
|
9
|
+
not a script and not a daemon process. You stay alive in a tmux session,
|
|
10
|
+
managed by `thrum monitor` (which handles crash-class recovery via a
|
|
11
|
+
grace-windowed liveness wrapper) and your own self-restart-at-ctx-threshold
|
|
12
|
+
discipline (which handles routine, cost-driven restarts).
|
|
13
|
+
|
|
14
|
+
## Your mission
|
|
15
|
+
|
|
16
|
+
Read `.thrum/agents/<you>/watch_params.json` at the top of every cycle. For
|
|
17
|
+
every agent in your `roster`:
|
|
18
|
+
|
|
19
|
+
1. **Keep it from stopping.** Capture its pane. If it's blocked on a
|
|
20
|
+
permission modal or appears stalled, judge whether to unblock it.
|
|
21
|
+
2. **Watch its context% and warn.** Read its ctx% from YOUR OWN tmux
|
|
22
|
+
capture of its status bar — **never from a figure it claims about
|
|
23
|
+
itself** (a message, a memory-footer line, anywhere). If it's getting
|
|
24
|
+
close to the end and hasn't restarted, warn it. This is the exact
|
|
25
|
+
mechanism — a watcher that trusts the target's self-report would catch
|
|
26
|
+
none of them.
|
|
27
|
+
3. **All of it is judgment.** Read pane, judge (unblock / warn-on-ctx /
|
|
28
|
+
escalate), act or escalate. Every cycle, every roster member.
|
|
29
|
+
4. **Catch declared-but-unexecuted intent.** If a roster member's last
|
|
30
|
+
assistant turn declares a concrete near-term action ("I'll check X
|
|
31
|
+
next", "let me run Y") and by TWO consecutive checks it's idle/at-rest
|
|
32
|
+
with no evidence the action happened, send it a plain reminder naming
|
|
33
|
+
the thing. This is a fourth judgment call, same loop, same data source
|
|
34
|
+
(your own pane capture) — see "Declared-intent reminders" below for the
|
|
35
|
+
full mechanics.
|
|
36
|
+
|
|
37
|
+
## The modal bright line (non-negotiable)
|
|
38
|
+
|
|
39
|
+
- Approve ONLY clearly-safe, recognized, non-`rm`/non-`--force` modals.
|
|
40
|
+
- Anything containing `rm`, `--force`, or anything unrecognized → refuse or
|
|
41
|
+
escalate to your `parent`. Never approve on the merits — this is a
|
|
42
|
+
pattern check, not a judgment call.
|
|
43
|
+
- Never approve blind: read the actual command text first.
|
|
44
|
+
- Verify by re-capture after acting — never trust exit status alone.
|
|
45
|
+
|
|
46
|
+
## Your own restart (auto-restart-at-ctx-threshold)
|
|
47
|
+
|
|
48
|
+
> 🔵 **CORE PRINCIPLE: you must NEVER self-restart alone.**
|
|
49
|
+
> Coordinator-handshake-first is mandatory, not a nicety — the
|
|
50
|
+
> coordinator has to watch you come back up. If something breaks mid-restart
|
|
51
|
+
> and you never return, the roster you cover goes UNWATCHED (dark) with
|
|
52
|
+
> nobody positioned to notice: the exact failure this archetype exists to
|
|
53
|
+
> prevent, inflicted on itself. This is distinct from duty #1/duty #4's
|
|
54
|
+
> handling of a WATCHED agent restarting (see "Declared-intent reminders"
|
|
55
|
+
> below) — this principle governs YOUR OWN restart specifically. Steps 1-4
|
|
56
|
+
> below implement this handshake; they are not optional even when the
|
|
57
|
+
> restart looks routine.
|
|
58
|
+
|
|
59
|
+
At `restart_ctx_pct` (default 50% — a cost optimization: past this point
|
|
60
|
+
every judgment cycle gets more expensive per call, so restarting is
|
|
61
|
+
cheaper, not a safety trigger) read off your OWN status bar each cycle
|
|
62
|
+
(never self-estimate):
|
|
63
|
+
|
|
64
|
+
1. **Message your `parent` first: "restarting now."**
|
|
65
|
+
2. Invoke your own `/thrum:restart`.
|
|
66
|
+
3. On resume, re-read `watch_params.json` — that's your entire resume state
|
|
67
|
+
(target/roster/parent/model), because your duty is stateless per cycle.
|
|
68
|
+
4. **Confirm you're back** to your parent; they stand down coverage.
|
|
69
|
+
|
|
70
|
+
While you're mid-restart, your `parent` covers you — using their own
|
|
71
|
+
tmux capture+send capability on your session, they can clear any modal
|
|
72
|
+
you block on during your own restart (you can't watch yourself while
|
|
73
|
+
restarting; that's exactly why the parent does). This pattern is proven
|
|
74
|
+
from live fleet operation, not a hypothetical.
|
|
75
|
+
|
|
76
|
+
> ⚠️ **Open verification item:** whether `thrum tmux restart` on your OWN
|
|
77
|
+
> session requires a capability grant, or is exempt as self-targeting, is
|
|
78
|
+
> unconfirmed. If your own restart command is ever refused for a capability
|
|
79
|
+
> reason you don't expect, escalate to your parent rather than working
|
|
80
|
+
> around it.
|
|
81
|
+
|
|
82
|
+
## Escalation
|
|
83
|
+
|
|
84
|
+
Your `parent` (from `watch_params.json`) is who you notify on anything you
|
|
85
|
+
don't understand, any irreversible/high-blast-radius action (agent
|
|
86
|
+
delete/retire/teardown, messages.db, the shared binary, daemon restart,
|
|
87
|
+
anything outside `/tmp`), and any stuck/wedged state you can't safely
|
|
88
|
+
resolve. The tree is REFERENCE DATA — who to notify — not an active
|
|
89
|
+
enforcement structure. If you're the root watcher, your parent is the
|
|
90
|
+
human operator directly.
|
|
91
|
+
|
|
92
|
+
**Additional rule — if your ROSTER CONTAINS A COORDINATOR, you escalate to
|
|
93
|
+
the HUMAN, not to that coordinator.** This is a *different condition* from
|
|
94
|
+
the root rule above: the root rule keys on **being the root**; this one keys
|
|
95
|
+
on **who is in your roster**. Both must hold. A non-root watcher whose
|
|
96
|
+
roster includes coordinator X, with `parent` set to X, satisfies the root
|
|
97
|
+
rule and still violates this one — set `parent` to the human instead.
|
|
98
|
+
|
|
99
|
+
**Why:** escalating to the agent you are watching is circular **exactly in
|
|
100
|
+
the case that matters**. A coordinator cannot perceive its own block — that
|
|
101
|
+
is the entire reason the watch exists — and a blocked coordinator cannot
|
|
102
|
+
receive its own escalation. The escalation would arrive at the one inbox
|
|
103
|
+
guaranteed not to be read.
|
|
104
|
+
|
|
105
|
+
## Declared-intent reminders (duty #4)
|
|
106
|
+
|
|
107
|
+
Each cycle, for each roster member, judge whether the last assistant turn
|
|
108
|
+
declares a concrete near-term action not yet performed ("I'll check X
|
|
109
|
+
next") vs. rhetorical/aspirational language — this is YOUR judgment call,
|
|
110
|
+
not a keyword/regex match — a keyword classifier here is the same
|
|
111
|
+
anti-pattern already rejected for the wake-loop itself.
|
|
112
|
+
|
|
113
|
+
- **Guardrail:** only act if BOTH hold — (a) you judged a
|
|
114
|
+
commitment was declared, AND (b) the member is idle/at-rest by YOUR OWN
|
|
115
|
+
pane capture (no new assistant text since last check, no live-subprocess
|
|
116
|
+
indicator in its status bar — the same "never trust the target's
|
|
117
|
+
self-report" discipline as duty #2's ctx% read). Never consume the
|
|
118
|
+
target's own `agent_status` field for this — self-reported status is a
|
|
119
|
+
known false-positive generator in this fleet.
|
|
120
|
+
- **Quiet-duration threshold:** require TWO consecutive idle
|
|
121
|
+
checks before nudging, not one — a single quiet interval routinely
|
|
122
|
+
reflects legitimate heads-down work, and at
|
|
123
|
+
`cadence_active` (2-5min) two checks still land in 4-10min, far inside
|
|
124
|
+
the sweep's ~30-60min effective cadence.
|
|
125
|
+
- **The reminder itself:** a plain `thrum send`, not a modal, not an
|
|
126
|
+
escalation. If the member sees it and does nothing, that's fine — their
|
|
127
|
+
call, no escalation.
|
|
128
|
+
- **Escalation (a BEHAVIORAL check, not a latency measurement, and not a
|
|
129
|
+
text-match):** at your next tick
|
|
130
|
+
after sending the reminder, the test is **"did the target DO SOMETHING,"
|
|
131
|
+
not "did the reminder's specific text appear."**
|
|
132
|
+
1. If the target's pane is DIFFERENT from your reminder-send capture
|
|
133
|
+
(new assistant text, a restart's session banner/resume prompt,
|
|
134
|
+
anything) → the reminder SUCCEEDED. Done — do not separately verify
|
|
135
|
+
the nudge marker arrived.
|
|
136
|
+
2. ONLY if the pane is IDENTICAL to your reminder-send capture **and**
|
|
137
|
+
the nudge marker is also absent → escalate to your `parent`.
|
|
138
|
+
Check for the GENERIC arrival template when checking for the marker
|
|
139
|
+
(step 2 only) — `thrum send` injects `tmux.FormatNudge`'s
|
|
140
|
+
`"New message from @<you> -- run \`thrum inbox --unread\`..."` line,
|
|
141
|
+
never the message body. Look for your own agent name inside that generic
|
|
142
|
+
line. `FormatNudge` has two branches — with a trailing `(Sent:...)`
|
|
143
|
+
suffix and without (when no send timestamp was available) — and the
|
|
144
|
+
sender name is rendered through `paneref.Agent()`, which only strips
|
|
145
|
+
leading whitespace/`@` characters (it does not add an `@` prefix), so
|
|
146
|
+
don't anchor on a raw `@name` shape either. The reliable anchor is the
|
|
147
|
+
stable substring `` -- run `thrum inbox --unread` to read `` present in
|
|
148
|
+
BOTH branches, unaffected by timestamp or sender formatting; treat your
|
|
149
|
+
own agent name appearing inside that line as a secondary check only.
|
|
150
|
+
|
|
151
|
+
**Why this design needs no exclusion list:** a routine self-restart makes the pane DIFFERENT (new
|
|
152
|
+
session banner/resume prompt/fresh text) — it can never read as "pane
|
|
153
|
+
identical," so it can never trigger step 2 and can never false-escalate. An
|
|
154
|
+
open interactive dialog or a deferred/re-queued nudge either lands before
|
|
155
|
+
your next tick (pane changes → success, no escalation) or costs one extra
|
|
156
|
+
cycle of latency at worst — not a false escalation, since escalation
|
|
157
|
+
requires BOTH pane-identical AND marker-absent simultaneously. A dead tmux
|
|
158
|
+
session is a pane change too (the session itself is gone) and is Layer 2's
|
|
159
|
+
crash-class job regardless, not this lens's. **ONE tick suffices** — a
|
|
160
|
+
second confirmation cycle would only add latency, not confidence, since
|
|
161
|
+
"pane changed" is already a strong, unambiguous positive signal.
|
|
162
|
+
|
|
163
|
+
## Driving your cycle: `thrum monitor`, never a session-scoped background task
|
|
164
|
+
|
|
165
|
+
Your capture loop must survive your own restarts and crashes, so it cannot
|
|
166
|
+
live inside your own Claude Code session — a harness-level background task
|
|
167
|
+
(e.g. a backgrounded shell command, or an in-session task-monitoring tool)
|
|
168
|
+
dies the moment your session ends, and nothing signals that it happened.
|
|
169
|
+
The roster then goes unwatched with no one positioned to notice — the exact
|
|
170
|
+
failure this archetype exists to prevent, inflicted on itself by the wrong
|
|
171
|
+
choice of plumbing.
|
|
172
|
+
|
|
173
|
+
Register the loop as a `thrum monitor` instead — a daemon-scheduled job,
|
|
174
|
+
independent of your session, that keeps running across your restarts:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
thrum monitor start --name <descriptive-name> \
|
|
178
|
+
--match "<a regex matching your script's one-line ready signal>" \
|
|
179
|
+
--to @<you> \
|
|
180
|
+
-- <path-to-a-script-that-loops-and-emits-that-signal>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The script's job is mechanical only: loop, capture each roster member's
|
|
184
|
+
pane to a file, and emit one matchable line per cycle. It does not judge or
|
|
185
|
+
act — `thrum monitor` matches that line and delivers it to you as a thrum
|
|
186
|
+
message, which is your wake signal. Judgment (duties 1-4 above) happens in
|
|
187
|
+
your own turn when you read that message, not in the script.
|
|
188
|
+
|
|
189
|
+
At the start of every cycle, confirm the monitor is still running
|
|
190
|
+
(`thrum monitor list`) — if it's missing or dead, re-`thrum monitor start`
|
|
191
|
+
it rather than assuming someone else will notice.
|
|
192
|
+
|
|
193
|
+
## Relationship to the deterministic context-monitoring sweep
|
|
194
|
+
|
|
195
|
+
You are NOT a replacement for `scripts/error-and-context-agent-sweep.sh`
|
|
196
|
+
(the `context-monitoring` `thrum monitor` job, `@every 30m`,
|
|
197
|
+
self-gated to ~30-60min effective) — the two COEXIST. The sweep stays the fleet-wide, always-on,
|
|
198
|
+
zero-incremental-cost tripwire covering every agent, including ones with no
|
|
199
|
+
assigned watcher. You are the tighter-cadence, judgment-capable layer for
|
|
200
|
+
your OWN roster specifically — duty #4 above (declared-intent) is exactly
|
|
201
|
+
the kind of check the sweep cannot do reliably (it needs semantic,
|
|
202
|
+
cross-cycle transcript judgment; the sweep's closest analog, its L9
|
|
203
|
+
`waiting_on_coord` lens, is regex-based). Do not treat a quiet sweep as
|
|
204
|
+
clearance to skip your own cycle, and do not treat your own coverage of a
|
|
205
|
+
roster member as a reason to suppress the sweep's coverage of that same
|
|
206
|
+
agent — they run independently.
|
|
207
|
+
|
|
208
|
+
## Cadence
|
|
209
|
+
|
|
210
|
+
Capture each roster member at `cadence_active` while they look active,
|
|
211
|
+
`cadence_idle` while idle. `rules` in `watch_params.json` may widen your
|
|
212
|
+
cadence for specific conditions (e.g. a weekly usage cap approaching) —
|
|
213
|
+
this is the ONLY sanctioned form of self-adjustment: a deterministic rule
|
|
214
|
+
table installed through the params file, never free-running judgment about
|
|
215
|
+
your own schedule. Any widening carries a blind-spot obligation: declare it
|
|
216
|
+
in your next report — quiet during a widened window is not evidence
|
|
217
|
+
nothing happened.
|
|
218
|
+
|
|
219
|
+
`override` is a TEMPORARY widening (not a permanent rule): if
|
|
220
|
+
`watch_params.json`'s `override` field is set, check its `expires`
|
|
221
|
+
timestamp EVERY cycle. Once `expires` has passed, treat `override` as gone
|
|
222
|
+
— revert to `cadence_active`/`cadence_idle` automatically, without waiting
|
|
223
|
+
for the coordinator to clear the field. This is a behavioral requirement
|
|
224
|
+
you enforce by reading the file each cycle, same as everything else in this
|
|
225
|
+
schema — no separate daemon-side mechanism does this for you.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prime-agent
|
|
3
|
+
description: "Use on scheduled-agent wake or lean prime restart - replaces the full thrum prime briefing with a lean two-step warmup tuned for short-lived scheduled-agent invocations. Step 1 reads the inbox literally (NOT a discipline assumption); step 2 surfaces newly-shipped skills since the agent last ran."
|
|
4
|
+
allowed-tools: "Bash(thrum:*),Bash(ls:*),Bash(diff:*),Bash(cat:*),Read"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Thrum: Prime Scheduled Agent (Lean)
|
|
8
|
+
|
|
9
|
+
This skill is the wake-time variant of `thrum prime` for scheduled agents. Full
|
|
10
|
+
`thrum prime` is too heavy when the agent is
|
|
11
|
+
going to run for a few minutes and then exit — but the two pieces of state that
|
|
12
|
+
matter MUST run literally, not as a discipline.
|
|
13
|
+
|
|
14
|
+
## Step 1: Read the inbox
|
|
15
|
+
|
|
16
|
+
**Run this exact command. Do not skip it. Do not defer it.**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
thrum inbox --unread
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This is a hard-required step (not a discipline assumption). The `agent.wake`
|
|
23
|
+
message that triggered this session lives in the inbox; the wake-primer
|
|
24
|
+
payload + any operator escalations land there too. Reading the inbox is how the
|
|
25
|
+
agent learns WHY it was woken and WHAT the primer said.
|
|
26
|
+
|
|
27
|
+
If `thrum inbox --unread` returns no messages, the agent was likely woken
|
|
28
|
+
without a wake message (manual restart, daemon error). In that case, continue to
|
|
29
|
+
Step 2 — the skill-library check is still useful — but flag the missing wake
|
|
30
|
+
message in the status report at end of session.
|
|
31
|
+
|
|
32
|
+
## Step 2: Skill-library check
|
|
33
|
+
|
|
34
|
+
**Run this exact diff against the last-seen skill set, then write the current
|
|
35
|
+
set back so the NEXT wake compares against THIS wake.**
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ls .claude/skills/ > /tmp/current_skills.txt
|
|
39
|
+
AGENT_NAME=$(thrum whoami --field agent_id)
|
|
40
|
+
LAST_SEEN=".thrum/agents/${AGENT_NAME}/last_seen_skills.txt"
|
|
41
|
+
if [ -f "${LAST_SEEN}" ]; then
|
|
42
|
+
diff "${LAST_SEEN}" /tmp/current_skills.txt || true
|
|
43
|
+
else
|
|
44
|
+
echo "First wake for this agent — full skill library:"
|
|
45
|
+
cat /tmp/current_skills.txt
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Update last_seen_skills.txt — this wake's skill set becomes the
|
|
49
|
+
# next wake's baseline. Do this AT BOOT (not at end-of-session) so
|
|
50
|
+
# the next wake's diff surfaces only what's truly NEW since this
|
|
51
|
+
# boot, not since end-of-session (which might be drift-free if no
|
|
52
|
+
# new skills shipped during the session).
|
|
53
|
+
#
|
|
54
|
+
# Atomic write via temp-file + mv. A crash mid-cp would leave a partial
|
|
55
|
+
# last_seen_skills.txt; the next wake's diff would then emit
|
|
56
|
+
# "all skills new" (false-positive drift). Temp+mv eliminates the
|
|
57
|
+
# partial-write window.
|
|
58
|
+
mkdir -p ".thrum/agents/${AGENT_NAME}"
|
|
59
|
+
cp /tmp/current_skills.txt "${LAST_SEEN}.tmp" && \
|
|
60
|
+
mv "${LAST_SEEN}.tmp" "${LAST_SEEN}"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When a new skill ships to `.claude/skills/`, the next wake's lean-prime
|
|
64
|
+
surfaces it. Writing `last_seen_skills.txt` at the END of Step 2 (after the diff) means each
|
|
65
|
+
wake updates its own baseline — the diff at wake N+1 reflects what's NEW since
|
|
66
|
+
wake N's boot, regardless of mid-session skill drift.
|
|
67
|
+
|
|
68
|
+
Do not skip this step even if Step 1's inbox was empty. Skill drift between
|
|
69
|
+
wakes is a real failure mode — agents can miss a critical new skill if the
|
|
70
|
+
lean-prime suppresses the diff during quiet sessions.
|
|
71
|
+
|
|
72
|
+
## When NOT to use this skill
|
|
73
|
+
|
|
74
|
+
This is the SCHEDULED-AGENT wake variant. If you're a coordinator or a
|
|
75
|
+
human-operator-spawned agent, use `thrum prime` instead — the full briefing
|
|
76
|
+
covers team state, project state, work context, and recent commits that the lean
|
|
77
|
+
variant skips. The lean variant exists because scheduled agents run for a few
|
|
78
|
+
minutes and exit; the full briefing's setup cost isn't recouped over the session
|
|
79
|
+
lifetime.
|
|
80
|
+
|
|
81
|
+
If the session will exceed ~30 minutes, or coordination with other agents is
|
|
82
|
+
needed, defer to `thrum prime`.
|
|
83
|
+
|
|
84
|
+
## After Step 2
|
|
85
|
+
|
|
86
|
+
Once both steps complete, proceed with the work the wake-primer asked for.
|
|
87
|
+
|
|
88
|
+
The `/thrum:update-agent-state` and `/thrum:recover-agent-state` skills that
|
|
89
|
+
used to be invoked here (recording end-of-session continuity into
|
|
90
|
+
`.thrum/agents/<agent_id>/state.md` via `thrum agent state update` /
|
|
91
|
+
`recover`) were retired — superseded by `thrum state` (personal_state kind). The
|
|
92
|
+
`last_seen_skills.txt` baseline write in Step 2 above is unaffected.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-hotpath-gate
|
|
3
|
+
description: "Use when a project needs its hot-path gate configuration established or updated - the canonical config at .thrum/hotpath-gate.json defining trigger directories, per-concept detection patterns, and incident prose for the coordinator-hotpath-merge-gate skill. Detects every language present in the repo and drives detection off the shared reliability-class library, so the generated config is real for Go, Python, JS/TS, Rust, or any mix. First invocation generates from project inspection; subsequent invocations reconcile against current project state and propose diffs."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project Hot-Path Gate
|
|
7
|
+
|
|
8
|
+
## Canonical path
|
|
9
|
+
|
|
10
|
+
`.thrum/hotpath-gate.json` — the single authoritative location.
|
|
11
|
+
`coordinator-hotpath-merge-gate` reads from this path only.
|
|
12
|
+
|
|
13
|
+
## Run modes and dispatch order
|
|
14
|
+
|
|
15
|
+
On every invocation, dispatch in this order. Earlier branches short-circuit
|
|
16
|
+
later ones.
|
|
17
|
+
|
|
18
|
+
1. **First-run** (config absent) — generate a new `.thrum/hotpath-gate.json`
|
|
19
|
+
from project inspection. Optionally accept seeded prose context (incident
|
|
20
|
+
descriptions, fix patterns) from the invoker.
|
|
21
|
+
2. **Re-run-unchanged** (config present, matches current project state) — report
|
|
22
|
+
"hotpath-gate.json is current; no changes needed" and exit without writing.
|
|
23
|
+
3. **Re-run-evolved** (config present, project has drifted) — compute a
|
|
24
|
+
sectional diff, present for approval, write only on confirmation.
|
|
25
|
+
|
|
26
|
+
Read the shared partial at the absolute path:
|
|
27
|
+
`claude-plugin/commands/_generator-idempotency-protocol.md`
|
|
28
|
+
|
|
29
|
+
> **Note: human-in-the-loop required.** This skill uses interactive prompts for
|
|
30
|
+
> first-run incident-prose seeding and re-run-evolved diff approval. It cannot
|
|
31
|
+
> run unattended in CI.
|
|
32
|
+
|
|
33
|
+
## First-run mode
|
|
34
|
+
|
|
35
|
+
Triggered when `.thrum/hotpath-gate.json` does not exist.
|
|
36
|
+
|
|
37
|
+
### Step 1: Census the repo's languages
|
|
38
|
+
|
|
39
|
+
Detect every language present — a repo can carry more than one:
|
|
40
|
+
|
|
41
|
+
- Manifest files at the repo root or in each workspace: `go.mod` (Go),
|
|
42
|
+
`package.json` (JS/TS), `pyproject.toml`/`requirements.txt`/`setup.py`
|
|
43
|
+
(Python), `Cargo.toml` (Rust).
|
|
44
|
+
- `git ls-files` extension histogram, as a cross-check and as the sole signal
|
|
45
|
+
where no manifest exists in a subtree.
|
|
46
|
+
- A language is detected when it has a manifest, or its tracked-file share
|
|
47
|
+
clears 5%.
|
|
48
|
+
|
|
49
|
+
Record every detected language as `languages_detected`. A repo clearing the
|
|
50
|
+
threshold for more than one language is polyglot: every detected language's
|
|
51
|
+
probe pack runs in Step 3, not just the largest one.
|
|
52
|
+
|
|
53
|
+
### Step 2: Detect trigger directories
|
|
54
|
+
|
|
55
|
+
Scan for hot-path code using each detected language's own route/handler/RPC
|
|
56
|
+
idiom:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Go
|
|
60
|
+
grep -rl 'func Handle\|func.*Handler\|http\.Handle\|rpc\.' --include='*.go'
|
|
61
|
+
# Python
|
|
62
|
+
grep -rl '@app\.\(route\|get\|post\|put\|delete\|patch\)\|@router\.' --include='*.py'
|
|
63
|
+
# JS/TS
|
|
64
|
+
grep -rlE "app\.(get|post|put|delete|patch)\(|router\.(get|post|put|delete|patch)\(" --include='*.ts' --include='*.js'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Also scan for projection/writer, storage/DB, sync, and listener/boot
|
|
68
|
+
directories, using each language's own idiom for those shapes (a database
|
|
69
|
+
open call, a background sync loop, a process-boot entrypoint). Collapse
|
|
70
|
+
subdirectories under their parent when the parent already matches. Record the
|
|
71
|
+
deduplicated list as `trigger_directories`.
|
|
72
|
+
|
|
73
|
+
### Step 3: Walk the reliability class library
|
|
74
|
+
|
|
75
|
+
Read `../project-philosophy/resources/reliability-class-library.md`, shared
|
|
76
|
+
with `project-philosophy`. Walk every class tagged `Consumers: ... hotpath` —
|
|
77
|
+
each names the lens it feeds and, where relevant, a hot-path facet within that
|
|
78
|
+
lens.
|
|
79
|
+
|
|
80
|
+
For each such class and each language recorded in `languages_detected`, run
|
|
81
|
+
the probe the library gives that class for that language, scoped to that
|
|
82
|
+
language's own files within `trigger_directories` from Step 2 — a language
|
|
83
|
+
with no hot-path directories of its own contributes no findings, even if it
|
|
84
|
+
clears the repo-wide detection threshold. Where the library marks a language
|
|
85
|
+
"none" or "open gap" for a class — this includes Go for some classes, such
|
|
86
|
+
as the security concepts that don't apply to a perimeter-protected daemon —
|
|
87
|
+
that language contributes no finding for that class and the walk moves to
|
|
88
|
+
the next one.
|
|
89
|
+
|
|
90
|
+
A class whose `**Consumers:**` line names hotpath with no facet parenthetical
|
|
91
|
+
IS its lens — e.g. Class 8 is the `silent_fail_open` lens. Record its matches
|
|
92
|
+
into that lens's primary pattern field (the array of distinct
|
|
93
|
+
patterns/identifiers actually found in the repo). A class with zero matches
|
|
94
|
+
for a given language is not an error — record it as walked-and-not-found, and
|
|
95
|
+
continue.
|
|
96
|
+
|
|
97
|
+
A class whose `**Consumers:**` line names a hot-path facet in parentheses
|
|
98
|
+
enriches the named lens's `context` field with the adapted finding. Where
|
|
99
|
+
that class's own probe is the only detection the lens has for its primary
|
|
100
|
+
pattern field, also record matches there (Class 1 into `subprocess_hot_path`,
|
|
101
|
+
Class 5 into `dispatch_blocking` and `peer_dial_circuit_breaker`). The one
|
|
102
|
+
exception is Class 2: `async_io_decoupling` carries its own independent,
|
|
103
|
+
incident-derived detection for its primary fields, so Class 2 enriches
|
|
104
|
+
`context` only and never touches a primary field.
|
|
105
|
+
|
|
106
|
+
A lens's secondary refinement fields (pre-flight guard patterns, native
|
|
107
|
+
alternatives, and similar) populate only where a class gives a probe for
|
|
108
|
+
them; a field with no probe for the detected language is left absent rather
|
|
109
|
+
than guessed at.
|
|
110
|
+
|
|
111
|
+
`async_io_decoupling`'s own primary fields (goroutine/boot-stage/cancellation
|
|
112
|
+
patterns and dropped-context params) come from a Go-only detection pass
|
|
113
|
+
independent of the class library, scoped to `trigger_directories`:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
grep -rn 'go func\|go bootstage\.Run' --include='*.go'
|
|
117
|
+
grep -rn 'bootstage\.Run\|OnReady\|SetOnReady\|DependsOn\|Await(' --include='*.go'
|
|
118
|
+
grep -rn 'CompactAll\|MergeGroup\|filepath\.Walk\|os\.ReadDir\|io\.Copy' --include='*.go'
|
|
119
|
+
grep -rn 'ctx\.Done()\|ctx\.Err()\|r\.Beat(\|context\.WithoutCancel' --include='*.go'
|
|
120
|
+
grep -rn '_ context\.Context' --include='*.go'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Record these as `io_work_patterns`, `boot_and_coupling_patterns`,
|
|
124
|
+
`async_discipline_patterns`, and `dropped_ctx_pattern` respectively. No
|
|
125
|
+
equivalent probe exists yet for other languages; the fields are absent there
|
|
126
|
+
rather than guessed at.
|
|
127
|
+
|
|
128
|
+
### Step 4: Accept seeded prose context
|
|
129
|
+
|
|
130
|
+
Use `AskUserQuestion` for per-lens prose seeding. Prefer sequential
|
|
131
|
+
category→item questions (one prompt per lens, ≤4 options each) over packing
|
|
132
|
+
every lens into a single prompt.
|
|
133
|
+
|
|
134
|
+
Prompt the invoker for incident descriptions, fix patterns, and
|
|
135
|
+
project-specific nuances for each lens. These become the `context` prose
|
|
136
|
+
fields in the config.
|
|
137
|
+
|
|
138
|
+
If the invoker has no incident prose to seed (first-time setup on a new
|
|
139
|
+
project), leave the `context` fields as empty strings with a
|
|
140
|
+
`<!-- TODO: seed with incident history -->` comment. The gate skill still
|
|
141
|
+
functions without prose context — it just provides less guidance to the
|
|
142
|
+
reviewer.
|
|
143
|
+
|
|
144
|
+
### Step 5: Generate `.thrum/hotpath-gate.json`
|
|
145
|
+
|
|
146
|
+
Assemble all discovered values into the JSON structure and write to
|
|
147
|
+
`.thrum/hotpath-gate.json`. Ensure the file is not gitignored (add a `!`
|
|
148
|
+
exception in `.gitignore` if needed, following the `.thrum/philosophy.md`
|
|
149
|
+
pattern).
|
|
150
|
+
|
|
151
|
+
Only emit `embed_aware_skip` for a language with a compiled-in-bundle concept
|
|
152
|
+
and a derivable reachability command (Go's `go:embed` is the current
|
|
153
|
+
example). Omit the field for a language with no such concept rather than
|
|
154
|
+
writing a placeholder.
|
|
155
|
+
|
|
156
|
+
If Step 3 found zero matching lenses across every detected language, do not
|
|
157
|
+
write an empty-but-valid config — see the scaffold/refuse behavior in
|
|
158
|
+
`## Zero-match handling` below.
|
|
159
|
+
|
|
160
|
+
Announce the path and summarize what was discovered per language, including
|
|
161
|
+
which reliability classes were found vs. walked-and-not-found.
|
|
162
|
+
|
|
163
|
+
## Zero-match handling
|
|
164
|
+
|
|
165
|
+
The generator never writes a config with an empty `lenses` object. When Step
|
|
166
|
+
3 finds no matching lens across every detected language:
|
|
167
|
+
|
|
168
|
+
- **Default:** write a scaffold marked at the top level with
|
|
169
|
+
`_UNAUTHORED_SCAFFOLD: true`. Populate `trigger_directories` from a
|
|
170
|
+
language-neutral serving heuristic — route decorators, files matching
|
|
171
|
+
`*proxy*`/`*server*`/`*api*`, a `Dockerfile` with `EXPOSE`/`CMD`, and
|
|
172
|
+
detected entrypoints. Add a commented TODO stub per reliability-library
|
|
173
|
+
concept, keyed to the files the heuristic found.
|
|
174
|
+
- **Alternative, offered interactively:** refuse to write the file, with a
|
|
175
|
+
message naming which languages were detected and why no lens matched.
|
|
176
|
+
|
|
177
|
+
No downstream consumer relies on a zero-lens config meaning "clean" — a
|
|
178
|
+
generator run either produces real findings, an explicit scaffold, or a
|
|
179
|
+
refusal.
|
|
180
|
+
|
|
181
|
+
## Re-run-unchanged mode
|
|
182
|
+
|
|
183
|
+
Triggered when `.thrum/hotpath-gate.json` exists.
|
|
184
|
+
|
|
185
|
+
### Step 1: Read the existing config
|
|
186
|
+
|
|
187
|
+
Read `.thrum/hotpath-gate.json` in full. Parse all lens sections.
|
|
188
|
+
|
|
189
|
+
### Step 2: Sanity-check against current project state
|
|
190
|
+
|
|
191
|
+
Re-run the detection steps from first-run mode, but compare rather than write:
|
|
192
|
+
|
|
193
|
+
- Are the `languages_detected` still correct? Any new language crossing the
|
|
194
|
+
threshold?
|
|
195
|
+
- Are the `trigger_directories` still correct? Any new directories with
|
|
196
|
+
hot-path code?
|
|
197
|
+
- Does each lens's primary pattern field still match what Step 3 finds? Any
|
|
198
|
+
new patterns per language?
|
|
199
|
+
- Are the `reference_patterns` still accurate? Any new established patterns?
|
|
200
|
+
|
|
201
|
+
Each check is a boolean "matches" vs. "differs".
|
|
202
|
+
|
|
203
|
+
### Step 3: If nothing differs — no-op exit
|
|
204
|
+
|
|
205
|
+
Print: `.thrum/hotpath-gate.json` is current; no changes needed.
|
|
206
|
+
|
|
207
|
+
### Step 4: If anything differs — hand off to evolved mode
|
|
208
|
+
|
|
209
|
+
## Re-run-evolved mode
|
|
210
|
+
|
|
211
|
+
Triggered when the config exists AND the unchanged-mode sanity check flagged
|
|
212
|
+
drift.
|
|
213
|
+
|
|
214
|
+
> **Never silently overwrite.** The config may contain hand-seeded incident
|
|
215
|
+
> prose that must survive every re-run. A write without explicit confirmation is
|
|
216
|
+
> a bug.
|
|
217
|
+
|
|
218
|
+
### Step 1: Detect drift
|
|
219
|
+
|
|
220
|
+
Four drift categories:
|
|
221
|
+
|
|
222
|
+
- **New or removed languages** — a language crossing or falling below the
|
|
223
|
+
detection threshold
|
|
224
|
+
- **New trigger directories** — new directories with hot-path code not in the
|
|
225
|
+
config
|
|
226
|
+
- **New patterns** — new matches for an existing lens, per language
|
|
227
|
+
- **New reference patterns** — new established working patterns or tripwire
|
|
228
|
+
tests
|
|
229
|
+
|
|
230
|
+
### Step 2: Compute a proposed diff
|
|
231
|
+
|
|
232
|
+
Present drift as a readable sectional diff. Each item should have:
|
|
233
|
+
|
|
234
|
+
- The detected change in plain language
|
|
235
|
+
- A proposed edit (addition / removal / update to a config field)
|
|
236
|
+
- A one-line rationale
|
|
237
|
+
|
|
238
|
+
### Step 3: Present for approval
|
|
239
|
+
|
|
240
|
+
Present the diff. On approval, apply edits in-place, preserving surrounding
|
|
241
|
+
content. Append a provenance comment:
|
|
242
|
+
`// updated YYYY-MM-DD via project-hotpath-gate re-run-evolved`.
|
|
243
|
+
|
|
244
|
+
On decline — do NOT write. Record the skipped proposal for future runs.
|
|
245
|
+
|
|
246
|
+
## Reference
|
|
247
|
+
|
|
248
|
+
- `.thrum/hotpath-gate.json` — the config file this skill generates
|
|
249
|
+
- `../project-philosophy/resources/reliability-class-library.md` — the shared
|
|
250
|
+
reliability class library; every `Consumers: ... hotpath` class and its
|
|
251
|
+
per-language probes drive Step 3
|
|
252
|
+
- `coordinator-hotpath-merge-gate` — the gate skill that reads this config
|
|
253
|
+
- `project-philosophy` — the sibling skill that generates `.thrum/philosophy.md`
|
|
254
|
+
(this skill follows the same generate-first / reconcile-on-reinvoke pattern;
|
|
255
|
+
run both at the same onboarding moment)
|