diff --git a/dist/setup-agents-task-runner.skill b/dist/setup-agents-task-runner.skill new file mode 100644 index 0000000..7c79f07 Binary files /dev/null and b/dist/setup-agents-task-runner.skill differ diff --git a/skills/setup-agents-task-runner/README.md b/skills/setup-agents-task-runner/README.md new file mode 100644 index 0000000..84c0441 --- /dev/null +++ b/skills/setup-agents-task-runner/README.md @@ -0,0 +1,16 @@ +# setup-agents-task-runner + +L2 installer skill for the **standing-duty stack** — turns `agents-task-runner` + `watchdog` + +`appeals-inbox` into platform-native OS services (systemd / launchd / winsw): no node window, +OS-supervised autostart + crash-restart, run-as-user, hard deploy-boundary. + +- **Design:** `concepts/poller-standing-duty` (fork 1), OpeItcLoc03/common. +- **Service templates:** `OpeItcLoc03/common @ lib/agents-task-runner/service/`. +- **Factory module:** `agents-task-runner` in `~/.factory/factory.yaml`. + +Installs **disarmed** — scope is runtime config (`~/.config/projects-mcp/poller-scope.json`); arming a +project for autonomous spawn is a separate operator step via the appeals-inbox pult. Cross-platform. +Confirmation gates before every mutating phase (copies a deploy tree, fetches `winsw.exe` +pinned+SHA256-verified, installs OS services). + +See `SKILL.md` for the full procedure. diff --git a/skills/setup-agents-task-runner/SKILL.md b/skills/setup-agents-task-runner/SKILL.md new file mode 100644 index 0000000..e8ad4b5 --- /dev/null +++ b/skills/setup-agents-task-runner/SKILL.md @@ -0,0 +1,252 @@ +--- +name: setup-agents-task-runner +version: 0.1.0 +description: Installs the standing-duty stack (agents-task-runner + watchdog + appeals-inbox) as platform-native OS services — systemd user units on Linux, launchd LaunchAgents on macOS, winsw-wrapped services on Windows. No node window on any OS; OS-supervised autostart + crash-restart. Fetches winsw (pinned + SHA256-verified, not vendored). Installs DISARMED — scope is runtime config (poller-scope.json), arming is a separate operator step via the appeals-inbox pult. Use when the user says "install agents-task-runner service", "set up the standing-duty service", "deploy the poller as a service", "настрой службу раннера", "поставь дежурный стек как службу", "agents-task-runner службой", or when migrating off the old start-worker.ps1 Scheduled Task. Cross-platform — Windows / Linux / macOS. Installs OS services, fetches a binary, writes a scope file; pauses for confirmation before every mutating phase. This is the L2 installer for the `agents-task-runner` factory module. +--- + +# setup-agents-task-runner + +> One-time L2 installer that turns the standing-duty stack into platform-native OS services with a +> hard deploy-boundary: the service runs from a factory-install copy, the dev tree +> `.common/lib/agents-task-runner` stays editable, and editing the dev tree does NOT hot-patch the +> running service. Stops at confirmation gates — it installs OS services, fetches `winsw.exe`, and +> writes a runtime scope file. + +Design: `concepts/poller-standing-duty` (fork 1, OpeItcLoc03/common). Service templates live in +`OpeItcLoc03/common @ lib/agents-task-runner/service/` (`README.md` is the launch-recipe SSOT). +This skill is the `agents-task-runner` module declared in `~/.factory/factory.yaml`. + +## The three services + +`mongo` + `reconciler` stay in docker (own restart policy). This skill installs only the **host** +node processes (LocalSpawnAdapter spawns the host `claude`, which docker can't): + +| service id | script | port | role | +|---|---|---|---| +| `agents-task-runner` | `task-runner/server.js` | 3000 | claim + spawn | +| `agents-task-runner-watchdog` | `watchdog/watchdog.js` | — | hang-backstop + board hygiene | +| `agents-task-runner-appeals-inbox` | `dist/index.js` | 4317 | HITL pult + arming control | + +**Two-level supervision:** OS supervisor = crash/exit restart (primary); watchdog = alive-but-hung +backstop + board hygiene. Both kept — different failure modes, not duplicates. + +## When to use + +- User explicitly asks to install / set up / deploy the agents-task-runner (or "standing-duty") service. +- Migrating off the legacy `start-worker.ps1` Scheduled Task (the live-patch-prone launcher this replaces). +- New machine in the fleet that should run standing duty. + +## Out of scope + +- **Arming / going-live.** This skill installs the stack **disarmed**. Arming a project for autonomous + spawn is a runtime operator step via the appeals-inbox pult (writes `poller-scope.json`). Never arm + from this skill. +- Editing runner / watchdog / appeals-inbox source — that's dev-tree work in `OpeItcLoc03/common`. +- Building / registering `projects-meta-mcp` (that's `setup-projects-meta`) — this skill *uses* its + `dist/tasks-cli.js`. +- docker `mongo` + `reconciler` bring-up (`docker compose -f docker-compose.yml -f docker-compose.host.yml up -d`). +- Pushing any repo. + +## Hard rule: don't auto-mutate + +The procedure copies a deploy tree, fetches and runs a binary, installs OS services, and writes a +scope file. **Pause for explicit confirmation between Phase 1 (discovery, read-only) and Phase 2 +(plan), and again before Phase 3+ (writes).** A trigger phrase authorizes discovery only. + +Two never-do guardrails: +- **Never carry `POLLER_PROJECTS` or `DRY_RUN`** into any unit — scope is runtime config now. Their + presence is the exact anti-pattern this deploy removes. +- **Never overwrite an existing *armed* `poller-scope.json`.** If it exists, leave it. Only create a + disarmed `{"armed":[]}` when absent. + +## Procedure + +### Phase 0 — Environment sanity (read-only) + +- Node ≥ 22 on PATH (`node --version`); capture the absolute node binary → `{{NODE_BIN}}`. +- Dev tree present: `~/projects/.common/lib/agents-task-runner/` (source of `service/` templates + + the runner/watchdog) and `~/projects/.common/lib/appeals-inbox/`. +- `projects-meta-mcp` built: `~/projects/.common/lib/projects-meta-mcp/dist/tasks-cli.js` exists + (→ `{{TASKS_BIN}}`). If missing → run `setup-projects-meta` first; stop. +- Resolve `{{HOME}}`, `{{USER}}`, `{{PROJECTS_ROOT}}` (`~/projects`). +- Detect OS → systemd (Linux) / launchd (macOS) / winsw (Windows). + +### Phase 1 — Discovery (read-only) + +Report "found / absent" for each; never echo secrets: + +- **Install dirs.** Default `{{INSTALL_DIR}}` / `{{APPEALS_DIR}}` per OS (Phase 2 table). Note if they + already exist (→ redeploy, not first install). +- **Existing services.** + - Linux: `systemctl --user list-unit-files 'agents-task-runner*'` + - macOS: `ls ~/Library/LaunchAgents/site.kzntsv.agents-task-runner*` + - Windows: `sc.exe query agents-task-runner*` (or `Get-Service agents-task-runner*`) +- **Legacy launcher.** Windows Scheduled Task `AgentsTaskRunnerWorker` (the `start-worker.ps1` task) — + flag it for teardown in Phase 2 (it must not coexist with the service — two task-runners = double-claim). +- **Scope file.** `~/.config/projects-mcp/poller-scope.json` — present? armed (non-empty `armed[]`)? If + armed, record and DO NOT touch. +- **winsw pin (Windows only).** Read `service/winsw/WINSW-PIN.md` — is `expected SHA256` filled (not the + `` placeholder)? If placeholder → Phase 2 must STOP and ask the operator to fill it. + +### Phase 2 — Plan + confirm + +Present one block. Default install dirs: + +| OS | `{{INSTALL_DIR}}` | `{{APPEALS_DIR}}` | service mechanism | +|---|---|---|---| +| Linux | `~/.local/share/agents-task-runner` | `~/.local/share/appeals-inbox` | systemd `--user` | +| macOS | `~/Library/Application Support/agents-task-runner` | `~/Library/Application Support/appeals-inbox` | launchd LaunchAgents | +| Windows | `%LOCALAPPDATA%\agents-task-runner` | `%LOCALAPPDATA%\appeals-inbox` | winsw | + +``` +OS / mechanism: +Install dirs: + () +Services: agents-task-runner, -watchdog, -appeals-inbox (run-as-user: , NOT root) +Legacy teardown: +Scope file: )> +winsw (Win only): fetch v2.12.0 WinSW-x64.exe, verify SHA256= +Run-as password: 's password (run-as-user requirement)> +Backups: existing unit/config files → .bak- +``` + +Wait for explicit "ok / go / поехали". State plainly: **this installs disarmed; nothing spawns until +you arm a project via the pult.** + +### Phase 3 — Backup + +Copy any existing unit / plist / winsw config that will be overwritten to `.bak-YYYYMMDD-HHMMSS`. +Deploy copies need no backup (git is the backup). + +### Phase 4 — Deploy copy (the boundary) + +Sync the dev tree into the install dirs — the service runs from here, NOT the dev tree. + +```bash +# runner (+ watchdog, which lives inside it) +rsync -a --delete --exclude node_modules ~/projects/.common/lib/agents-task-runner/ "$INSTALL_DIR"/ # or robocopy /MIR on Windows +( cd "$INSTALL_DIR" && npm ci --omit=dev ) + +# appeals-inbox (build dist) +rsync -a --delete --exclude node_modules ~/projects/.common/lib/appeals-inbox/ "$APPEALS_DIR"/ +( cd "$APPEALS_DIR" && npm ci && npm run build ) # produces dist/index.js +``` + +Windows: use `robocopy /MIR /XD node_modules` instead of rsync. Verify +`"$INSTALL_DIR"/task-runner/server.js`, `"$INSTALL_DIR"/watchdog/watchdog.js`, and +`"$APPEALS_DIR"/dist/index.js` exist before proceeding. + +### Phase 5 — Render templates + +For each unit in `service//`, substitute the placeholders +(`{{NODE_BIN}}`, `{{INSTALL_DIR}}`, `{{APPEALS_DIR}}`, `{{HOME}}`, `{{USER}}`, `{{TASKS_BIN}}`, +`{{PROJECTS_ROOT}}`; Windows also `{{WINSW_USER_PASSWORD}}`) → rendered files. Create the log dirs the +units reference (`~/.local/state/agents-task-runner/`, `~/Library/Logs/agents-task-runner/`, or +`%LOCALAPPDATA%\agents-task-runner\logs`). Confirm no `{{...}}` token remains in any rendered file. + +### Phase 6 — Install services + +**Linux (systemd user):** +```bash +mkdir -p ~/.config/systemd/user +cp /*.service ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user enable --now agents-task-runner-appeals-inbox.service \ + agents-task-runner.service \ + agents-task-runner-watchdog.service +loginctl enable-linger "$USER" # survive logout / start at boot +``` + +**macOS (launchd):** +```bash +cp /*.plist ~/Library/LaunchAgents/ +for p in site.kzntsv.agents-task-runner-appeals-inbox site.kzntsv.agents-task-runner site.kzntsv.agents-task-runner-watchdog; do + launchctl unload ~/Library/LaunchAgents/$p.plist 2>/dev/null + launchctl load -w ~/Library/LaunchAgents/$p.plist +done +``` + +**Windows (winsw):** follow `service/winsw/WINSW-PIN.md` verification contract first. +```powershell +# 1. Fetch + verify (ABORT on mismatch; STOP if pin is still the placeholder) +Invoke-WebRequest -OutFile "$INSTALL_DIR\winsw.exe" +if ((Get-FileHash "$INSTALL_DIR\winsw.exe" -Algorithm SHA256).Hash -ne $ExpectedSha) { throw "winsw SHA256 mismatch" } +# 2. winsw convention: .exe + .xml side by side. Copy winsw.exe per service id, place rendered xml. +# Then install + start each: +& "$INSTALL_DIR\agents-task-runner.exe" install +& "$INSTALL_DIR\agents-task-runner.exe" start +# repeat for -watchdog and -appeals-inbox +``` +Disable the legacy launcher so it can't coexist: `schtasks /change /tn AgentsTaskRunnerWorker /disable` +(or `/delete` after confirming the service is healthy). + +### Phase 7 — Scope file (disarmed default) + +```bash +mkdir -p ~/.config/projects-mcp +# Only if absent — NEVER overwrite an existing (possibly armed) file: +[ -f ~/.config/projects-mcp/poller-scope.json ] || echo '{"armed":[]}' > ~/.config/projects-mcp/poller-scope.json +``` + +### Phase 8 — Verify acceptance + +The design's acceptance criteria — verify each, show evidence: + +1. **Starts without a window.** No console window appears; `services.msc` / `systemctl --user status` / + `launchctl list` shows the three running. +2. **Survives kill.** Kill the task-runner PID; within the restart window the OS supervisor respawns it + (re-check status / port 3000 answers again). +3. **Reads scope from runtime config.** With `{"armed":[]}` the poller logs claim nothing (disarmed). + Optionally arm a throwaway entry in the scope file and confirm hot-reload picks it up WITHOUT a + restart (then revert) — but real arming is the operator's pult step, not this skill's. +4. **No POLLER_PROJECTS / DRY_RUN** present in any installed unit (grep the rendered files). + +### Phase 9 — Final report + +``` +✅ Standing-duty stack installed as services, run-as-user , DISARMED. + Services: agents-task-runner (:3000), -watchdog, -appeals-inbox (:4317) + Install dirs: + (dev tree stays editable — deploy-boundary) + Scope: ~/.config/projects-mcp/poller-scope.json = {"armed":[]} (nothing spawns yet) + + GOING LIVE is a separate operator step: arm a project via the appeals-inbox pult + (http://127.0.0.1:4317). Until then the poller claims nothing. + + Redeploy after a dev-tree change: re-run this skill (re-syncs install dir + restarts), + or `factory update agents-task-runner` once the L1 Go-CLI lands. Editing the dev tree + does NOT hot-patch the running service. + + Backups: .bak-. docker mongo+reconciler are separate — bring up via compose. +``` + +## Rollback + +1. Stop + remove the services: + - Linux: `systemctl --user disable --now agents-task-runner*.service; rm ~/.config/systemd/user/agents-task-runner*.service; systemctl --user daemon-reload` + - macOS: `launchctl unload ~/Library/LaunchAgents/site.kzntsv.agents-task-runner*.plist; rm ...` + - Windows: `& "$INSTALL_DIR\.exe" stop; & "$INSTALL_DIR\.exe" uninstall` per id +2. Restore any `.bak-` files. +3. Re-enable the legacy launcher only if you need the old path back: + `schtasks /change /tn AgentsTaskRunnerWorker /enable`. +4. Install dirs are disposable copies — `rm -rf` them; the dev tree is untouched. +5. Leave `poller-scope.json` as-is. + +## Cross-platform notes + +| | service unit | install location | run-as-user | boot-before-login | +|---|---|---|---|---| +| Linux | systemd `*.service` | `~/.config/systemd/user/` | inherent (user unit) | `loginctl enable-linger` | +| macOS | launchd `*.plist` | `~/Library/LaunchAgents/` | inherent (LaunchAgent) | runs at login (Agent) | +| Windows | winsw `.xml` | `%LOCALAPPDATA%\agents-task-runner\` | `` + password | needs stored creds; login-triggered is acceptable on a personal box | + +## Common mistakes + +- **Skipping Phase 1.** Re-installing over an existing armed scope file or a running service without + noticing → double-claim or a clobbered arming state. +- **Carrying `POLLER_PROJECTS` / `DRY_RUN`.** The whole point is runtime scope. Grep the rendered units. +- **Leaving the Scheduled Task enabled alongside the service.** Two task-runners claim the same board → + double-claim. Disable the legacy launcher. +- **Running as root / LocalSystem.** The runner needs the user's `~/.config`, `~/.claude`, git creds and + spawns `claude` — must be the user account. +- **Fabricating / skipping the winsw SHA256.** STOP if the pin is the placeholder; abort on mismatch. +- **Treating install as going-live.** Installed ≠ armed. Nothing spawns until the operator arms via the pult. +- **Editing the dev tree and expecting the service to pick it up.** It won't — redeploy (re-sync + restart).