spotworkers
Outsource bounded coding tasks to cheap agent workers. Your main agent keeps architecture, verification and commit rights — and you stop burning your quota.
Package details
Install spotworkers from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:spotworkers- Package
spotworkers- Version
0.3.0- Published
- Oct 24, 2026
- Downloads
- 202/mo · 202/wk
- Author
- hlxiao9
- License
- AGPL-3.0-or-later
- Types
- skill
- Size
- 625.6 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"skills": [
"./"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
spotworkers
Stop spending your Claude Code / Codex quota on work a cheap model can do.
spothands a bounded implementation task to a cheap agent worker (Pi / Qwen / DeepSeek / Kimi / Qoder / CodeBuddy), then independently re-runs your tests, checks the diff hash, and merges only when every gate is green. Architecture, review and commit authority stay with your main agent.
The main agent never asks the user to defend code it did not write. Treat the worker as untrusted: it never gets Bash, extensions, Skills, or project trust, and it cannot lie about test results — the wrapper writes verification.json, not the worker.
Renamed from
pi-delegate. Thepi-workercommand still works as an alias forspot.
Features
Closed-loop delegation with independent verification
doctor → prepare → run → verify → self-review → approve → integrate → report → cleanup. The wrapper re-runs your verification commands itself (not Pi's claims) and the diff hash is checked at every gate — a changed hash blocks approval.
Anti-cheat self-review (parent token saver)
Before the parent agent reads the full diff, an optional second worker turn re-reads its own work and emits a compact self-review.json (~50 lines vs thousands). Safeguards keep it honest:
verification.jsonis produced by the wrapper, not the worker — it cannot lie about test results.diffSha256Mismatch=trueif it echoes the wrong hash → parent escalates to a full review.fallbackRecommended=truewhen it self-reportsunmet/uncertainor any P0–P2 finding → parent escalates.approvere-scans the diff and rejects if the hash changed.- High-risk tasks never reach self-review (
HIGH_RISK_BLOCKED).
Self-review never blocks the loop — on worker failure, parse failure, a diff below minDiffBytes, or a review surface (diff + changed files) below minSurfaceBytes, it self-degrades to reviewing with skipped:true. minSurfaceBytes (default 4000) exists because a report is ~700 bytes per acceptance criterion while a small diff is cheaper to just read. Disable with selfReview.enabled=false; tune the proportional check with selfReview.spotCheckRatio (default 0.34).
Hard gates & security
- Wrapper verification success, unchanged diff hash, passed security scan, acceptance evidence, and zero unresolved P0–P2 findings required to approve.
- The worker cannot edit the harness that grades it: contract
lockedPaths, configalwaysLockedPaths(CI + test-runner config), and the execution keys ofpackage.json(scripts,jest,vitest,mocha,ava,nyc,c8,workspaces,packageManager) are frozen. Dependencies stay editable. Violations block withLOCKED_PATH_CHANGED/VERIFICATION_SCRIPT_CHANGED. - The worker is treated as untrusted: Bash, extensions, Skills, prompt templates, context files, and project auto-trust are never enabled.
- Never pushes, creates PRs, changes remotes, or describes worktree isolation as an OS sandbox.
- Never routes around authentication errors or capability mismatches with a fallback — a setup/credential failure is a real blocker.
Crash-safe recovery
Process-group timeouts, SIGINT/SIGTERM cleanup, and stale-lock reclamation. After an interruption, spot recover --id <run-id> reopens the run in place: a partial diff goes to verify, an empty diff goes back to run. A live lock is never stolen — only a lock whose owner process is gone is reclaimed.
Multi-parent dispatch
Tracks the caller (trae, codex, claude-code, cursor, pi-recursive, cli) via the PARENT_AGENT / PI_WORKER_CALLER env var, so the dashboard can attribute runs and list --caller <name> can filter.
Parallel execution
Multiple parent-agent sessions can run concurrently — each run gets an isolated Git worktree, state directory, and per-run file lock, so different runs never collide and the same run is never advanced by two processes at once. maxConcurrentRuns (default 4, range 1–16) caps active runs; prepare rejects with CONCURRENCY_LIMIT when the cap is hit. The dashboard reads state.json with JSON-parse tolerance, so a snapshot never crashes on a concurrent write.
Automatic model selection by difficulty
Declare difficulty in the contract (low/medium/high) and routing follows it. Without a declaration, risk and domain decide — medium risk plus a backend/systems/algorithm domain is already medium — and harness-size signals (acceptance count, verification count, constraint count, tool-use) add at most +2. Difficulty selects a cheap/standard/premium profile; within a tier, task.domain soft-matches profile.strengths. Override with --profile <name>; disable auto-selection by leaving costTier off your profiles.
Multi-CLI backends
Beyond the default Pi backend, profiles can set adapter: kimi | trae | qoder | opencode | codebuddy | zcode to drive other coding CLIs (Kimi Code CLI, Trae CLI, Qoder CLI, OpenCode, CodeBuddy Code CLI, ZCode CLI). Cross-adapter fallback works seamlessly. See references/provider-configuration.md.
Cost & savings metrics
The headline number is main-model tokens avoided, because the worker side is free, discounted, or far cheaper per token. Every run records what the parent read in delegation mode (self-review report, or the full review surface) against a counterfactual of reading the diff plus changed files over 1.5 cycles and writing the added lines — reported as both a token ratio and a spend-weighted ratio (output tokens cost ~4x input). The basis and excluded terms are written into metrics.json, so the number can be audited instead of trusted.
Per-run detail also records token usage (input/output/cached), the worker's provider-side usage, and — only when the parent backend has a rate card — displaced credits and subscription-allowance portion. Where a backend bills in credits instead of dollars (Qoder CN reports total_credits, not token counts), that figure is surfaced as its own field and the credit-based saving degrades to null rather than being invented.
Live monitoring dashboard
HTTP server with refresh button, summary cards, filterable run table (by caller, status, free text), expandable detail rows, light/dark theme, and a "Connection status" tab showing CLI adapter and profile credential health.
Recent updates
v0.1.0 shipped the core loop. Since then (unreleased, on main):
- Free-tier routing.
model: "free"(aliasesqoder-free,cheapest) resolves at dispatch time to the lowestprice_factormodel the account can actually reach, so a profile never hardcodes a model name that gets delisted. Qoder currently exposes two at0x:DeepSeek-FlashandQwen3.8-Flash. - Price-aware catalog.
spot detectprints each model with its multiplier, context tiers and legal effort levels —12 model(s) (2 free)followed by per-model[free]/[0.1x]/[1.4x]. The catalog is cached for 30 minutes and degrades to "no catalog" when offline instead of failing the run. - Per-model capability validation. An unsupported
reasoningEffortormaxContextSizeis snapped to the nearest legal value for the pinned model and recorded as aworker_warningevent plus a[warn]line inworker.log. Adjustments are visible, never silent. - CodeBuddy CLI adapter. Sixth backend, alongside Pi, Kimi, Trae, Qoder and OpenCode (
adapter: codebuddy). - Usage metering for every adapter. Non-streaming backends echo a
message_endusage event, soreadPiUsageaggregates tokens and duration uniformly. Dashboard labels are adapter-agnostic ("Token", "duration", "call count"). - Fixed:
EXCESSIVE_DELETIONrejected every pure edit. The guardrail measureddeleted / (added + deleted), so a one-line fix always scored0.5and was blocked — the single most common delegation shape. It now measures against base-file size, withlimits.maxDeletedLines(default300) as an absolute ceiling. - Fixed: Qoder ran interactively and hung. The CN build now gets
-p/--printexplicitly; without it the worker opened a TUI and stalled until the runner timeout. Auth failures are also classified asauthinstead ofpermanent, so a bad token reads as a setup problem rather than bad code. - Dashboard. 60s auto-refresh (persisted in localStorage, refreshes on tab focus),
workbuddyrecognised as a caller, and aworker.log+scripts/pi-tmux-monitorfor watching concurrent runs.
Prerequisites
- Node.js
>=22.19.0 - At least one worker CLI on
PATH— Pi (the default), or Kimi / Trae / Qoder / OpenCode / CodeBuddy with the matchingadapterin your profile.PI_WORKER_PI_BINonly applies to the Pi adapter; the others never shell out topi. - Git (the worker requires a Git repository)
- Provider credentials in env (e.g.
VOLCENGINE_API_KEY)
Run spot detect to see which worker CLIs this machine already has.
Install a worker CLI
The default backend is the Pi CLI (project page: https://pi.dev/). Pi is a separate open-source project — install it independently. If you prefer another backend, skip to adapter and install that CLI instead; only the adapter you select has to exist on PATH.
Option A — npm global (cross-platform)
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version # verify; spotworkers requires >= 0.80.10
Option B — one-line installer (macOS / Linux)
curl -fsSL https://pi.dev/install.sh | sh
pi --version
First-time Pi setup: launch pi once in any directory, then run /login inside Pi to configure a model provider (API key or subscription login). See the Pi documentation for the full provider list (Anthropic, OpenAI, Google, Volcengine, DeepSeek, Kimi, MiniMax, GLM, Qwen, OpenRouter, Ollama, …).
spotworkers only invokes Pi non-interactively (
pi -p), so the interactive TUI is just for initial/loginand model setup. You never need to drive Pi by hand for delegated tasks.
Install
Option 1 — npm global (recommended)
npm install -g spotworkers
spot --help
Option 2 — npx (no install)
npx -p spotworkers spot serve
The package ships two binaries (spot and the pi-worker alias), so npx has to be told which one to run — a bare npx spotworkers cannot guess.
Option 3 — as a Pi package
pi install npm:spotworkers
This registers the bundled SKILL.md with Pi, so /skill:spotworkers becomes available inside a Pi session. It does not put spot on your PATH — use Option 1 if you want both.
Option 4 — local link (for development / pre-publish verification)
git clone https://github.com/hLxiao9/spotworkers.git
cd spotworkers
npm install # only if you add deps later; currently zero runtime deps
npm link # exposes `spot` on PATH
spot --help
Roadmap: Homebrew tap (
brew tap hLxiao9/spotworkers && brew install spotworkers) and acurl | shone-line installer will be added after the first npm release.
First-time setup
npm install -g spotworkers automatically runs node scripts/install-config.mjs (via the postinstall hook) to merge the default config into ~/.config/pi-worker/config.json and the Volcengine provider into ~/.pi/agent/models.json. The merge is idempotent — your existing profiles, rate card, and subscription are preserved.
If the auto-config did not run (e.g., npm install --ignore-scripts, a sandboxed package manager, or a tarball install), run it manually:
node scripts/install-config.mjs # merges defaults; never overwrites user edits
spot init # interactive provider selection + credential report
apiKeyEnvcontract warning: theapiKeyEnvfield in~/.config/pi-worker/config.jsonand the$VARreference in~/.pi/agent/models.jsonmust point to the same environment variable name. Mismatched names (e.g.,apiKeyEnv: VOLCENGINE_API_KEYin config but$API_KEYin models.json) cause silent 401s at run time.spot doctordetects this and suggests the correct name.
Quickstart
# 1. Pre-flight check (validates Pi, model, credentials, config — no run created)
spot doctor --task ./task.json
# 2. Prepare an isolated worker branch + worktree
spot prepare --task ./task.json --usage-start ./usage-start.json
# 3. Run Pi on the isolated worktree
spot run --id <run-id>
# 4. Verify the actual diff (independent of Pi's claims)
spot verify --id <run-id>
# 5. Approve + integrate into the source branch
spot approve --id <run-id> --review ./review.json --message "feat: ..."
spot integrate --id <run-id>
# 6. Persist metrics + clean up
spot report --id <run-id>
spot cleanup --id <run-id>
If the terminal or parent process is interrupted, inspect the persisted run and re-enter the same closed loop instead of creating a new run:
spot inspect --id <run-id>
spot recover --id <run-id>
recover only reopens an interrupted or timed-out run. If the worker left a
partial diff, it sends the run to verify; if no diff exists, it sends it back
to run. A live lock is never stolen; only a lock whose owner process is gone
is reclaimed.
See SKILL.md for the full parent-agent workflow, hard gates, and routing rules.
Integrating with parent agents
spotworkers is a skill: it is driven by your parent agent (Codex / Claude Code / Trae / Cursor / another Pi) reading SKILL.md and then invoking the spot CLI. The parent keeps architecture, review, and commit authority; Pi only implements the bounded task.
Step 1 — Give the parent agent the skill
Load SKILL.md into the parent agent's context using whichever mechanism that agent supports:
| Parent agent | How to load SKILL.md |
|---|---|
| Pi | pi install path/to/spotworkers (treats it as a local Pi skill package) |
| Codex (OpenAI) | Drop SKILL.md (or a symlink) into ~/.codex/skills/ and restart Codex, or paste its contents into your project's AGENTS.md |
| Claude Code | Add the skill under ~/.claude/skills/ or include the contents in .claude/CLAUDE.md |
| Trae | Add the contents of SKILL.md to your Trae project rules / instructions |
| Cursor | Add the contents of SKILL.md to .cursor/rules/ (project rules) |
| Ad-hoc shell | Just node scripts/spot.mjs <command> ... — no skill loading needed |
SKILL.md is self-contained: it tells the parent agent the closed-loop workflow (doctor → prepare → run → verify → self-review → approve → integrate → report → cleanup), the hard gates, the routing rules, and the exact spot commands. The parent agent then calls spot via its normal shell/bash tool.
Step 2 — Identify the caller
Set PARENT_AGENT (or PI_WORKER_CALLER) in the environment that invokes spot prepare, so usage tracking and the dashboard can attribute runs correctly:
export PARENT_AGENT=codex # one of: codex | claude-code | trae | cursor | pi-recursive | cli
Step 3 — Run a delegated task
Once the skill is loaded, ask your parent agent in natural language, e.g.:
"Delegate to Pi: add a
slugifyfunction toutils.jsthat lowercases, trims, collapses whitespace to hyphens, and strips non-alphanumerics. Tests intest.jsmust pass."
The parent agent will construct a task.json matching schemas/task-contract.schema.json, then drive the full closed loop itself. You do not need to call spot manually.
Step 4 — Watch the dashboard
While the parent agent works, open a second terminal:
spot serve # http://localhost:7317/
The dashboard groups runs by caller, so you can see which agent is delegating what, in real time.
Note on
traeandcursorcallers: usage metering for these two is not yet implemented — the dashboard reportsavailable: falsefor their usage numbers. SetPARENT_AGENT=codexorclaude-codeinstead if you need usage stats.
Monitoring
Every run is persisted under ~/.local/state/pi-worker/runs/<run-id>/ (state.json, pi-events.jsonl, metrics.json, report.md). Query it without touching the worker:
| Command | Purpose |
|---|---|
spot list [--status <s>] [--caller <c>] |
JSON array of runs |
spot inspect --id <run-id> |
Full state + metrics + verification + review + Pi usage |
spot recover --id <run-id> |
Reopen an interrupted/timed-out run safely |
spot dashboard [--output <file>] |
Static single-page HTML snapshot |
spot serve [--port <port>] [--no-open] |
Live HTTP dashboard with refresh button |
Live dashboard
Fastest: double-click launcher
The start-dashboard.command at the repo root is a one-click launcher (double-click on macOS):
- Auto-detects the
spotcommand; printsnpm install -g spotworkersornpm linkhints when missing - Starts a local HTTP server and opens the browser automatically
- The "Refresh" button in the browser pulls the latest usage in real time, or just press F5
Ctrl+Cstops the server- Port can be overridden with the
PI_WORKER_PORTenv var (default 7317)
Linux / generic environments:
bash start-dashboard.command
# or directly
spot serve
Direct CLI invocation:
spot serve # http://localhost:7317/
spot serve --port 8080 # custom port
spot serve --no-open # do not auto-open browser
GET /— HTML dashboardGET /api/fragment— rendered HTML fragment (used by the "Refresh" button)GET /api/runs— raw JSON of state + metricsGET /health— health check
The "Refresh" button (and ordinary F5) re-reads ~/.local/state/pi-worker/runs/ so you always see the latest runs. A statically opened dashboard.html (file://) is a snapshot — use serve for real-time monitoring.
Caller identification
Set PARENT_AGENT (or PI_WORKER_CALLER) in the environment that invokes spot prepare:
PARENT_AGENT=codex spot prepare --task ./task.json # → caller: codex
PARENT_AGENT=trae spot prepare --task ./task.json # → caller: trae
PARENT_AGENT=cli spot prepare --task ./task.json # → caller: cli
The dashboard groups runs by caller; list --caller <name> filters accordingly.
Configuration
File locations
- Default config:
fixtures/default-config.json - User config:
~/.config/pi-worker/config.json - Models registry:
~/.config/pi-worker/models.json - Override paths via env:
PI_WORKER_CONFIG,PI_WORKER_MODELS_FILE,PI_WORKER_STATE_DIR,PI_WORKER_CACHE_DIR,PI_WORKER_PI_BIN
Configuring models
spotworkers does not manage model credentials itself — Pi does. The flow is:
Configure providers in Pi first. Launch
pi, run/login, and add your API keys / subscriptions. Pi stores them in~/.pi/agent/models.json. See the Pi documentation for the full provider list and setup steps.List models Pi can see, to confirm what you can reference in spotworkers profiles:
pi --no-extensions --no-skills --no-prompt-templates --no-themes --no-context-files --no-approve --list-modelsMap those models to spotworkers profiles in
~/.config/pi-worker/config.json. Each profile picks aprovider+modelfrom Pi's registry and tags it with acostTier(cheap/standard/premium),strengths(domains), andmodalities. Nine profiles ship by default (volcengine,deepseek,kimi,minimax-m3,gemini-vision,gpt-image,kimi-cli,trae-cli,qoder-cli) — seereferences/provider-configuration.mdfor the full table and copy-paste JSON snippets for adding your own.Validate with
spot doctor— it checks Node, Git, Pi, credentials, model availability, and task schema in one pass.
Tip: spotworkers auto-selects a profile by task difficulty (
low → cheap,medium → standard,high → premium). To force a specific profile (e.g. you only have MiniMax credentials), pass--profile minimax-m3todoctor/prepare.
Personal routing preference (callerProfiles)
Which model you like to start from is a personal taste, not something to hardcode into a shared repo's agent instructions. Put it in your own ~/.config/pi-worker/config.json, keyed by caller:
{
"callerProfiles": {
"claude-code": "qoder-qwen-flash",
"codex": "minimax-m3"
}
}
When no --profile is given, the order is: --profile → callerProfiles[<caller>] → difficulty routing → defaultProfile. The caller comes from PARENT_AGENT (see Caller identification); keys must be known callers and values existing profiles, or the config is rejected. The preferred profile keeps its own fallbackProfiles, so a free-model chain (e.g. Qoder free → CodeBuddy flash) is configured once, per machine. A preference is skipped when that profile cannot take the task (missing capability/modality), and it never unblocks a risk: high task. spot doctor without --profile reports profileSource: caller-preference and the chain it will use. The shipped default config sets no preference.
Multi-CLI backends (Pi / Kimi / Trae / Qoder)
Beyond the default Pi backend, profiles can set adapter: kimi | trae | qoder to drive other coding CLIs. See the "Multi-CLI Adapter Support" section in references/provider-configuration.md.
Development
npm test # node:test suite
npm run check:syntax # node --check on all entry points
npm run check # syntax + tests
Tests live in tests/*.test.mjs and run with node --test --test-concurrency=1.
Project layout
spotworkers/
├── scripts/
│ ├── spot.mjs # CLI entry (bin)
│ ├── install-config.mjs # one-time config bootstrap
│ └── parent-meter.mjs # parent-agent usage snapshot
├── lib/
│ ├── cli.mjs # arg parsing + dispatch
│ ├── dashboard.mjs # HTML rendering + summary logic
│ ├── server.mjs # `serve` HTTP server
│ ├── state.mjs # run state machine + persistence
│ ├── pi-runner.mjs # Pi invocation
│ ├── verification.mjs # independent verification gate
│ ├── review.mjs # parent review / approve
│ ├── integration.mjs # branch integrate + cleanup
│ ├── report.mjs # metrics + report.md
│ └── ...
├── schemas/ # JSON schemas (task, review)
├── fixtures/ # default config + provider templates
├── references/ # provider-configuration.md, review-policy.md
├── agents/ # parent-agent adapters
└── tests/ # node:test suite
License
This project is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
Commercial use that keeps derivatives closed-source requires a separate commercial license — please open an issue to discuss.