@martintrojer/murmur 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/ARCHITECTURE.md +357 -0
- package/README.md +117 -0
- package/dist/cli.js +1426 -0
- package/dist/cli.js.map +1 -0
- package/dist/extension/murmur-pi.js +224 -0
- package/dist/extension/murmur-pi.js.map +1 -0
- package/dist/extension/store.js +257 -0
- package/dist/extension/store.js.map +1 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.js +894 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# murmur architecture
|
|
2
|
+
|
|
3
|
+
How it works, why it is built this way, and why it exists rather than a
|
|
4
|
+
configuration of something else.
|
|
5
|
+
|
|
6
|
+
## The problem
|
|
7
|
+
|
|
8
|
+
Coding agents run on more than one machine: a laptop, a desktop, a remote box.
|
|
9
|
+
Each machine knows what its own agents are doing. No machine knows what the
|
|
10
|
+
others are doing. So "is anything blocked on me right now" means visiting each
|
|
11
|
+
one in turn, and jumping to an agent means first remembering which host it lives
|
|
12
|
+
on.
|
|
13
|
+
|
|
14
|
+
That is the whole problem. Everything below is in service of it, and the design
|
|
15
|
+
spends most of its effort refusing to solve harder problems nearby.
|
|
16
|
+
|
|
17
|
+
## The shape
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
pi agent ──in-process── murmur store ── events.db (one per node)
|
|
21
|
+
│
|
|
22
|
+
ssh murmur export --since N
|
|
23
|
+
│
|
|
24
|
+
collector ── fold ── picker
|
|
25
|
+
│
|
|
26
|
+
jump: local switch, or ssh -t
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each node keeps an append-only SQLite log describing only its own agents. Any
|
|
30
|
+
node pulls its peers' logs over ssh and folds the union into one
|
|
31
|
+
attention-sorted picker. No daemon, no listening socket, no master node.
|
|
32
|
+
|
|
33
|
+
murmur observes and connects. It does not place work. That is an orchestrator's
|
|
34
|
+
job, and mixing the two is how you end up owning scheduling, credentials and
|
|
35
|
+
artifact movement.
|
|
36
|
+
|
|
37
|
+
## Why this is not a distributed system
|
|
38
|
+
|
|
39
|
+
It is single-writer-per-partition replication. Each node authors only events
|
|
40
|
+
about its own agents, so no two nodes ever write about the same thing. Merging
|
|
41
|
+
is a UNION.
|
|
42
|
+
|
|
43
|
+
Calling it "distributed" invites machinery the problem does not have: consensus,
|
|
44
|
+
conflict resolution, vector clocks, leader election. If a change starts to need
|
|
45
|
+
conflict resolution, that is the signal the single-writer invariant has been
|
|
46
|
+
broken somewhere.
|
|
47
|
+
|
|
48
|
+
Two consequences worth stating, because both are easy to violate by accident:
|
|
49
|
+
|
|
50
|
+
- **A node may not author events about another node's agents.** Not even
|
|
51
|
+
corrections. A reader that learns something (a jump proving a window is gone)
|
|
52
|
+
records it as *reader state*, or deletes its replica. It does not write an
|
|
53
|
+
event.
|
|
54
|
+
- **`host_id` is the origin, watermarks are keyed by peer.** These are the same
|
|
55
|
+
thing today, and differ the moment anything gossips. Free to preserve now,
|
|
56
|
+
expensive to retrofit.
|
|
57
|
+
|
|
58
|
+
## The six units
|
|
59
|
+
|
|
60
|
+
| Unit | Does | Depends on |
|
|
61
|
+
| ---- | ---- | ---------- |
|
|
62
|
+
| `identity` | Read/create this node's `{host_id, display_name}` | state dir |
|
|
63
|
+
| `store` | Append, query, ingest, prune. **The only module touching SQL** | `identity` |
|
|
64
|
+
| `fold` | **Pure.** Events in, agent states out | nothing |
|
|
65
|
+
| `channel` | Seam: `exec(target, argv) -> stdout`. One impl: ssh | OS |
|
|
66
|
+
| `collector` | Pull each peer, ingest, advance watermark | `channel`, `store` |
|
|
67
|
+
| `mux` | Seam: window/pane queries, set state, attach. One impl: tmux | OS |
|
|
68
|
+
|
|
69
|
+
`fold` being pure and `store` being the only SQL is the boundary that carries
|
|
70
|
+
the design. Four heuristics live in one of those two modules: attention
|
|
71
|
+
ordering, staleness, crash synthesis, and retention. Both modules are testable
|
|
72
|
+
without a machine, a network or a multiplexer.
|
|
73
|
+
|
|
74
|
+
Three seams exist with exactly one implementation each: `channel` (ssh),
|
|
75
|
+
`harness` (pi, in-process), and `mux` (tmux). Defined so a second
|
|
76
|
+
backend is possible; not designed for one that does not exist.
|
|
77
|
+
|
|
78
|
+
## The data model
|
|
79
|
+
|
|
80
|
+
One append-only table. Sole author per `host_id`. Primary key `(host_id, seq)`,
|
|
81
|
+
which is what makes ingest idempotent. A re-read after a partial failure is
|
|
82
|
+
free.
|
|
83
|
+
|
|
84
|
+
| Column | Notes |
|
|
85
|
+
| ------ | ----- |
|
|
86
|
+
| `host_id` | UUID of the **origin** node, not the node we fetched from |
|
|
87
|
+
| `seq` | Monotonic per `host_id`. The watermark unit |
|
|
88
|
+
| `ts` | Wall clock. Display ordering only |
|
|
89
|
+
| `agent_id` | Stable for the agent's life. Identity, distinct from location |
|
|
90
|
+
| `session`, `window`, `pane` | Current **location**. May change |
|
|
91
|
+
| `session_name`, `window_name` | Recorded by the author; ids are machine-local |
|
|
92
|
+
| `agent_name`, `pi_session` | Richer than tmux, and not derivable from it |
|
|
93
|
+
| `workstream`, `role`, `cli` | Nullable. Grouping and display |
|
|
94
|
+
| `driver` | `human \| orchestrated`. **Per agent, not per node** |
|
|
95
|
+
| `kind` | `state` today. Discriminator for future kinds |
|
|
96
|
+
| `state` | `working \| blocked \| done \| crashed \| cleared` |
|
|
97
|
+
| `message`, `pid`, `synthetic`, `reason` | Detail |
|
|
98
|
+
| `extra` | JSON. Unknown fields, preserved verbatim |
|
|
99
|
+
|
|
100
|
+
**Unknown data round-trips.** A node ingesting an unknown `kind` or unknown
|
|
101
|
+
fields stores them in `extra` and re-exports them unchanged. Without this, an
|
|
102
|
+
old node sitting in a future replication path silently truncates data. That
|
|
103
|
+
failure is invisible from both ends, and only reachable against a version you
|
|
104
|
+
do not control.
|
|
105
|
+
|
|
106
|
+
Deliberately absent: a generic `put`/`del` op-log (that needs conflict
|
|
107
|
+
resolution, which single-writer partitioning lets us skip, and it would simply
|
|
108
|
+
*be* `mu`), task/DAG structure (observing a task graph means owning it), and any
|
|
109
|
+
hybrid logical clock (nothing depends on cross-node causality; clock skew
|
|
110
|
+
affects display order only).
|
|
111
|
+
|
|
112
|
+
**An event log, not a state snapshot.** Current state is a fold over the log,
|
|
113
|
+
which buys three things: there is no state table that can disagree with the log,
|
|
114
|
+
incremental sync is a watermark ("everything after N", idempotent), and history
|
|
115
|
+
is not optional, because the picker's preview shows recent events, so the
|
|
116
|
+
protocol has to ship events rather than derived state.
|
|
117
|
+
|
|
118
|
+
**TypeScript, on install-story grounds.** pi is itself an npm package, so every
|
|
119
|
+
node that runs agents necessarily has Node and npm, so npm is not a new
|
|
120
|
+
dependency on exactly the machines that matter. Python would mean inventing a cross-machine
|
|
121
|
+
install story for the ecosystem that handles it worst, on machines where the
|
|
122
|
+
system interpreter is least yours to touch. The "keep the working script"
|
|
123
|
+
argument is weaker than it looks: those lines worked because dotfiles symlinked
|
|
124
|
+
them, so the moment a second machine needs them they must be packaged from
|
|
125
|
+
scratch either way.
|
|
126
|
+
|
|
127
|
+
## Three ideas to understand before changing anything
|
|
128
|
+
|
|
129
|
+
Everything else is mechanical. These three are the ones to understand before
|
|
130
|
+
changing anything.
|
|
131
|
+
|
|
132
|
+
### 1. State and freshness are different axes, and freshness is two
|
|
133
|
+
|
|
134
|
+
`stale` is not an agent state. The enum stays `working | blocked | done |
|
|
135
|
+
crashed | cleared`.
|
|
136
|
+
|
|
137
|
+
- **state** — what the agent is doing. Authored by the agent, in the log.
|
|
138
|
+
- **freshness** — how current our replica is. Known only by the reader.
|
|
139
|
+
|
|
140
|
+
Putting `stale` in the enum produces unanswerable questions: is a crashed agent
|
|
141
|
+
on an unreachable host `crashed` or `stale`? Both, on different axes.
|
|
142
|
+
|
|
143
|
+
Freshness then splits again, and missing this shipped a bug:
|
|
144
|
+
|
|
145
|
+
| | asks | answers |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `fetched_at` | how current is my copy | is the host reachable |
|
|
148
|
+
| event `ts` | how old is this agent's news | is the row worth believing |
|
|
149
|
+
|
|
150
|
+
A peer polled one second ago can be serving three-hour-old events. Collapsing
|
|
151
|
+
these made a dead host's agents render as live, because the replica really was
|
|
152
|
+
current.
|
|
153
|
+
|
|
154
|
+
### 2. Facts only the author can know are recorded by the author
|
|
155
|
+
|
|
156
|
+
Pids, window liveness and window names mean something only on the machine that
|
|
157
|
+
owns them. So crash synthesis runs on the authoring node during export, not on
|
|
158
|
+
the reader.
|
|
159
|
+
|
|
160
|
+
Do it on the reader and every remote `working` agent is marked `crashed`, which
|
|
161
|
+
looks exactly like a real crash, so it does not get investigated. This is the
|
|
162
|
+
single easiest thing in the codebase to get backwards.
|
|
163
|
+
|
|
164
|
+
The same rule gave names their home: window ids are machine-local, so resolving
|
|
165
|
+
a remote id against the local tmux labels an agent with whatever *this* machine
|
|
166
|
+
has at that id. Names travel on the event instead.
|
|
167
|
+
|
|
168
|
+
### 3. The single-machine case is the same code path
|
|
169
|
+
|
|
170
|
+
murmur replaced a 1500-line script that was the daily local tool. Zero peers is
|
|
171
|
+
therefore the common case:
|
|
172
|
+
|
|
173
|
+
- the pi extension appends events and sets the tmux window option
|
|
174
|
+
- the status bar and picker read the fold
|
|
175
|
+
- the collector iterates the peer list, finds nothing, and does nothing
|
|
176
|
+
|
|
177
|
+
No network, no ssh, no daemon, no added latency. Federation is strictly
|
|
178
|
+
additive: a `host_id` on rows that all say "me", and a loop over an empty array.
|
|
179
|
+
|
|
180
|
+
This is a constraint, not an observation. A tool that only pays for itself at
|
|
181
|
+
three nodes charges rent daily for fleet capability used occasionally, which is
|
|
182
|
+
one of the things herdr was rejected for. Measured first paint with zero peers:
|
|
183
|
+
48 ms against 250 ms for the picker it replaces.
|
|
184
|
+
|
|
185
|
+
## Design choices, and what they cost
|
|
186
|
+
|
|
187
|
+
**Pull, not push.** "Adhoc ssh" and "an open tunnel" are one model at two
|
|
188
|
+
latencies. Both are the reader pulling, and OpenSSH `ControlMaster` collapses
|
|
189
|
+
them, because the persisted control socket *is* the tunnel. Push needs a
|
|
190
|
+
listener, which is the thing this design does not have.
|
|
191
|
+
|
|
192
|
+
**The collector never initiates authentication.** A machine that wants a
|
|
193
|
+
hardware-token touch per connection makes a background collector intolerable, so
|
|
194
|
+
the collector rides an existing warm control socket and fails fast otherwise.
|
|
195
|
+
The consequence is correct: remote visibility is a side effect of having worked
|
|
196
|
+
on that box, and a cold host shows stale until you connect for any reason.
|
|
197
|
+
|
|
198
|
+
**Membership is local and asymmetric.** No shared node list, no registry, no
|
|
199
|
+
join protocol. Reachability is not symmetric: a laptop reaches a server, and
|
|
200
|
+
the server does not reach a laptop behind NAT that sleeps. A global list would
|
|
201
|
+
advertise peers half the fleet cannot use. And nothing needs one: only a node
|
|
202
|
+
rendering a picker needs targets, and only ones it can reach. Identity is
|
|
203
|
+
*discovered*. Config holds an ssh target, and the first export returns the
|
|
204
|
+
node's UUID and display name.
|
|
205
|
+
|
|
206
|
+
**Zero knobs.** Every setting exported to the user is one they can get wrong
|
|
207
|
+
invisibly. Only two are irreducible: `peers` (only the operator knows their
|
|
208
|
+
fleet) and `theme`. Retention horizon, collection interval and the staleness
|
|
209
|
+
threshold are constants or derived. The trade is real: a wrong constant needs a
|
|
210
|
+
release rather than an edit. Since heuristics replace knobs, the heuristics are
|
|
211
|
+
where the tests go.
|
|
212
|
+
|
|
213
|
+
**`driver` distinguishes who is waiting.** An agent you are talking to and an
|
|
214
|
+
agent an orchestrator placed want opposite treatment: when the orchestrated one
|
|
215
|
+
finishes, its supervisor consumes the result and nobody needs to acknowledge
|
|
216
|
+
anything. Same state, opposite attention. It is per *agent*, not per node,
|
|
217
|
+
because the normal case is one machine running your session and six spawned
|
|
218
|
+
workers at once. Null reads as `human`, so an older node's events degrade toward
|
|
219
|
+
visible.
|
|
220
|
+
|
|
221
|
+
**Glance, not remote rendering.** "Render any pane from the master" hides two
|
|
222
|
+
very different problems: a stateless `capture-pane` (cheap, and what the picker
|
|
223
|
+
preview does) and continuous frame streaming with resize negotiation and input
|
|
224
|
+
routing (most of herdr's codebase). Glance plus jump gets everything except
|
|
225
|
+
never leaving the local frame. Since you are jumping there to work anyway, that
|
|
226
|
+
may not be missed. This deferral is the main reason murmur is small.
|
|
227
|
+
|
|
228
|
+
## Why not something else
|
|
229
|
+
|
|
230
|
+
Investigated against herdr 0.8.2, a T3 Code checkout, and `mu`, all built and
|
|
231
|
+
run locally rather than judged from their READMEs.
|
|
232
|
+
|
|
233
|
+
| | multi-machine view | pi support | state source |
|
|
234
|
+
| --- | --- | --- | --- |
|
|
235
|
+
| herdr | no — 1:1, planned, blocked | yes | screen-scraped |
|
|
236
|
+
| T3 Code | no — "unbuilt" by its own docs | no — 14-method adapter | driven |
|
|
237
|
+
| mu | state sync yes, agents no | yes | reported |
|
|
238
|
+
| **murmur** | **yes** | **yes, in-process** | **pushed** |
|
|
239
|
+
|
|
240
|
+
### herdr
|
|
241
|
+
|
|
242
|
+
A Rust terminal multiplexer built for coding agents: workspaces, tabs, panes, a
|
|
243
|
+
per-pane `idle/working/blocked/done` sidebar, a socket API. Evaluated as a tmux
|
|
244
|
+
replacement and rejected on its own terms.
|
|
245
|
+
|
|
246
|
+
On multi-machine it is strictly 1:1. `--remote <target>` takes a single
|
|
247
|
+
target, and no agent/pane/notification subcommand has a `--host` flag anywhere.
|
|
248
|
+
`--remote` *replaces* the view rather than adding to it, so two machines means
|
|
249
|
+
two sessions and a full switch between them. Multi-client is the maintainer's
|
|
250
|
+
stated top priority but gated behind a server/client refactor that has been in
|
|
251
|
+
progress for some time.
|
|
252
|
+
|
|
253
|
+
Waiting would not help, for two structural reasons. The scope is one client
|
|
254
|
+
attaching to multiple *herdr* servers, so every machine must run herdr,
|
|
255
|
+
including machines where you cannot install a multiplexer of your choosing. And
|
|
256
|
+
herdr detects state by matching terminal output, so adopting it means trading
|
|
257
|
+
push-based state from inside the agent for screen-scraping.
|
|
258
|
+
|
|
259
|
+
Taken from it: integration installs that write hooks into each agent's own
|
|
260
|
+
config directory (`murmur link pi`), reusing one authenticated connection, and
|
|
261
|
+
its own stated non-goals: no merged PTYs across machines, no moving work
|
|
262
|
+
between hosts, host as a lightweight label. Rejected: the always-present
|
|
263
|
+
sidebar, not for its ~4 columns but because it is fixed to one edge and cannot
|
|
264
|
+
become a horizontal strip, while a status row is overhead already paid.
|
|
265
|
+
|
|
266
|
+
### T3 Code
|
|
267
|
+
|
|
268
|
+
An "agent harness control surface": a server owning agent sessions plus web,
|
|
269
|
+
desktop and mobile clients over one RPC WebSocket.
|
|
270
|
+
|
|
271
|
+
Its remote access is well ahead of herdr: direct ws/wss, bearer pairing, relay
|
|
272
|
+
tunnels, mesh-VPN serve and desktop-managed SSH, all shipped. But the aggregated view is unbuilt, by its own internals docs: multiple
|
|
273
|
+
live connections exist, a fused cross-machine agent overview does not.
|
|
274
|
+
|
|
275
|
+
It also does not support pi, and adding it is expensive: a provider needs a
|
|
276
|
+
driver plus an adapter implementing fourteen methods, and the reference
|
|
277
|
+
implementation is over 1700 lines. murmur's adapter problem is smaller for a
|
|
278
|
+
structural reason. T3 Code drives an agent it does not live inside, while
|
|
279
|
+
murmur's extension runs *in process* and calls the store directly.
|
|
280
|
+
|
|
281
|
+
Taken from it: the rule that *remoteness is expressed at the connection layer,
|
|
282
|
+
never by splitting the runtime* (murmur's channel seam is exactly this),
|
|
283
|
+
transport is not an identity, and environment identity as a stable UUID rather
|
|
284
|
+
than a hostname.
|
|
285
|
+
|
|
286
|
+
### mu
|
|
287
|
+
|
|
288
|
+
An agent orchestrator: workstreams, a task DAG, agents in panes, isolated
|
|
289
|
+
workspaces. It already solved the hard half of the replication problem: machine
|
|
290
|
+
identity, per-peer watermarks, an op-log.
|
|
291
|
+
|
|
292
|
+
Two ideas taken directly. Sync is ambient rather than a daemon: every invocation
|
|
293
|
+
syncs before the verb, and no watcher outlives the command. And sync must never
|
|
294
|
+
fail a command (every ambient entry point is total; a dead peer warns and returns).
|
|
295
|
+
|
|
296
|
+
One idea deliberately not taken: mu's generic replicated KV. A generic op-log
|
|
297
|
+
needs conflict resolution, which single-writer partitioning skips entirely, and
|
|
298
|
+
a murmur with `put`/`del` over arbitrary entities would just *be* mu.
|
|
299
|
+
|
|
300
|
+
**Relationship:** murmur observes, mu orchestrates. Merging is plausible later,
|
|
301
|
+
since murmur would give mu global agent addressing and remote observation. But
|
|
302
|
+
remote *orchestration* is much harder than remote observation, and none of it is
|
|
303
|
+
murmur's problem.
|
|
304
|
+
|
|
305
|
+
## Testing posture
|
|
306
|
+
|
|
307
|
+
Bug-driven, not coverage-driven. A thing earns a test when it can fail *without
|
|
308
|
+
you noticing*:
|
|
309
|
+
|
|
310
|
+
| Target | Why it can fail silently |
|
|
311
|
+
| ------ | ------------------------ |
|
|
312
|
+
| Fold precedence | A wrong glyph gets rationalized, not investigated |
|
|
313
|
+
| Staleness derivation | A dead peer keeps showing last-known state forever |
|
|
314
|
+
| Ingest idempotency | Duplicate rows after a partial read |
|
|
315
|
+
| Unknown-field preservation | Breaks against a future node you do not own |
|
|
316
|
+
| Retention keeping newest-per-agent | Idle agents vanish; reads as "not there" |
|
|
317
|
+
|
|
318
|
+
Not tested: ssh transport (OpenSSH's job), tmux wrappers (thin and loud), TUI
|
|
319
|
+
rendering, packaging.
|
|
320
|
+
|
|
321
|
+
New tests are verified by breaking the code they cover and watching them fail. A
|
|
322
|
+
test that has never failed has not been shown to test anything. During
|
|
323
|
+
development a test asserting a *wrong* behaviour was written twice, so this step
|
|
324
|
+
is not ceremony.
|
|
325
|
+
|
|
326
|
+
**The thing to know before adding a feature:** most bugs worth fixing here were
|
|
327
|
+
invisible to unit tests and to reading the code. A silently no-opping extension,
|
|
328
|
+
a remote jump broken by two independent shell-quoting layers, a 75-second hang
|
|
329
|
+
on an unreachable peer, ages that measured the wrong clock. All of them surfaced
|
|
330
|
+
by running the thing on two real machines. Unit tests protect the heuristics;
|
|
331
|
+
they do not tell you the tool is usable.
|
|
332
|
+
|
|
333
|
+
## Deliberate non-goals
|
|
334
|
+
|
|
335
|
+
Each was considered and refused with reasons above:
|
|
336
|
+
|
|
337
|
+
- a daemon or listening socket
|
|
338
|
+
- interactive remote terminal rendering
|
|
339
|
+
- orchestration or work placement
|
|
340
|
+
- a generic put/del op-log
|
|
341
|
+
- HLC or clock reconciliation
|
|
342
|
+
- gossip replication (schema-compatible, not built)
|
|
343
|
+
- configuration knobs beyond peers and theme
|
|
344
|
+
- harnesses other than pi, multiplexers other than tmux, channels other than ssh
|
|
345
|
+
|
|
346
|
+
## Known gaps
|
|
347
|
+
|
|
348
|
+
- **Nested tmux.** Jumping to a remote agent runs `ssh -t host tmux attach`
|
|
349
|
+
inside a local tmux window, which nests. Needs a distinct inner prefix or
|
|
350
|
+
`send-prefix`. Everyone in this space punts on it; herdr bans nesting outright.
|
|
351
|
+
- **Interactive attach is unverified.** Federation, staleness and the jump
|
|
352
|
+
target are verified across two machines over real ssh; sitting in a remote
|
|
353
|
+
pane and working in it is not.
|
|
354
|
+
- **The hardware-token path is verified only in the cold-fail direction.** The
|
|
355
|
+
second test node authenticates by key, so it never needed a warm socket.
|
|
356
|
+
- **Non-pi harnesses have no attention path.** Agents that cannot report from
|
|
357
|
+
inside themselves need an outside-in `notify` verb, which does not exist.
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# murmur
|
|
2
|
+
|
|
3
|
+
**Every coding agent you have running, on every machine, in one list.**
|
|
4
|
+
|
|
5
|
+
You have agents on your laptop, your desktop, and a box somewhere else. One of
|
|
6
|
+
them is blocked waiting on you right now. Which one?
|
|
7
|
+
|
|
8
|
+
Today you find out by walking the machines. Attach here, glance there, try to
|
|
9
|
+
remember where you left that session. murmur answers the question in one
|
|
10
|
+
keystroke, then jumps you to the agent on whichever machine it turns out to be.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
state agent workstream host age / flags
|
|
14
|
+
! blocked review the auth change api → devbox 4m
|
|
15
|
+
▶ working Fix the picker filter murmur here
|
|
16
|
+
✓ done migrate the fixtures api → devbox 12m
|
|
17
|
+
· idle worker-2 infra here crew
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Pick a row and press enter. Local agents are a window switch away; remote ones
|
|
21
|
+
open over ssh. The preview beside the list shows the last few lines the agent
|
|
22
|
+
printed, so you can tell "waiting on me" from "still thinking" without going
|
|
23
|
+
there at all.
|
|
24
|
+
|
|
25
|
+
## Why it exists
|
|
26
|
+
|
|
27
|
+
The tools in this space are excellent at one machine and stop there.
|
|
28
|
+
[herdr](https://herdr.dev) is strictly one client to one server, and its
|
|
29
|
+
multi-client work is blocked behind an unfinished refactor. T3 Code has better
|
|
30
|
+
remote *access* than anything else here, but lists the aggregated view as
|
|
31
|
+
unbuilt in its own docs. Both infer agent state by matching terminal output.
|
|
32
|
+
|
|
33
|
+
murmur takes a different bet. The agent reports its own state from inside the
|
|
34
|
+
process, and the machines exchange nothing more complicated than "here is my
|
|
35
|
+
log since event N". Knowing what is happening is the hard part, and reporting
|
|
36
|
+
it from inside the agent is what makes it reliable.
|
|
37
|
+
|
|
38
|
+
## What it is
|
|
39
|
+
|
|
40
|
+
- **A state layer over tmux:** tmux keeps owning your panes. murmur owns the
|
|
41
|
+
answer to "what is every agent doing right now".
|
|
42
|
+
- **Push-based state:** a pi extension reports from inside the agent. Nothing
|
|
43
|
+
screen-scrapes, and a crash is detected from a pid rather than guessed from
|
|
44
|
+
output.
|
|
45
|
+
- **No daemon, no listening socket, no master:** peers are pulled over ssh when
|
|
46
|
+
you run a command. Every node can aggregate; none is special.
|
|
47
|
+
- **Fast with one machine:** it replaced a local-only script and got quicker
|
|
48
|
+
doing it, 48 ms to first paint against 250 ms. Configuring zero peers is the
|
|
49
|
+
common case, and nothing about it is degraded.
|
|
50
|
+
|
|
51
|
+
## What it is not
|
|
52
|
+
|
|
53
|
+
It does not orchestrate. It observes and connects, and never places work; that
|
|
54
|
+
is [`mu`](https://github.com/martintrojer/mu)'s job.
|
|
55
|
+
|
|
56
|
+
It is not a remote terminal. You can glance at a remote pane or jump to it, but
|
|
57
|
+
there is no frame streaming and no resize negotiation, which is most of why it
|
|
58
|
+
stays small.
|
|
59
|
+
|
|
60
|
+
It does not replace your multiplexer. See
|
|
61
|
+
[ARCHITECTURE.md](ARCHITECTURE.md#why-not-something-else) for the comparison
|
|
62
|
+
against herdr, T3 Code and `mu`.
|
|
63
|
+
|
|
64
|
+
## Requirements
|
|
65
|
+
|
|
66
|
+
tmux, [pi](https://github.com/earendil-works/pi-coding-agent), `fzf`, and Node
|
|
67
|
+
20+. For more than one machine: ssh access, and murmur installed on each.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
On every node that runs agents:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install -g @martintrojer/murmur
|
|
75
|
+
murmur init # this node's identity
|
|
76
|
+
murmur link pi # install the agent-side extension
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`link pi` writes the extension into `~/.pi/agent/extensions/`, pinned to this
|
|
80
|
+
installation. Re-run it after upgrading murmur.
|
|
81
|
+
|
|
82
|
+
Then, on whichever machine you want to watch from:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
murmur peer add devbox # an ssh target; identity is discovered
|
|
86
|
+
murmur pick
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Bind it to a key and you have the whole interface:
|
|
90
|
+
|
|
91
|
+
```tmux
|
|
92
|
+
bind -N "agent state picker" a display-popup -E -w 80% -h 60% "murmur pick"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`murmur status` prints per-state counts for a status bar. Everything else is
|
|
96
|
+
`--help`.
|
|
97
|
+
|
|
98
|
+
## Status
|
|
99
|
+
|
|
100
|
+
**0.1.0.** In daily use on one machine and verified across two over real ssh.
|
|
101
|
+
It is new and not battle-tested. The known gaps are listed at the end of
|
|
102
|
+
[ARCHITECTURE.md](ARCHITECTURE.md#known-gaps); the one most likely to annoy you
|
|
103
|
+
is that jumping to a remote agent nests tmux inside tmux, which every tool in
|
|
104
|
+
this space punts on.
|
|
105
|
+
|
|
106
|
+
The event schema is versioned on the wire and preserves fields it does not
|
|
107
|
+
recognise, so a newer node and an older one can already talk to each other.
|
|
108
|
+
|
|
109
|
+
## Documentation
|
|
110
|
+
|
|
111
|
+
[ARCHITECTURE.md](ARCHITECTURE.md) explains how it works, the three ideas you
|
|
112
|
+
need before changing anything, why it exists rather than the alternatives, and
|
|
113
|
+
what is unfinished.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
*A murmuration: many independent agents, no leader, coherent from a distance.*
|