Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLI
Assigneesstack72

Relationships

#979 Worker fleets phase 3 PR2: swamp worker daemon enable

Opened by stack72 · 7/5/2026· Shipped 7/5/2026

Summary

Add `swamp worker daemon enable/disable/status` commands that generate systemd/launchd units for the worker process, mirroring the existing `swamp serve daemon` pattern.

Today, running a worker as a persistent service requires hand-writing a systemd unit or launchd plist. After this PR, `swamp worker daemon enable` generates the unit with correct signal handling (`KillSignal=SIGTERM` triggers drain from PR1), env-file configuration, and a generous `TimeoutStopSec` so the drain completes before the service manager force-kills.

Design reference: `worker-fleets.md` section "Worker lifecycle" → "`swamp worker daemon enable`".

Existing pattern

`swamp serve daemon enable` (src/cli/commands/serve.ts:252-395) provides the template:

  • `ServiceScheduler` interface (`src/domain/serve/service_scheduler.ts`)
  • `LaunchdServiceScheduler` (`src/infrastructure/daemon/launchd_service_scheduler.ts`)
  • `SystemdServiceScheduler` (`src/infrastructure/daemon/systemd_service_scheduler.ts`)
  • `createServiceScheduler` factory (`src/infrastructure/daemon/service_scheduler_factory.ts`)
  • Render output (`src/presentation/output/serve_daemon_output.ts`)
  • Platform detection: macOS → launchd, Linux → systemd (with `systemctl` check)

The worker daemon needs the same infrastructure but for `swamp worker connect` instead of `swamp serve`.

Changes

1. Worker daemon CLI commands

New file: `src/cli/commands/worker_daemon.ts`

Three subcommands under `swamp worker daemon`:

`enable`: ``` swamp worker daemon enable --token [--label k=v ...] [--cache-dir ] [--max-dispatches ] [--idle-timeout ] ```

Generates and installs a systemd unit / launchd plist that runs `swamp worker connect` with the specified flags. Configuration goes in an env file (same pattern as serve daemon) so secrets don't appear in the unit file.

Key unit settings:

  • `KillSignal=SIGTERM` — triggers graceful drain (PR1)
  • `TimeoutStopSec=300` (5 minutes) — generous window for in-flight work to complete before force-kill
  • `Restart=on-failure` — restart on crashes, not on policy-complete exits (exit 0 from drain/max-dispatches/idle-timeout)
  • `Environment=` or `EnvironmentFile=` for `SWAMP_ORCHESTRATOR_URL`, `SWAMP_WORKER_TOKEN`, `SWAMP_WORKER_LABELS`, `SWAMP_WORKER_CACHE_DIR`, `SWAMP_WORKER_MAX_DISPATCHES`, `SWAMP_WORKER_IDLE_TIMEOUT`

`disable`: ``` swamp worker daemon disable ``` Stops and removes the worker service unit.

`status`: ``` swamp worker daemon status ``` Shows whether the worker daemon is installed, running, and its configuration.

Register in: `src/cli/commands/worker.ts` as a subcommand group.

2. Worker service schedulers

The existing `ServiceScheduler` interface and infrastructure (`LaunchdServiceScheduler`, `SystemdServiceScheduler`, `createServiceScheduler`) are currently coupled to serve-specific configuration. Two approaches:

Option A (preferred): Generalize the existing schedulers to accept a service name and command args, so both serve and worker use the same classes. The service name differentiates the unit files (`swamp-serve.service` vs `swamp-worker.service`).

Option B: Create parallel worker-specific schedulers. More code duplication but zero risk of regressing serve daemon.

The implementer should evaluate which approach is cleaner given the current code. The serve daemon pattern at `serve.ts:252-395` shows how flags are collected into `collectServeExtraArgs` and passed to the scheduler — the worker equivalent collects worker connect args.

3. Render output

New file or extend: `src/presentation/output/worker_daemon_output.ts` (or extend `serve_daemon_output.ts` if generalizing)

Render enable/disable/status output in both log and json modes, matching the serve daemon output pattern.

Invariants

  1. Env file for secrets. The token must go in the env file, not in the unit file's `ExecStart` args — unit files are world-readable on many systems.
  2. `Restart=on-failure` not `Restart=always`. Exit 0 from drain/max-dispatches/idle-timeout means the worker is done — the service manager should not restart it. Only crashes (non-zero exit) trigger restart.
  3. `TimeoutStopSec` must be generous. A worker draining a long-running step needs time. 300s (5 minutes) is a safe default; the design doc says "generous".
  4. Cache dir must be stable. The enable command should default `--cache-dir` to a well-known path (e.g., `/var/lib/swamp-worker`) so the machine identity survives restarts. Don't use a temp dir for daemon mode.

Tests required

  • Unit tests for the worker daemon commands (enable writes the correct unit/plist, disable removes it, status reads it)
  • Env file contains the token and labels, not the unit file's ExecStart
  • `KillSignal=SIGTERM` and `TimeoutStopSec=300` present in generated unit
  • `Restart=on-failure` in generated unit
  • Platform detection (macOS → launchd, Linux → systemd)

Acceptance criteria

  1. `swamp worker daemon enable wss://orch:9090 --token tok.secret --label tier=ci --cache-dir /var/lib/swamp-worker` generates and installs a systemd unit (Linux) or launchd plist (macOS)
  2. `systemctl start swamp-worker` (or equivalent) starts the worker, which enrolls with the orchestrator
  3. `systemctl stop swamp-worker` sends SIGTERM, worker drains, exits 0, service reports "inactive (dead)" not "failed"
  4. `swamp worker daemon status` shows the service state
  5. `swamp worker daemon disable` removes the unit
  6. All existing tests pass
  7. `deno check`, `deno lint`, `deno fmt`, `deno run test`, `deno run compile` all pass

Repo conventions

  • New `.ts` files need the AGPLv3 header
  • TypeScript strict, no `any`, named exports only
  • Unit tests next to source
  • Use the `github-pr` skill for commits/PRs
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 4 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/5/2026, 9:22:26 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack727/5/2026, 7:35:05 PM

Sign in to post a ripple.