opencode-thrum-pro 0.1.3 → 0.1.5
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/skills/agent-messaging-protocol/SKILL.md +35 -0
- package/assets/skills/brainstorm-steward/SKILL.md +3 -2
- package/assets/skills/brainstormer-dashboard-needs/SKILL.md +62 -0
- package/assets/skills/brainstormer-staying-current/SKILL.md +116 -0
- package/assets/skills/conversation-only-context/SKILL.md +16 -8
- package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +138 -0
- package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +97 -0
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +24 -3
- package/assets/skills/coordinator-dashboard-human-loop/SKILL.md +72 -0
- package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +396 -0
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +7 -3
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +7 -3
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +38 -52
- package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -4
- package/assets/skills/persistent-watcher-archetype/SKILL.md +49 -15
- package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +8 -4
- package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +1 -1
- package/assets/skills/state-migration/SKILL.md +1 -1
- package/assets/skills/using-the-dashboard/SKILL.md +148 -0
- package/assets/skills/using-thrum-state/SKILL.md +1 -1
- package/package.json +1 -1
|
@@ -21,7 +21,11 @@ every agent in your `roster`:
|
|
|
21
21
|
never capture ad-hoc or key on fixed prompt phrases. A failed or empty
|
|
22
22
|
capture is NOT a dead or idle pane: fall back and re-read; never skip a
|
|
23
23
|
roster member because its capture failed. If it's blocked on a permission
|
|
24
|
-
modal or appears stalled, judge whether to unblock it.
|
|
24
|
+
modal or appears stalled, judge whether to unblock it. A member that is
|
|
25
|
+
*persistently* unwatchable (fails every capture path, isolated,
|
|
26
|
+
unreachable) is escalated to your `parent` every cycle until a coordinator
|
|
27
|
+
or human removes it from your roster — never dropped, never silently
|
|
28
|
+
given up on (see Escalation).
|
|
25
29
|
2. **Watch its context% and warn.** Read its ctx% from YOUR OWN tmux
|
|
26
30
|
capture of its status bar — **never from a figure it claims about
|
|
27
31
|
itself** (a message, a memory-footer line, anywhere). If it's getting
|
|
@@ -50,8 +54,10 @@ every agent in your `roster`:
|
|
|
50
54
|
- Verify by re-capture after acting — never trust exit status alone.
|
|
51
55
|
- To unblock a REMOTE roster member's modal, use `thrum tmux send <agent> " "`
|
|
52
56
|
(proxies by agent name to the owning peer daemon and appends Enter, so a
|
|
53
|
-
space+Enter confirms the default-focused option)
|
|
54
|
-
|
|
57
|
+
space+Enter confirms the default-focused option); for a specific selection
|
|
58
|
+
use `thrum tmux send <agent> --keys <K1,K2,...>`. `thrum tmux send` is
|
|
59
|
+
peer-routed, so it works identically local or remote (fleet-wide) — this is
|
|
60
|
+
the replacement for the retired local-only tmux-key primitive. This grants only
|
|
55
61
|
the leftmost/default option; the bright line above still applies. From the
|
|
56
62
|
CLI/peer-router path this send is QUEUED behind the conservative
|
|
57
63
|
monitor-silence wait, not instant — don't re-send while waiting it out.
|
|
@@ -115,6 +121,30 @@ is the entire reason the watch exists — and a blocked coordinator cannot
|
|
|
115
121
|
receive its own escalation. The escalation would arrive at the one inbox
|
|
116
122
|
guaranteed not to be read.
|
|
117
123
|
|
|
124
|
+
### An unwatchable roster member: escalate every cycle until it is removed
|
|
125
|
+
|
|
126
|
+
A roster member you **cannot watch at all** — capture fails by every path
|
|
127
|
+
(local proxy *and* SSH fallback), the agent is fully isolated, or its owning
|
|
128
|
+
daemon is unreachable so you cannot even message it — is NOT a member you may
|
|
129
|
+
drop, mark dead, or fall silent on. It stays in your roster and it stays your
|
|
130
|
+
duty.
|
|
131
|
+
|
|
132
|
+
- **Escalate it to your `parent` every cycle it remains unwatchable —
|
|
133
|
+
repeatedly, not once.** A single "fully isolated" notice that you then stop
|
|
134
|
+
repeating is exactly the silent-give-up this archetype exists to prevent:
|
|
135
|
+
the roster member goes dark with nobody positioned to notice. Re-send the
|
|
136
|
+
escalation on each tick it is still unwatchable. (This is a standing
|
|
137
|
+
per-cycle duty, unlike the one-shot declared-intent nudge.)
|
|
138
|
+
- **You never remove it from your own roster.** Roster membership is
|
|
139
|
+
coordinator-owned config in `watch_params.json`, which you only ever read.
|
|
140
|
+
Only a **coordinator or the human** may remove an agent from your roster —
|
|
141
|
+
the signal that it no longer needs watching. Until that removal lands in
|
|
142
|
+
`watch_params.json`, you keep escalating.
|
|
143
|
+
- **Do not borrow the empty-roster shutdown rule here.** "Shut yourself down"
|
|
144
|
+
applies only when your *whole* roster is empty/missing. One unwatchable
|
|
145
|
+
*member* out of several is not that case: keep watching the rest, keep
|
|
146
|
+
escalating the unwatchable one.
|
|
147
|
+
|
|
118
148
|
## Declared-intent reminders (duty #4)
|
|
119
149
|
|
|
120
150
|
Each cycle, for each roster member, judge whether the last assistant turn
|
|
@@ -197,18 +227,22 @@ stability check, writes one report file, and emits one matchable summary
|
|
|
197
227
|
line. It is mechanical only — no auto-escalate, no auto-key — exactly the
|
|
198
228
|
scope this section already describes; judgment stays in your own turn.
|
|
199
229
|
|
|
200
|
-
**Setup (one-time per watcher instance):**
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
230
|
+
**Setup (one-time per watcher instance):** the script cannot locate itself
|
|
231
|
+
inside the plugin tree at run time — `thrum monitor`'s scheduled-process
|
|
232
|
+
environment has none of the plugin-root env vars a hook invocation gets
|
|
233
|
+
(`CLAUDE_PLUGIN_ROOT`, codex's `PLUGIN_ROOT`), and neither is reliably set
|
|
234
|
+
in an ordinary interactive turn either, so a shell `cp` against either
|
|
235
|
+
variable is not runtime-safe in any host. Copy both scripts out of the
|
|
236
|
+
skill's `resources/` into your own agent directory using your Read/Write
|
|
237
|
+
tools instead — the same skill-relative resolution every other `resources/`
|
|
238
|
+
reference in this plugin already uses, with no env var and no
|
|
239
|
+
runtime-versioned path involved:
|
|
240
|
+
|
|
241
|
+
1. Read `resources/thrum-watch-pane-capture.sh` (relative to this skill) and
|
|
242
|
+
Write its exact content to `.thrum/agents/<you>/thrum-watch-pane-capture.sh`.
|
|
243
|
+
2. Read `resources/thrum-capture-fallback.sh` and Write its exact content to
|
|
244
|
+
`.thrum/agents/<you>/thrum-capture-fallback.sh`.
|
|
245
|
+
3. `chmod +x .thrum/agents/<you>/thrum-watch-pane-capture.sh .thrum/agents/<you>/thrum-capture-fallback.sh`
|
|
212
246
|
|
|
213
247
|
Then register the monitor against that absolute copy path —
|
|
214
248
|
**`--notify-on-success` is MANDATORY, not optional decoration**: a
|
|
@@ -63,12 +63,16 @@ ssh_destination() {
|
|
|
63
63
|
|
|
64
64
|
run_local() {
|
|
65
65
|
case "$MODE" in
|
|
66
|
-
# "${EXTRA_ARGS[
|
|
67
|
-
# subscripted with [
|
|
66
|
+
# "${EXTRA_ARGS[*]:-}" not "${EXTRA_ARGS[*]}": a DECLARED-EMPTY array
|
|
67
|
+
# subscripted with [*] is a fatal unbound-variable under `set -u` on
|
|
68
68
|
# bash <4.4 (a real bug, not a style choice -- `key` mode with zero
|
|
69
69
|
# key args crashes on this fleet's /bin/bash 3.2.57 without the ":-").
|
|
70
|
+
# key mode delivers named tmux keys via `thrum tmux send --keys` (the
|
|
71
|
+
# peer-routed replacement for the retired local-only tmux-key primitive);
|
|
72
|
+
# EXTRA_ARGS (one key per element) is comma-joined into a single --keys
|
|
73
|
+
# value inside a subshell so IFS="," never leaks into the caller.
|
|
70
74
|
capture) thrum tmux capture "$AGENT" 2>&1 ;;
|
|
71
|
-
key) thrum tmux
|
|
75
|
+
key) thrum tmux send "$AGENT" --keys "$(IFS=,; printf '%s' "${EXTRA_ARGS[*]:-}")" 2>&1 ;;
|
|
72
76
|
esac
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -77,7 +81,7 @@ run_ssh() {
|
|
|
77
81
|
dest="$(ssh_destination)"
|
|
78
82
|
case "$MODE" in
|
|
79
83
|
capture) cmd="thrum tmux capture '$AGENT'" ;;
|
|
80
|
-
key) cmd="thrum tmux
|
|
84
|
+
key) cmd="thrum tmux send '$AGENT' --keys $(IFS=,; printf '%s' "${EXTRA_ARGS[*]:-}")" ;;
|
|
81
85
|
esac
|
|
82
86
|
# Client-side expansion of REPO_PATH/cmd into the remote command string is
|
|
83
87
|
# intentional -- that's how the resolved repo path and thrum invocation
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
#
|
|
30
30
|
# DELIBERATE SCOPE LIMIT: this script is MECHANICAL ONLY — capture, detect,
|
|
31
31
|
# emit. It never auto-escalates and never auto-approves/denies a modal
|
|
32
|
-
# (never calls `thrum tmux
|
|
32
|
+
# (never sends keys or text — never calls `thrum tmux send`). The persistent-watcher-
|
|
33
33
|
# archetype skill is explicit that judgment (approve/cancel/escalate/nudge)
|
|
34
34
|
# happens in the watcher's OWN turn when it reads the wake message this
|
|
35
35
|
# script's summary line triggers, not in a script. A bash script cannot
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: state-migration
|
|
3
|
-
description: "Use when your agent-local thrum state is empty AND you have an existing State.md file in your agent directory - this is a ONE-TIME conditional onboarding that fires only on that exact combination. Do NOT use if thrum state already has entries (nothing to migrate) or if you have no State.md (brand-new agent, nothing to migrate). Triggers on
|
|
3
|
+
description: "Use when your agent-local thrum state is empty AND you have an existing State.md file in your agent directory - this is a ONE-TIME conditional onboarding that fires only on that exact combination. Do NOT use if thrum state already has entries (nothing to migrate) or if you have no State.md (brand-new agent, nothing to migrate). Triggers on - first session after the thrum state redesign lands, `thrum state list` returning empty while State.md exists on disk, being asked to migrate your personal state."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Migrating personal State.md into `thrum state`
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-the-dashboard
|
|
3
|
+
description: "Use when you want to present an interactive board to the human user (status, options, NEEDs with A/B/C picks), or when you see a dashboard mention and wonder what is actually usable today. Carries the CLI surface, the write/read discipline, and the live-vs-coming honesty table."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using the Agent Dashboard
|
|
7
|
+
|
|
8
|
+
The Dashboard is an agent's way to put an **interactive board** in front of the
|
|
9
|
+
human user: typed widgets (status rows, cards, lists), optional HTML regions,
|
|
10
|
+
and **action-picks the user answers by clicking an option and optionally typing
|
|
11
|
+
nuance**. It replaces the "agent runs a TUI" pattern — it is out-of-band from
|
|
12
|
+
the messaging channel, so it never blocks messages and answers never scroll
|
|
13
|
+
away.
|
|
14
|
+
|
|
15
|
+
This is the COMMONS skill: role-agnostic surface + discipline. Role overlays
|
|
16
|
+
(`coordinator-dashboard-human-loop`, `brainstormer-dashboard-needs`) add the
|
|
17
|
+
role-specific loop on top. When an overlay and this skill conflict, the overlay
|
|
18
|
+
wins for role-specific behavior.
|
|
19
|
+
|
|
20
|
+
## 🔴 Honesty table — what is usable TODAY
|
|
21
|
+
|
|
22
|
+
Check this BEFORE you promise the human anything. Stale promises are worse
|
|
23
|
+
than no dashboard.
|
|
24
|
+
|
|
25
|
+
| Capability | Status |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `dashboard.set/get/show/clear/delete` RPCs (daemon-side) | **LANDED** on trunk (merge `6c7539236f83`); usable only against daemons BUILT after that merge — verify your daemon's build before relying on the RPCs |
|
|
28
|
+
| `thrum dashboard` CLI (set/show/get/update/delete/clear/results) | **LANDED** on trunk (merge `108511a0`); usable only against a `thrum` binary BUILT after that merge — verify your CLI's build before relying on these commands |
|
|
29
|
+
| Web UI Dashboard tab (where the human SEES a board) | **BUILT, NOT YET DEPLOYED** (E3 landed on trunk, merge `49712f87` — visible after the next deploy) |
|
|
30
|
+
| Answer round-trip (user clicks → message + nudge to you) | **BUILT, NOT YET DEPLOYED** (E4 landed on trunk, merge `ea95318a` — visible after the next deploy) |
|
|
31
|
+
|
|
32
|
+
**Consequence until the next deploy carries E3 and E4:** publishing a board
|
|
33
|
+
is STILL a no-op for the human on any box running a pre-E3/pre-E4 binary —
|
|
34
|
+
it renders NOWHERE there and, even where it renders, an answer cannot
|
|
35
|
+
return to you, even though the UI and answer-round-trip code now both exist
|
|
36
|
+
on trunk. Do NOT substitute a dashboard for a nudge or a direct message
|
|
37
|
+
unless you know the box you're talking to has deployed a build containing
|
|
38
|
+
BOTH E3 and E4. If the human cannot see the board or their answer cannot
|
|
39
|
+
reach you, the question DOES disappear — the exact failure the dashboard
|
|
40
|
+
exists to kill.
|
|
41
|
+
|
|
42
|
+
**Consequence, now that E2 has landed:** the `thrum dashboard ...` command
|
|
43
|
+
shapes below are runnable — but, per the E2 row above, only against a CLI
|
|
44
|
+
binary built after `108511a0`. Against an older binary, or if you're
|
|
45
|
+
calling the daemon directly, the raw RPCs (`dashboard.set` etc. over the
|
|
46
|
+
daemon socket) remain the fallback surface.
|
|
47
|
+
|
|
48
|
+
## The board model
|
|
49
|
+
|
|
50
|
+
A dashboard is ONE structured document per agent: an envelope
|
|
51
|
+
`{v, agent_id, title, updated_at, rev, widgets[]}`. Six widget types:
|
|
52
|
+
`section` (labeled grouping), `stat-row` (label/value pairs),
|
|
53
|
+
`card` (title + markdown body), `list` (ordered rows),
|
|
54
|
+
`action-pick` (question + options + optional text — the interactive control),
|
|
55
|
+
`html` (sanitized rich region, authored from a FILE).
|
|
56
|
+
|
|
57
|
+
Semantics that matter to you as an author:
|
|
58
|
+
|
|
59
|
+
- **Append-only + rev'd.** Every change appends a new snapshot; `rev` increments.
|
|
60
|
+
Your `update` keeps a widget's position; `delete` removes it; `clear` wipes
|
|
61
|
+
the board. Cleared/deleted content is **GONE** — the board is ephemeral, and
|
|
62
|
+
the DURABLE record of any decision is the answer MESSAGES, not the board.
|
|
63
|
+
- **Widget cardinality (enforced):** action-pick ≤8, html ≤4, section ≤8,
|
|
64
|
+
card ≤24, stat-row ≤12, list ≤4, total ≤64. Lists have NO row cap.
|
|
65
|
+
- **Answer path:** when the user answers an action-pick, you get a **nudge**
|
|
66
|
+
and the answer lands as a thrum MESSAGE (payload type `dashboard.answer`,
|
|
67
|
+
carrying `question_id`, the option `pick`, optional `text`). You read
|
|
68
|
+
answers with `thrum dashboard results`, then flip the widget's status via
|
|
69
|
+
`thrum dashboard update` so the board shows it resolved.
|
|
70
|
+
- **User deletes are SILENT.** If a widget you posted disappears, the user
|
|
71
|
+
dismissed it — you are NOT nudged. Notice it on your next `show`; do not
|
|
72
|
+
repost it reflexively (the user saw it and removed it on purpose).
|
|
73
|
+
- **No dashboard yet is not an error.** `dashboard.get`/`dashboard.show`
|
|
74
|
+
succeed with `found: false` and an empty envelope when nothing has been
|
|
75
|
+
posted for you yet — do not treat that as a failure to recover from.
|
|
76
|
+
- **Concurrent writes are guarded by `expected_rev`, not merged.** If you pass
|
|
77
|
+
`expected_rev` and it no longer matches the board's current `rev`, the write
|
|
78
|
+
is rejected with `dashboard changed (rev %d, you expected %d) — re-read with
|
|
79
|
+
dashboard show and resubmit`. Fix: re-fetch the current document and
|
|
80
|
+
resubmit with the fresh `rev`. Omitting `expected_rev` skips the check
|
|
81
|
+
entirely (last-writer-wins) — fine for your own solo edits, risky if
|
|
82
|
+
something else writes the same board.
|
|
83
|
+
|
|
84
|
+
## CLI surface (runnable now, against a `thrum` binary built after `108511a0`)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Publish/replace whole widgets (upsert by widget id — targets update in place)
|
|
88
|
+
cat > /tmp/board.md <<'EOF'
|
|
89
|
+
{"title": "My board", "widgets": [ ... ]}
|
|
90
|
+
EOF
|
|
91
|
+
thrum dashboard set --doc-file /tmp/board.md
|
|
92
|
+
thrum dashboard set --stdin < /tmp/board.md
|
|
93
|
+
|
|
94
|
+
# HTML regions come from FILES ONLY — never inline (backticks in shell strings EXECUTE)
|
|
95
|
+
thrum dashboard set --html-file /tmp/panel.html --html-id my-panel
|
|
96
|
+
|
|
97
|
+
# Read
|
|
98
|
+
thrum dashboard show # compact outline: what's on the board, what's PENDING/ANSWERED
|
|
99
|
+
thrum dashboard get <widget-id> # full widget JSON
|
|
100
|
+
thrum dashboard results # user answers (after your nudge), newest N selected, displayed oldest-first
|
|
101
|
+
thrum dashboard results --unanswered # only questions with no answer yet
|
|
102
|
+
|
|
103
|
+
# Mutate
|
|
104
|
+
thrum dashboard update <widget-id> --doc-file /tmp/new-widget.json # in place, keeps position
|
|
105
|
+
thrum dashboard delete <widget-id>
|
|
106
|
+
thrum dashboard clear # wipe the whole board (content is GONE)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Validation loop:** `set`/`update` either print `posted OK (rev N)` or fail
|
|
110
|
+
with one of three error shapes: a per-widget schema error naming the exact
|
|
111
|
+
widget id + field; a per-type cardinality error naming the widget TYPE + count
|
|
112
|
+
(e.g. "too many action-pick widgets: 9 (max 8)"); or the aggregate/total-cap
|
|
113
|
+
error, which names a CATEGORY TOKEN + count + max rather than a widget type
|
|
114
|
+
(e.g. "too many widgets (total_widgets): 65 (max 64)" — "total_widgets" is the
|
|
115
|
+
violation category, not a widget type: the per-type limits sum to 60 < 64, so
|
|
116
|
+
a doc breaching 64 always breaches some per-type limit too, and there is no
|
|
117
|
+
single offending widget type to name). On any of these: fix the file and
|
|
118
|
+
resubmit — do not recompute the whole board inside one invocation.
|
|
119
|
+
|
|
120
|
+
**Authoring rules with teeth:**
|
|
121
|
+
|
|
122
|
+
- Widget `id`s are load-bearing — stable across updates; `update`/`delete`
|
|
123
|
+
and the answer join key off them (action-picks also carry `question_id`).
|
|
124
|
+
- Text fields render as MARKDOWN (no raw HTML — that is the `html` widget's
|
|
125
|
+
job, sanitized). Keep compact fields single-line.
|
|
126
|
+
- HTML files: no script/style/iframe/event-handlers — the render side strips
|
|
127
|
+
them, but author clean anyway.
|
|
128
|
+
|
|
129
|
+
## When to reach for the dashboard (vs a nudge vs a message)
|
|
130
|
+
|
|
131
|
+
- **Dashboard:** state the human will consume VISUALLY and possibly answer
|
|
132
|
+
interactively — rosters, queues of options, pipelines, NEEDs with discrete
|
|
133
|
+
choices. Persistent board the human checks on their own rhythm.
|
|
134
|
+
- **Nudge:** something needs attention NOW and is small (one line). The
|
|
135
|
+
dashboard does not pulse or blink; urgent + tiny = nudge.
|
|
136
|
+
- **Message:** conversation, anything needing prose back-and-forth, or anything
|
|
137
|
+
the record must keep forever (decisions live in messages).
|
|
138
|
+
|
|
139
|
+
Anti-pattern: posting a board INSTEAD of answering a direct question. The
|
|
140
|
+
dashboard is a surface you maintain, not a replacement for talking.
|
|
141
|
+
|
|
142
|
+
## Refs to other skills
|
|
143
|
+
|
|
144
|
+
- `coordinator-dashboard-human-loop` — coordinator overlay: owning a board for
|
|
145
|
+
the human, the human-loop mandate
|
|
146
|
+
- `brainstormer-dashboard-needs` — brainstormer/researcher overlay: authoring
|
|
147
|
+
NEEDs as answerable action-picks
|
|
148
|
+
- `using-the-queue`, `using-thrum-state` — sibling agent-tied local surfaces
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: using-thrum-state
|
|
3
|
-
description: "Use when reading or writing thrum state - checking your own agent-local current state at session start, updating your status/next-actions, reading a fleet fact like deploy state or topology before acting on it, citing a SHA or box fact in a decision, deciding whether a fleet row is fresh enough to trust, or responding to a freshness-sweep ping. Also use before writing any fleet row - deploy state, topology, worktree, agent pool, or a standing ruling - to confirm you are writing from a by-effect measurement, not a guess. Triggers on
|
|
3
|
+
description: "Use when reading or writing thrum state - checking your own agent-local current state at session start, updating your status/next-actions, reading a fleet fact like deploy state or topology before acting on it, citing a SHA or box fact in a decision, deciding whether a fleet row is fresh enough to trust, or responding to a freshness-sweep ping. Also use before writing any fleet row - deploy state, topology, worktree, agent pool, or a standing ruling - to confirm you are writing from a by-effect measurement, not a guess. Triggers on - citing a fleet fact, deploying, restarting a daemon, migrating schema, reconciling state at session start, receiving a stale-row ping."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Using Thrum State
|