zswarm 0.2.1 → 0.2.2
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/README.md +5 -2
- package/package.json +8 -5
- package/skills/zswarm/SKILL.md +73 -0
- package/skills/zswarm/agents/openai.yaml +4 -0
- package/skills/zswarm/references/harness.md +21 -0
- package/skills/zswarm/references/ops.md +90 -0
- package/skills/zswarm/references/remote.md +121 -0
- package/skills/zswarm/references/workflows.md +76 -0
package/README.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# zswarm
|
|
2
2
|
|
|
3
|
-
Meta-package for a global install. Puts `zswarm` (CLI) and `zswarm-mcp` (MCP server) on your PATH.
|
|
3
|
+
Meta-package for a global install. Puts `zswarm` (CLI) and `zswarm-mcp` (MCP server) on your PATH, and bundles `skills/zswarm` for AI agent harnesses.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm i -g zswarm
|
|
7
7
|
zswarm list
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
The bundled skill can be copied from `$(npm root -g)/zswarm/skills/zswarm` into your harness skill directory (e.g. `~/.claude/skills/zswarm`, `~/.codex/skills/zswarm`, or `~/.cursor/skills/zswarm`).
|
|
11
|
+
|
|
12
|
+
Ops, env, MCP setup, and the event bus: the [repo README](https://github.com/brandonkramer/zswarm#readme).
|
|
11
13
|
|
|
12
14
|
## License
|
|
13
15
|
|
|
14
16
|
MIT
|
|
17
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zswarm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Coordinate CLI agent crews in Zellij panes",
|
|
6
6
|
"engines": {
|
|
@@ -19,14 +19,17 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"bin",
|
|
22
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"skills"
|
|
23
24
|
],
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@zswarm/cli": "0.2.
|
|
26
|
-
"@zswarm/mcp": "0.1.
|
|
26
|
+
"@zswarm/cli": "0.2.2",
|
|
27
|
+
"@zswarm/mcp": "0.1.12"
|
|
27
28
|
},
|
|
28
29
|
"publishConfig": {
|
|
29
30
|
"access": "public"
|
|
30
31
|
},
|
|
31
|
-
"scripts": {
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node -e \"const fs=require('node:fs'); fs.cpSync('../../skills', 'skills', {recursive:true})\""
|
|
34
|
+
}
|
|
32
35
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zswarm
|
|
3
|
+
description: >-
|
|
4
|
+
Coordinate CLI crews in Zellij panes via zSwarm MCP (list, send, dump, tail,
|
|
5
|
+
wait, status, keys, interrupt, spawn, close, broadcast, signal, signals,
|
|
6
|
+
await, log, worktrees, unworktree, rename, focus, tabs, layout, stack, diff,
|
|
7
|
+
checkpoint, bus, serve). Use when messaging another Codex, Claude Code, Cursor
|
|
8
|
+
CLI, pi, OpenCode, or agy session in a Zellij pane, waiting for one to finish,
|
|
9
|
+
broadcasting to a crew, signalling barriers,
|
|
10
|
+
interrupting, opening a new crew pane, isolating peers in git worktrees,
|
|
11
|
+
reviewing peer diffs/checkpoints, renaming/focusing panes, or dumping short
|
|
12
|
+
scrollback. Same host as Zellij, or ZSWARM_SSH / ZSWARM_SERVE for a remote
|
|
13
|
+
crew — not IDE side-panel chat.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# zSwarm
|
|
17
|
+
|
|
18
|
+
Talk to **CLI** sessions in Zellij panes (Codex, Claude Code, Cursor CLI, pi, OpenCode, agy, shells).
|
|
19
|
+
Delivery is `zellij action paste` + Enter. Not IDE side-panel chat.
|
|
20
|
+
|
|
21
|
+
## Happy path
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
zswarm({ op: "list" })
|
|
25
|
+
zswarm({ op: "send", to: "reviewer", body: "please review the plan" })
|
|
26
|
+
zswarm({ op: "wait", to: "reviewer", for: "idle" })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`session` when more than one Zellij session is live, or set `ZSWARM_SESSION`.
|
|
30
|
+
Resolution: arg → `ZSWARM_SESSION` → `ZELLIJ_SESSION_NAME` → the sole live session.
|
|
31
|
+
MCP hosts inherit neither PATH nor Zellij env — absolute interpreter + `ZSWARM_SESSION`.
|
|
32
|
+
CLI backup: `zswarm <op>` (`@zswarm/cli`).
|
|
33
|
+
|
|
34
|
+
## Read when needed
|
|
35
|
+
|
|
36
|
+
- Ops, submit/expect, bus: [references/ops.md](references/ops.md)
|
|
37
|
+
- Remote SSH (Linux / macOS / Windows) / serve: [references/remote.md](references/remote.md)
|
|
38
|
+
- Barriers, worktrees, review, wait: [references/workflows.md](references/workflows.md)
|
|
39
|
+
- Harness notes: [references/harness.md](references/harness.md)
|
|
40
|
+
|
|
41
|
+
## Prefix
|
|
42
|
+
|
|
43
|
+
Unless `raw: true`:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
[zswarm from=<sender>]
|
|
47
|
+
<body>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`<sender>` is `from` if you pass it, else `ZSWARM_FROM`, else the sending
|
|
51
|
+
pane's title when `ZELLIJ_PANE_ID` / `ZSWARM_SELF_PANE` is visible, else
|
|
52
|
+
`swarm`. MCP hosts that drop Zellij env need those vars listed (or set
|
|
53
|
+
`from` / `ZSWARM_FROM`).
|
|
54
|
+
|
|
55
|
+
## Rules
|
|
56
|
+
|
|
57
|
+
1. Target **pane ids or names** from `list` — do not invent transports.
|
|
58
|
+
2. Keep the session + pane ID returned by `spawn`. Use names once `alias.observed` is true; retry observation before creating a duplicate.
|
|
59
|
+
3. Prefer `send` + `wait` over polling. Prefer `tail` over repeated `dump`.
|
|
60
|
+
4. Check `submitted` on `send`; `false` means the peer never got it.
|
|
61
|
+
5. Prefer `diff` / `checkpoint` over reading a worktree by hand.
|
|
62
|
+
6. Zellij terminal panes only — not IDE side-panel chats.
|
|
63
|
+
7. Same host as Zellij, or `ZSWARM_SSH` / `ZSWARM_SERVE`. Linux/macOS SSH is
|
|
64
|
+
the same user + `$TMPDIR`. Windows pane attach: `ZSWARM_SSH_MODE=interactive`
|
|
65
|
+
or `zswarm serve` next to Zellij. `file:` wasm stays on the box that owns
|
|
66
|
+
Zellij. Details: [references/remote.md](references/remote.md).
|
|
67
|
+
8. Writes refuse own pane (`self_target`) and exited panes (`pane_exited`).
|
|
68
|
+
Override with `allowSelf` / `force` only when you mean it.
|
|
69
|
+
9. Policy env vars can block writes (`policy_denied` names the env var).
|
|
70
|
+
10. `spawn` is executable + argv — no shell, so no pipes or `&&`.
|
|
71
|
+
11. Prefer `worktree` on `spawn` when peers should not share one dirty tree.
|
|
72
|
+
Tear down with `unworktree` after the pane is closed.
|
|
73
|
+
12. Crew barriers: `broadcast` + `signal` + `await`, not ad-hoc dumps.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Harness notes
|
|
2
|
+
|
|
3
|
+
Verified live against **codex**, **cursor**, **pi**, **opencode**, and **gemini** (`agy`).
|
|
4
|
+
|
|
5
|
+
- **Read** (`dump`, `tail`, `status`, `wait --for idle`) works on all five.
|
|
6
|
+
- **Send** lands on all five (`codex` auto-detects `submit=double-enter`, others `auto`). The `expect` guard works on all five.
|
|
7
|
+
- **Replies can take > 60s.** Budget timeouts; a quiet pane is rarely a failed send.
|
|
8
|
+
- **`wait --match` on a redrawing TUI is viewport-and-moment dependent.** Those apps
|
|
9
|
+
own the alternate screen (no scrollback; `--full` is identical). Prefer
|
|
10
|
+
`wait --for idle` or match text the harness keeps pinned.
|
|
11
|
+
- Re-test: `node scripts/harness-check.mjs <panes...>`
|
|
12
|
+
|
|
13
|
+
Reaching zswarm from a harness is separate from driving one:
|
|
14
|
+
|
|
15
|
+
- **4 of 5 have an MCP client** — they reach zswarm once session and interpreter
|
|
16
|
+
path are explicit in the server config.
|
|
17
|
+
- **1 ships no MCP client** — use `@zswarm/cli`, or the generic MCP bridge at
|
|
18
|
+
`packages/pi/extensions/zswarm-mcp.ts` (`ZSWARM_MCP_SERVERS`).
|
|
19
|
+
- **Being a target needs no integration.** All five are drivable with
|
|
20
|
+
`send` / `dump` / `tail` / `wait` / `status`.
|
|
21
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Ops
|
|
2
|
+
|
|
3
|
+
MCP: `zswarm({ op, ... })`. CLI: `zswarm <op>`. Same surface.
|
|
4
|
+
|
|
5
|
+
| op | Purpose |
|
|
6
|
+
|----|---------|
|
|
7
|
+
| `list` | Terminal panes (id, title, command, tab); `verbose` adds cwd/flags |
|
|
8
|
+
| `send` | Paste body + Enter (`to` = id / title / command). `from` labels `[zswarm from=…]` (default: `ZSWARM_FROM`, else the sending pane's title, else `swarm`). `submit`: `auto` (default; Codex panes default to `double-enter`) / `double-enter` / `none`. Result `submitted: true\|false\|"unverified"`. `expect` refuses unless the screen already shows that substring |
|
|
9
|
+
| `dump` | Full-screen read; capped at 8000 chars (tail) — expensive vs `tail` |
|
|
10
|
+
| `tail` | Incremental read since last cursor; `reset: true` returns the whole screen |
|
|
11
|
+
| `wait` | Block until quiet or `match`; returns `reason` + a 2000-char tail. Bus holds one pipe for the wait |
|
|
12
|
+
| `status` | busy / waiting / idle / exited / running / unknown, with tab names/IDs, tab summary and waiting evidence; `free[]` = idle ids. `sampleMs: 0` skips sampling. `sinceLast: true` skips the 400ms gap |
|
|
13
|
+
| `keys` | `expect` is checked before any input. Key specs (`keys: ["Ctrl c"]`) or literal `chars` (+ `enter`) |
|
|
14
|
+
| `interrupt` | `Esc`; `hard: true` sends `Ctrl c` |
|
|
15
|
+
| `spawn` | New pane (`newTab: true` for a fresh tab) with `command`, `cwd`, `name`, `direction`, `floating`; `tab` = tab name; `worktree` isolates on a branch |
|
|
16
|
+
| `close` | Close a pane |
|
|
17
|
+
| `rename` | Retitle a pane (`to` + `name`) or a tab (`tab` + `name`) |
|
|
18
|
+
| `focus` | Focus a pane; already-focused is a no-op success |
|
|
19
|
+
| `tabs` | List tabs with pane counts |
|
|
20
|
+
| `layout` | Dump the session layout as KDL |
|
|
21
|
+
| `stack` | Stack a comma list of panes (needs 2+) |
|
|
22
|
+
| `broadcast` | One body to many panes (`to` list, `tab`, or `all`; narrow with `group`) |
|
|
23
|
+
| `signal` | Post to a channel (`channel`, optional `payload`); `clear` resets |
|
|
24
|
+
| `signals` | List channels with cumulative counts |
|
|
25
|
+
| `await` | Block until a channel reaches `count` posts |
|
|
26
|
+
| `log` | Delivery log for send/broadcast/keys/interrupt/close |
|
|
27
|
+
| `worktrees` | List repo git worktrees, annotated with panes working in them |
|
|
28
|
+
| `unworktree` | Remove a worktree (`path` or `branch`; `worktree` aliases `branch`) |
|
|
29
|
+
| `diff` | What a peer changed in its worktree |
|
|
30
|
+
| `checkpoint` | Commit a peer worktree (`message`); clean tree is not an error |
|
|
31
|
+
| `sessions` | Live Zellij session names |
|
|
32
|
+
| `bus` | Event-bus status; `install: true` loads the plugin, `clear: true` forgets it |
|
|
33
|
+
| `serve` | Listen for remote zswarm (`--listen`). `--install` / `--clear` installs/removes a service (Windows logon task, Linux systemd user unit, macOS launchd job). MCP cannot listen; set `ZSWARM_SERVE` on the client. See [remote.md](remote.md) |
|
|
34
|
+
| `doctor` | Inspect-only route/SSH/serve/Zellij/bus diagnostics |
|
|
35
|
+
|
|
36
|
+
`submit=auto` retries Enter if the paste is still sitting in a TUI composer.
|
|
37
|
+
`submit=double-enter` always sends the extra Enter; `submit=none` skips the check.
|
|
38
|
+
Check `submitted` — a queued composer used to report success.
|
|
39
|
+
|
|
40
|
+
**Breaking:** `spawn`'s boolean `tab` is now `newTab`; `tab` is a tab **name**.
|
|
41
|
+
|
|
42
|
+
## expect
|
|
43
|
+
|
|
44
|
+
A pane that dropped back to a shell will **run** your message as a command.
|
|
45
|
+
Name something the screen must already show:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
zswarm({ op: "send", to: "reviewer", body: "…", expect: "Add a follow-up" })
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Failure is `expect_missing` and nothing is written.
|
|
52
|
+
|
|
53
|
+
## Event bus
|
|
54
|
+
|
|
55
|
+
`zswarm({ op: "bus", install: true })` once per Zellij session. After that
|
|
56
|
+
`list` and `status` read a pushed manifest (`source: "plugin"` or `"zellij"`).
|
|
57
|
+
Off until installed; any failure falls back silently — speed, not a dependency.
|
|
58
|
+
Do not pass `force` to recover from a quiet bus: that used to stack WASM copies.
|
|
59
|
+
Approve the existing pane's permission prompt, or `force` only when you mean to
|
|
60
|
+
close every bus plugin pane and load one replacement. Keep the floating pane
|
|
61
|
+
open; closing it unloads the bus.
|
|
62
|
+
|
|
63
|
+
The manifest has no pane command or cwd, so a bus-served `list` omits `command`,
|
|
64
|
+
and `list` with `verbose` / `status --to <command>` keep polling. `status`
|
|
65
|
+
sampling and `wait` use the plugin; `dump` / `tail` stay on the CLI.
|
|
66
|
+
|
|
67
|
+
## Reliable spawn and handoffs
|
|
68
|
+
|
|
69
|
+
Keep the returned session + pane ID. Spawn creates once and observes for up to
|
|
70
|
+
`observeMs` (default 3000), within one `timeoutMs` budget (default 30000).
|
|
71
|
+
`created` is the creation acknowledgment; `observed`, `exited`, `observation`,
|
|
72
|
+
`alias.observed`, and `ready` describe what was actually seen. `live` means
|
|
73
|
+
observed and not exited; it does not establish application readiness. An
|
|
74
|
+
observation timeout can accompany `ok: true`: retry reads before spawning again.
|
|
75
|
+
New-tab correlation stays within the returned tab and ambiguous layouts remain
|
|
76
|
+
unresolved. `newTab` + `name` also establishes the terminal alias when observed.
|
|
77
|
+
Pane lookups retry absence for 1000ms; `observeMs: 0` disables retries.
|
|
78
|
+
|
|
79
|
+
CLI: `send --body-file PATH` reads a local UTF-8 file before remote forwarding;
|
|
80
|
+
`--body-file -` reads stdin. Do not combine with `--body`/`--text`/a positional
|
|
81
|
+
body. Newlines are preserved. MCP uses `body`, never its protocol stdin. Paste
|
|
82
|
+
still uses argv, so reference a shared file for very large handoffs.
|
|
83
|
+
|
|
84
|
+
For menus: wait for a match and check `reason`, perform one
|
|
85
|
+
`keys --expect TEXT --key Enter`, then wait for the resulting state. `expect`
|
|
86
|
+
also applies to `chars` and `interrupt`. It is a fresh case-insensitive screen
|
|
87
|
+
check, not an atomic transaction. Serialize input per pane and avoid blind
|
|
88
|
+
retries. Waiting evidence is a screen heuristic, not authorization to approve.
|
|
89
|
+
|
|
90
|
+
Ordinary status uses bus changes without a sample gap by default; first/missing observations are unknown unless a prompt is visible. `sampleMs` explicitly selects two-sample observation (0 = metadata only). Bus change history is shared by the plugin instance. Read-only listings use a 500ms process cache; `fresh: true` bypasses it, and `ZSWARM_CACHE_TTL_MS=0` disables reuse. Writes/spawn settling use fresh metadata. Serve retains caches across client CLI calls.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Remote crews
|
|
2
|
+
|
|
3
|
+
Client (MCP or CLI) talks to Zellij on another machine. `file:` wasm stays on
|
|
4
|
+
the host that owns Zellij.
|
|
5
|
+
|
|
6
|
+
## Linux / macOS
|
|
7
|
+
|
|
8
|
+
Same user, same `$TMPDIR` — SSH is enough:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
ZSWARM_SSH=user@host zswarm list
|
|
12
|
+
ZSWARM_SSH=user@host zswarm send --to reviewer --body "please review"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If the remote Zellij uses a different socket dir than the SSH login's `$TMPDIR`:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
ZSWARM_SSH=user@host ZSWARM_TMP=auto zswarm list
|
|
19
|
+
# or set ZSWARM_TMP to that directory explicitly
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`auto` reads live `zellij --server` paths (`ps` on Unix). `serve` (below) is
|
|
23
|
+
optional when SSH already sees the sockets.
|
|
24
|
+
|
|
25
|
+
## Windows
|
|
26
|
+
|
|
27
|
+
OpenSSH is session 0; live Zellij is usually the desktop session. Named pipes
|
|
28
|
+
live there, so SSH + TEMP can **list sessions** but not attach panes.
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
ZSWARM_SSH=user@host ZSWARM_TMP=auto → sessions
|
|
32
|
+
ZSWARM_SSH=user@host ZSWARM_SSH_MODE=interactive → list/send
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`interactive` is Windows-only: `schtasks /IT` in the desktop session. Discovers
|
|
36
|
+
TEMP unless `ZSWARM_TMP` is set. `ZSWARM_REMOTE_BIN=zellij.exe` if `zellij` is
|
|
37
|
+
not on the remote PATH. `ZSWARM_REMOTE_SHELL=cmd|sh` overrides quoting.
|
|
38
|
+
|
|
39
|
+
## Serve (any OS)
|
|
40
|
+
|
|
41
|
+
Run zswarm **next to Zellij**; the client talks over a tunnel. Use this when
|
|
42
|
+
SSH is a different session than Zellij, or when MCP should not spawn `ssh` per
|
|
43
|
+
op.
|
|
44
|
+
|
|
45
|
+
**Native Windows + Tailscale:** the default recipe is
|
|
46
|
+
[`zswarm serve --install`](../../../docs/tailscale.md) on the already-logged-in
|
|
47
|
+
desktop (Interactive/Limited logon task, loopback + token), then
|
|
48
|
+
`--serve 'ssh://user@host?servePort=9419'` from the controller (OpenSSH over
|
|
49
|
+
the tailnet — not Tailscale SSH). That guide also covers an **optional**
|
|
50
|
+
verified Tailscale-IP bind for direct TCP, and **private raw TCP Tailscale
|
|
51
|
+
Serve** (`tailscale serve --tcp=…` over a loopback backend). Readiness, token
|
|
52
|
+
storage, bus approval, and login/reboot limits are documented there.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# On the host, in the session that owns Zellij (default loopback):
|
|
56
|
+
ZSWARM_SERVE_TOKEN=secret zswarm serve --listen 127.0.0.1:9419
|
|
57
|
+
# Token is required on loopback too: another local OS user can connect to 127.0.0.1.
|
|
58
|
+
# Optional: bind a verified local Tailscale IP (see docs/tailscale.md):
|
|
59
|
+
# zswarm serve --listen "$(tailscale ip -4):9419"
|
|
60
|
+
# Windows verified logon task: zswarm serve --install --listen 127.0.0.1:9419 --session crew --timeout-ms 30000
|
|
61
|
+
# (Start-ScheduledTask is asynchronous; install waits for authenticated hello + host visibility.)
|
|
62
|
+
# Linux/macOS service (systemd user unit + linger / launchd job; token in ~/.zswarm/serve/):
|
|
63
|
+
# zswarm serve --install --listen 127.0.0.1:9419 --session crew
|
|
64
|
+
# Keep the session itself running from a layout: zswarm crew up crew --layout crew.kdl
|
|
65
|
+
|
|
66
|
+
# On the client (concise default is ssh://; token already in this process):
|
|
67
|
+
zswarm --serve 'ssh://user@host?servePort=9419' status --session crew
|
|
68
|
+
# Or a manual LocalForward to an existing endpoint:
|
|
69
|
+
ssh -fN -L 9419:127.0.0.1:9419 user@host
|
|
70
|
+
ZSWARM_SERVE=127.0.0.1:9419 ZSWARM_SERVE_TOKEN=secret zswarm list
|
|
71
|
+
# Direct endpoint when the host bound its Tailscale IP:
|
|
72
|
+
# zswarm --serve '<host-tailscale-ip>:9419' status --session crew
|
|
73
|
+
# Private TCP Tailscale Serve (frontend port; backend remains 127.0.0.1:9419):
|
|
74
|
+
# zswarm --serve 'tcp://crew-host:19419' status --session crew
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`ZSWARM_SSH` only forwards Zellij. Git worktree ops (`worktrees`, `unworktree`,
|
|
78
|
+
`diff`, `checkpoint`, `spawn --worktree`) and barrier ops (`signal`, `signals`,
|
|
79
|
+
`await`) are refused over plain SSH without serve (`ssh_git_unsupported`) — use
|
|
80
|
+
`serve` on the host that owns the repo and session if those ops should run there.
|
|
81
|
+
|
|
82
|
+
Direct SSH cannot `bus --install` or `bus --clear` (`bus_remote_unsupported`):
|
|
83
|
+
the plugin `file:` URL and the install marker belong on the Zellij host. Run
|
|
84
|
+
those on that host, or through `serve` over a tunnel. A remote `bus` report
|
|
85
|
+
still explains that the bus is unavailable. `--local` overrides inherited SSH
|
|
86
|
+
when this machine owns Zellij.
|
|
87
|
+
|
|
88
|
+
MCP: set `ZSWARM_SERVE` in the MCP server env. Pass `session` explicitly or
|
|
89
|
+
configure `ZSWARM_SESSION` on the host running `serve`. Do **not** call
|
|
90
|
+
`op: "serve"` with `listen` — that is CLI-only. `serve` with `install` or `clear`
|
|
91
|
+
works via MCP. Other ops forward.
|
|
92
|
+
A client with `ZSWARM_SERVE` never sends a local wasm path across the tunnel.
|
|
93
|
+
|
|
94
|
+
## Invocation routing
|
|
95
|
+
|
|
96
|
+
`--local` clears SSH, serve, and remote IPC for this call only. It preserves the
|
|
97
|
+
parent environment and inherited session selection; use `--session` explicitly
|
|
98
|
+
when switching hosts. `--ssh user@host` overrides the inherited destination and
|
|
99
|
+
serve for this call. Passing both flags is a usage error.
|
|
100
|
+
|
|
101
|
+
Normal responses carry `context`: transport, host, resolved session, and the
|
|
102
|
+
origin of those settings. Serve responses also identify the server's context
|
|
103
|
+
when supported, because the endpoint may be a local tunnel. Interactive CLI
|
|
104
|
+
routing notices go to stderr; stdout stays JSON. Scope remote env vars to the
|
|
105
|
+
remote launcher and give local crew wrappers fixed `--local --session` flags.
|
|
106
|
+
Configure each MCP server's environment explicitly.
|
|
107
|
+
|
|
108
|
+
Dedicated crew Zellij configs can disable startup distractions with
|
|
109
|
+
`show_startup_tips false` and `show_release_notes false`. Correct TERM on the
|
|
110
|
+
host that starts workers. Terminal screen reads do not prove the presence or
|
|
111
|
+
absence of all Zellij overlays; inspect held exit output before recovery.
|
|
112
|
+
|
|
113
|
+
For frequent status polling, prefer serve beside Zellij with an SSH tunnel. Use `--serve 127.0.0.1:9419 --session crew` (MCP: `serveAddress`) to select an already-open endpoint, or `--serve 'ssh://user@host?servePort=9419'` for a process-owned LocalForward that probes hello before ops. Keep `ZSWARM_SERVE_TOKEN` configured on both sides. Direct SSH status reports its bus limitation and a serve recommendation. `host:port` does not start a tunnel; `ssh://` starts only the local SSH child, never remote serve. Native Windows recipe: [Tailscale crew](../../../docs/tailscale.md). See also [performance guidance](../../../docs/performance.md).
|
|
114
|
+
|
|
115
|
+
## Agent hosts (CLI subcommands)
|
|
116
|
+
|
|
117
|
+
`zswarm host install|doctor`, `zswarm crew up|status|down`, `zswarm relay`
|
|
118
|
+
(hand a remote pane work, get a message in a local pane when a done line
|
|
119
|
+
appears; `zswarm relay --wait ID` is the pull path) and `zswarm slot`
|
|
120
|
+
(shared build slots) set up and keep a remote crew running. See
|
|
121
|
+
docs/hosts.md in the zswarm repo.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Workflows
|
|
2
|
+
|
|
3
|
+
## Barriers
|
|
4
|
+
|
|
5
|
+
Broadcast a task, each peer signals when done, await N:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
zswarm({ op: "signal", channel: "done", clear: true })
|
|
9
|
+
zswarm({ op: "broadcast", body: "finish your slice, then signal done", all: true, group: "claude" })
|
|
10
|
+
zswarm({ op: "await", channel: "done", count: 3, timeoutMs: 600000 })
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`status` to see who is free; `tail` (not `dump`) to poll what a peer printed.
|
|
14
|
+
State under `ZSWARM_STATE_DIR` (default `~/.zswarm`): `log.jsonl`, `signals.json`,
|
|
15
|
+
`cursors.json`. `ZSWARM_LOG=0` disables the delivery log; past 4MB it is trimmed to
|
|
16
|
+
its last 512KB, which is all `log` reads.
|
|
17
|
+
|
|
18
|
+
`broadcast` skips plugin / exited / own pane (never errors on those);
|
|
19
|
+
`force` / `allowSelf` override. Empty selection → `no_targets`.
|
|
20
|
+
|
|
21
|
+
## Worktrees
|
|
22
|
+
|
|
23
|
+
`spawn` with `worktree=<branch>` gives the peer its own git worktree + branch,
|
|
24
|
+
setting the pane's working directory to the worktree path. `cwd` selects the base
|
|
25
|
+
repository the worktree is created from (defaulting to the current working
|
|
26
|
+
directory). Optional `worktreeRoot`, `baseRef`. Defaults: worktrees under
|
|
27
|
+
`<repo>-worktrees` beside the repo (`ZSWARM_WORKTREE_ROOT`). Existing worktree
|
|
28
|
+
at the target path is reused. Git binary: `ZSWARM_GIT_BIN`.
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
zswarm({ op: "spawn", command: "claude", worktree: "review-auth", cwd: "/path/to/repo", name: "reviewer" })
|
|
32
|
+
zswarm({ op: "worktrees", cwd: "/path/to/repo" })
|
|
33
|
+
zswarm({ op: "unworktree", branch: "review-auth", cwd: "/path/to/repo" })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`unworktree` refuses the main worktree (`worktree_is_main`), a worktree still
|
|
37
|
+
used by a pane (`worktree_busy`), or one with uncommitted changes
|
|
38
|
+
(`worktree_dirty`). `force: true` overrides busy/dirty only.
|
|
39
|
+
|
|
40
|
+
Note on remote SSH: Over plain SSH (`ZSWARM_SSH` without `serve`), git worktree ops
|
|
41
|
+
and signal/await barrier ops are refused with `ssh_git_unsupported`. Run `zswarm serve`
|
|
42
|
+
on the host that owns the session and connect via `ZSWARM_SERVE`.
|
|
43
|
+
|
|
44
|
+
## Review loop
|
|
45
|
+
|
|
46
|
+
Isolate a peer, name it, task it, save its work, tear down:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
zswarm({ op: "spawn", command: "claude", worktree: "review-auth", name: "reviewer" })
|
|
50
|
+
zswarm({ op: "rename", to: "terminal_11", name: "reviewer" })
|
|
51
|
+
zswarm({ op: "send", to: "reviewer", body: "review the auth changes" })
|
|
52
|
+
zswarm({ op: "wait", to: "reviewer", for: "idle" })
|
|
53
|
+
zswarm({ op: "diff", branch: "review-auth", cwd: "/path/to/repo" })
|
|
54
|
+
zswarm({ op: "checkpoint", branch: "review-auth", cwd: "/path/to/repo", message: "review checkpoint" })
|
|
55
|
+
zswarm({ op: "close", to: "reviewer" })
|
|
56
|
+
zswarm({ op: "unworktree", branch: "review-auth", cwd: "/path/to/repo" })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Prefer `diff` / `checkpoint` over reading the worktree by hand. Clean
|
|
60
|
+
`checkpoint` returns `committed: false`, `nothingToCommit: true` — not an error.
|
|
61
|
+
|
|
62
|
+
## Waiting
|
|
63
|
+
|
|
64
|
+
`wait` polls the pane screen instead of you re-dumping it.
|
|
65
|
+
|
|
66
|
+
- `for: "idle"` (default) — screen unchanged for `idleMs` (2000).
|
|
67
|
+
- `match: "…"` — substring, or `regex: true`; sets `for` to `match`.
|
|
68
|
+
- `for: "either"` — whichever lands first. `timeoutMs` defaults to 60000.
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
zswarm({ op: "send", to: "reviewer", body: "run the tests" })
|
|
72
|
+
zswarm({ op: "wait", to: "reviewer", for: "either", match: "FAIL", idleMs: 4000 })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Prefer `tail` for cheap incremental polls; reserve `dump` for a one-shot full
|
|
76
|
+
screen (or `tail` with `reset: true`).
|