c8ctl-plugin-nano 1.64.0 → 1.65.1
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 +70 -0
- package/c8ctl-plugin.js +507 -12
- package/package.json +8 -8
- package/supervisor.dist.js +8 -8
package/README.md
CHANGED
|
@@ -1060,6 +1060,8 @@ c8ctl nano supervisor add reviewer # add + spawn a worker (forw
|
|
|
1060
1060
|
c8ctl nano supervisor add reviewer --name reviewer-2 # a SECOND reviewer, named so it stays distinct
|
|
1061
1061
|
c8ctl nano supervisor add reviewer --instances 3 # add 3 distinct auto-named reviewers in one call
|
|
1062
1062
|
c8ctl nano supervisor restart reviewer # by worker id or profile name
|
|
1063
|
+
c8ctl nano supervisor reload # adopt new code fleet-wide (rolling drain+respawn, zero downtime)
|
|
1064
|
+
c8ctl nano supervisor reload reviewer # reload just one worker/profile
|
|
1063
1065
|
c8ctl nano supervisor remove coder # stop + drop a worker (also: `all`)
|
|
1064
1066
|
c8ctl nano supervisor logs reviewer --follow # tail a worker's log (or the daemon's)
|
|
1065
1067
|
c8ctl nano supervisor stop # stop the daemon and every worker
|
|
@@ -1109,6 +1111,73 @@ How it works and where things live:
|
|
|
1109
1111
|
- Stopping is SIGTERM → grace → SIGKILL, per worker and for the daemon; `stop`
|
|
1110
1112
|
always clears `supervisor.json` so a stale marker never wedges a future start.
|
|
1111
1113
|
|
|
1114
|
+
### Hot code reload: `supervisor reload` / `workforce reload`
|
|
1115
|
+
|
|
1116
|
+
When you update the plugin on a machine that's already running a fleet
|
|
1117
|
+
(`c8ctl nano update`, or otherwise replacing the installed `c8ctl-plugin-nano`),
|
|
1118
|
+
`supervisor reload` adopts the new code **without stopping the fleet**:
|
|
1119
|
+
|
|
1120
|
+
```bash
|
|
1121
|
+
c8ctl nano update # pull the new harness onto disk
|
|
1122
|
+
c8ctl nano supervisor reload # roll it into the running fleet, zero downtime
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
- Each supervised worker is a **separate `nano work` process** that reads the
|
|
1126
|
+
plugin from disk when it starts, so the daemon adopts new code by **rolling
|
|
1127
|
+
through the workers one at a time** — gracefully draining each (the same
|
|
1128
|
+
`SIGUSR2` quiesce as `stop`: it stops leasing new jobs, finishes the ones in
|
|
1129
|
+
flight, and exits) and respawning it, which re-reads the updated
|
|
1130
|
+
`c8ctl-plugin.js`, its sidecars, and `supervisor.dist.js`. Because only one
|
|
1131
|
+
worker is down at a time, the rest of the fleet keeps serving — **zero fleet
|
|
1132
|
+
downtime**. To keep it genuinely one-at-a-time, after respawning a worker the
|
|
1133
|
+
daemon **waits for the replacement to report ready** (its activation loop is up
|
|
1134
|
+
and leasing) before draining the next one — a bare spawn/PID is not readiness,
|
|
1135
|
+
so on the normal readiness path a slow replacement can never leave two workers
|
|
1136
|
+
down at once. That wait is
|
|
1137
|
+
**bounded** (`NANO_SUPERVISOR_RELOAD_READY_TIMEOUT_MS`, default 30s): a
|
|
1138
|
+
never-ready replacement can't wedge the roll — the daemon advances anyway. That
|
|
1139
|
+
timeout fallback is the one exception to the guarantee above: when a
|
|
1140
|
+
replacement never reports ready the daemon drains the next worker while the
|
|
1141
|
+
previous one is still unready, so **two (or more) workers can be temporarily
|
|
1142
|
+
unavailable** until the slow replacement catches up. This
|
|
1143
|
+
is a *fleet-level* guarantee: a worker is drained **before** its replacement
|
|
1144
|
+
spawns, so a **single-worker fleet** (or a job type served by only one worker)
|
|
1145
|
+
does lose that capacity for the drain+boot window. Run more than
|
|
1146
|
+
one worker for a type if you need it served continuously across a reload.
|
|
1147
|
+
- It **never kills in-flight work**: a reload waits indefinitely for each
|
|
1148
|
+
worker's jobs to finish (adopting new code is never worth losing a running
|
|
1149
|
+
job). The command **streams progress** and **Ctrl-C detaches** — the daemon
|
|
1150
|
+
keeps rolling in the background (rerun `supervisor status` to check). A reload
|
|
1151
|
+
is refused while another is already in progress.
|
|
1152
|
+
- It **stops the roll on a failed reload** (a canary): if a worker's replacement
|
|
1153
|
+
crashes, fails to spawn, or is swapped out from under the roll — i.e. it has no
|
|
1154
|
+
confirmed serving child — the daemon aborts the remaining pass rather than drain
|
|
1155
|
+
the next worker on top of that gap (which would both break one-at-a-time and
|
|
1156
|
+
risk rolling a broken replacement across the whole fleet). The remaining workers
|
|
1157
|
+
are reported **skipped** and the terminal frame reports a partial failure
|
|
1158
|
+
(`ok:false`), so automation sees it. A readiness *timeout* on a still-live
|
|
1159
|
+
replacement is **not** a failure — it counts as reloaded and the roll continues.
|
|
1160
|
+
- `reload [target]` defaults to the whole fleet; pass a worker id or profile to
|
|
1161
|
+
reload just those. `workforce reload` rolls only the workers a manifest owns.
|
|
1162
|
+
- **Not supported on Windows.** The graceful drain relies on `SIGUSR2` to quiesce
|
|
1163
|
+
each worker, which Windows cannot deliver (Node maps a non-zero signal there to
|
|
1164
|
+
a forceful, SIGKILL-like termination, so the child's drain handler never fires).
|
|
1165
|
+
The daemon therefore **rejects `supervisor reload` / `workforce reload` on
|
|
1166
|
+
Windows** rather than hang or hard-kill in-flight work — use
|
|
1167
|
+
`supervisor restart <target>`, or a full `supervisor stop` + `start`, to adopt
|
|
1168
|
+
new code there.
|
|
1169
|
+
- **Scope — workers, not the daemon.** A reload adopts all **worker-side** code
|
|
1170
|
+
(job running, agent instances, git/container provisioning, the agentic
|
|
1171
|
+
connection, the Effect runtime workers load — the bulk of the harness). The
|
|
1172
|
+
supervisor **daemon** keeps running the code it started with: its workers are
|
|
1173
|
+
its children and watch its pid, so re-exec'ing the daemon would take the fleet
|
|
1174
|
+
down with it. To adopt new **supervisor** code, do a full restart
|
|
1175
|
+
(`c8ctl nano supervisor stop && c8ctl nano supervisor start`) — a rare event,
|
|
1176
|
+
since the daemon is a thin process manager. `supervisor status` shows an
|
|
1177
|
+
`on disk:` line flagging "update available" whenever the code on disk has
|
|
1178
|
+
advanced past the running daemon, so you know when a reload (or restart) is
|
|
1179
|
+
worthwhile.
|
|
1180
|
+
|
|
1112
1181
|
### Surviving SSH logout: `supervisor install` / `uninstall`
|
|
1113
1182
|
|
|
1114
1183
|
On **macOS**, a supervisor started over SSH is bound to your SSH login session's
|
|
@@ -1216,6 +1285,7 @@ c8ctl nano workforce list # print the manifest (+ --json)
|
|
|
1216
1285
|
c8ctl nano workforce start # ensure the daemon is up, then reconcile
|
|
1217
1286
|
c8ctl nano workforce status # desired vs actual, per worker (+ --json)
|
|
1218
1287
|
c8ctl nano workforce stop # remove this manifest's workers (+ stop an empty daemon)
|
|
1288
|
+
c8ctl nano workforce reload # hot-adopt new code into this manifest's workers (rolling, zero downtime)
|
|
1219
1289
|
c8ctl nano workforce remove qwen # drop an entry ("all" clears the manifest)
|
|
1220
1290
|
```
|
|
1221
1291
|
|