Skip to content

feat(multi-core): Phase 2a — claim primitive + launchd pool scripts (tracks #880) - #882

Closed
qingyun-wu wants to merge 1 commit into
feat/multi-core-pool-designfrom
feat/multi-core-claim-primitive
Closed

qingyun-wu wants to merge 1 commit into
feat/multi-core-pool-designfrom
feat/multi-core-claim-primitive

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

Summary

Phase 2a of the multi-core agent pool. Ships the two foundation pieces that are safe to land before the pool-aware skill:

  • src/claim_task.py + 7 unit tests including a 2-process race test (forked subprocesses, verifies exactly one wins via POSIX-atomic rename).
  • scripts/install-core-pool.sh N (default N=3) and scripts/uninstall-core-pool.sh.

Stacked on #881 (the multi-core-pool design doc). Once #881 merges, rebase the base to main and this lands cleanly.

Regression-prevention posture

  1. Opt-in by default. Default behavior unchanged — pool only activates when install-core-pool.sh runs.
  2. Pre-flight refuse: install REFUSES to create plists when the /proactive-loop-pool skill (Phase 2b) doesn't exist on disk — unless --force is passed — so today's install can't quietly create broken pool members that loop-restart on missing skill and burn quota.
  3. Narrow glob: both scripts use com.sutando.core-[0-9]*.plist, NOT the wider com.sutando.core-*.plist. Critical — see "Regression caught during self-test" below.
  4. Idempotent install: re-running with smaller N removes excess plists; re-running with same N updates in place.
  5. Logs preserved on uninstall: $SUTANDO_WORKSPACE/logs/core-*.log kept for post-mortem.
  6. Input sanitization: claim_task.py rejects .., /, dot-prefixed task_id / core_id (CodeQL py/path-injection class).

Regression caught + fixed during self-test

Original uninstall glob com.sutando.core-*.plist engulfed com.sutando.core-agent.plist — the pre-existing Sutando.app production launchd agent, NOT a pool member. Self-test removed it; recovered via cp .plist.bak .plist && launchctl bootstrap. Both scripts now use com.sutando.core-[0-9]*.plist (numeric-suffix-only) with a prominent comment citing this regression at each glob site. Saved as durable memory (feedback_specific_globs_system_paths.md) so the lesson outlives this PR.

What's NOT in this PR (Phase 2b, separate)

  • /proactive-loop-pool skill that wedges the claim call before task pickup.
  • Side-effect helper done-flag gate (~10 helpers across Python+TS bridges).
  • Boot-time orphan-scan watchdog (release stale claims when sessions crash).
  • Per-session state/cores/core-<n>/ heartbeat generalization.

Without Phase 2b, installing the pool at N>1 would result in 3× duplicate processing of every task. The install script's pre-flight refuse blocks this — owner can --force if they understand the risk and want to test the launchd shape.

Eval (per the always-propose-eval rule)

  • Synthetic — correctness (CI gating): 7 unit tests in tests/claim-task.test.py:
    • happy path: single claim renames file
    • missing task: returns None
    • double claim: second returns None
    • race: 2 forked subprocesses both call claim() on the same task; exactly one wins, other loses, kernel decides
    • different core IDs: claim files distinguish properly
    • path-traversal validation: rejects .., /, dot-prefix
    • realistic task_id (e.g. 1779170589673): accepted
  • Synthetic — throughput: deferred to Phase 2b once the skill wedge exists; the burst-load benchmark needs the full claim wiring to measure speedup.
  • Real-user — shadow-mode: deferred to Phase 2b; needs the full pool to replay bridge logs against.

Test plan

  • python3 tests/claim-task.test.py — 7/7 pass
  • bash -n scripts/install-core-pool.sh syntax check pass
  • bash -n scripts/uninstall-core-pool.sh syntax check pass
  • Install script pre-flight refuse verified (without --force, errors out cleanly with usage message)
  • Install script --force path verified on temp workspace
  • Narrow glob verified — confirmed com.sutando.core-[0-9]*.plist does NOT match com.sutando.core-agent.plist
  • Owner local install test (Phase 2b ready)

Tracks #880. Phase 2b lands as a separate PR once the wedge + done-flag gate are designed.

🤖 Generated with Claude Code

…tracks #880)

Phase 2a of the multi-core agent pool (#880, design in #881). Ships the
two foundation pieces that are safe to land before the pool-aware skill:

1. `src/claim_task.py` — atomic-rename claim primitive. Single-FS POSIX
   `rename()` is atomic — no two callers both succeed. Library API:
   `claim(task_id, core_id, workspace=None) -> Path | None`. CLI: same
   semantics, exit 0/1 to indicate win/lose. Input validation rejects
   path-traversal (`..`, `/`, dot-prefix) so a hostile task_id can't
   escape `tasks/`.

2. `tests/claim-task.test.py` — 7 unit tests: happy path, missing task,
   double claim, **2-process race** (forked subprocesses, verifies
   exactly one wins), different core IDs, path-traversal validation,
   realistic task_id format. All pass locally.

3. `scripts/install-core-pool.sh N` — generates N launchd plists at
   `~/Library/LaunchAgents/com.sutando.core-<n>.plist`, kicks each via
   `launchctl bootstrap`. Default N=3 per owner directive. Pre-flight
   refuses install when the `/proactive-loop-pool` skill (Phase 2b)
   doesn't exist — unless `--force` is passed — so installing today
   can't quietly create broken pool members that loop-restart and burn
   quota.

4. `scripts/uninstall-core-pool.sh` — bootout + plist removal.
   Logs under `$SUTANDO_WORKSPACE/logs/core-*.log` preserved for
   post-mortem.

**Critical regression caught + fixed during self-test**: the original
glob `com.sutando.core-*.plist` engulfed `com.sutando.core-agent.plist`
(pre-existing Sutando.app launchd agent, NOT a pool member). The
uninstaller removed it before I noticed; recovery was
`cp .plist.bak .plist && launchctl bootstrap`. Both scripts now use
the narrower `com.sutando.core-[0-9]*.plist` pattern with a
prominent comment citing the regression so future maintainers don't
loosen it. Lesson saved as durable memory
(`feedback_specific_globs_system_paths.md`).

**Out of scope (Phase 2b, separate PR)**:
- `/proactive-loop-pool` skill that wedges the claim call.
- Side-effect helper done-flag gate (~10 helpers across Python+TS bridges).
- Boot-time orphan-scan watchdog.
- Per-session `state/cores/core-<n>/` heartbeat generalization.

**Regression posture**: opt-in only. Default Sutando behavior unchanged.
The pool only activates when `install-core-pool.sh N` runs AND the
Phase-2b skill exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant