Whilly v3 → v4 Migration Guide
⚠️ no backwards compat. v4 is a wholesale rewrite. (Per PRD Appendix B.) There is no in-place upgrade. v3.x state files (
.whilly_state.json,.whilly_workspaces/,whilly_logs/) are not read by v4. The plan JSON schema is mostly compatible (see “Plan format” below), but every other interface — env vars, CLI flags, deployment shape, on-disk paths — has changed.This doc walks through the migration path: what to keep, what to throw away, and what to re-derive.
Scope
| Category | v3.x | v4.0 |
|---|---|---|
| Runtime topology | Single process driving Claude CLI in tmux/worktree panes | Postgres + FastAPI control plane + N remote workers over HTTP |
| Task storage | tasks.json is the source of truth | Postgres tasks table; tasks.json is import format only |
| State persistence | .whilly_state.json (resume support) | tasks table + events audit log |
| Concurrency | tmux panes / git worktrees | optimistic locking + SKIP LOCKED claim + visibility-timeout sweep |
| Failure recovery | manual --reset / re-run | sweep flips stale claims back to PENDING; peer worker re-claims |
| Python | 3.10+ | 3.12+ (TaskGroup, @override) |
| Install | pip install whilly-orchestrator (everything) | [worker] / [server] / [all] extras split |
| Logs | whilly_logs/whilly_events.jsonl | events table (audit log), whilly dashboard (live) |
Decision: stay on v3 or move to v4?
Stay on v3 (tag v3-final) if any of:
- You run a single laptop / single-VM workflow and don’t need distributed workers.
- Your Postgres ops budget is zero — v4 needs an actual database to talk to.
- You depend on the v3 CLI surface (tmux panes, interactive menu, plan-level workspace,
--prd-wizard,--init, etc.) — none of this ships on v4. - You’re inside a Python 3.10/3.11 deployment that you can’t bump.
The v3.x line stays on PyPI; bugfix releases will be tagged 3.3.x.
Move to v4 if any of:
- You want to run more than one worker against the same task plan (SC-1).
- You want SIGKILL’d workers to recover automatically (SC-2).
- You want a control plane on one VM and workers on another (SC-3).
- You want a static type-strict + 100%-coverage core domain layer (SC-5/SC-6).
- You’re starting a fresh deployment and want the long-term-supported shape.
Step-by-step migration
1. Decommission v3.x state
Stop any running whilly / whilly --resume processes. Then preserve and remove the v3.x runtime artefacts:
# Optional: archive v3 state in case you need to look at it later
tar czf whilly-v3-archive.tar.gz \
.whilly_state.json \
.whilly_workspaces/ \
.whilly_worktrees/ \
whilly_logs/
# Remove (v4 will not read any of these)
rm -rf .whilly_state.json .whilly_workspaces/ .whilly_worktrees/ whilly_logs/
tasks.json (or .planning/*tasks*.json) — keep. It’s the import format on v4.
2. Bump Python
v4 requires Python ≥3.12. Check:
python --version # must be 3.12+
If not, install via pyenv / asdf / your distro:
pyenv install 3.12.7 && pyenv local 3.12.7
# or
asdf install python 3.12.7 && asdf local python 3.12.7
3. Install v4 with the right extras
Pick the install closure that matches the deployment role:
# All-in-one local box (developer laptop, demo VM)
pip install -e '.[all]'
# Control-plane VM (Postgres + FastAPI)
pip install -e '.[server]'
# Worker VM (httpx-only — no Postgres / FastAPI bloat)
pip install whilly-worker
# or, equivalently, pulled directly from extras:
pip install whilly-orchestrator[worker]
The slim [worker] install is what lets a worker box stay on a tiny base image — no asyncpg, no FastAPI, no SQLAlchemy. See whilly_worker/README.md for the full rationale.
4. Stand up Postgres + apply migrations
# Local: docker-compose ships a postgres:15-alpine + whilly DB / role
docker compose up -d
export WHILLY_DATABASE_URL=postgresql://whilly:whilly@localhost:5432/whilly
# Production: point at your Postgres cluster
export WHILLY_DATABASE_URL=postgresql://USER:PASS@HOST:5432/DBNAME
# Apply schema (creates plans, tasks, events, workers tables)
alembic upgrade head
5. Re-import your plan
export PLAN_FILE=tasks.json # placeholder — your plan json path
export PLAN_ID=demo # placeholder — the plan id
# Old: whilly --tasks tasks.json
# New:
whilly plan import "$PLAN_FILE"
# Verify
whilly plan show "$PLAN_ID" # ASCII DAG of tasks + dependencies
The v4 plan JSON schema is mostly the same as v3’s. Required task fields: id, status, priority, description. Optional but commonly used: dependencies, key_files, acceptance_criteria, test_steps, prd_requirement. Extra fields are tolerated and ignored. Invalid plans fail at plan import with a structured error pointing at the offending task id.
6. Run
export PLAN_ID=demo # placeholder — the plan id
# Local (control plane embedded in worker — single process)
whilly run --plan "$PLAN_ID"
# Distributed
# a) on the control-plane box
export WHILLY_WORKER_TOKEN=$(openssl rand -hex 32)
export WHILLY_WORKER_BOOTSTRAP_TOKEN=$(openssl rand -hex 32)
uvicorn 'whilly.adapters.transport.server:create_app' --factory --port 8000
# b) on each worker box
whilly-worker --connect https://control.example.com:8000 \
--token "$WHILLY_WORKER_TOKEN" \
--plan "$PLAN_ID"
7. Watch progress
export PLAN_ID=demo # placeholder — the plan id
whilly dashboard --plan "$PLAN_ID" # Rich Live TUI
Env-var mapping
Variables marked removed are no longer read by v4 — set them or not, no effect.
| v3 var | v4 equivalent | Notes |
|---|---|---|
WHILLY_MAX_PARALLEL | (removed) | Concurrency is per-worker; spawn N workers instead |
WHILLY_BUDGET_USD | (deferred) | Budget guards land in v4.1 |
WHILLY_MODEL | WHILLY_CLAUDE_MODEL | Same value, renamed for clarity |
WHILLY_USE_TMUX | (removed) | tmux runner deleted |
WHILLY_USE_WORKSPACE | (removed) | Plan-level workspace deleted |
WHILLY_HEADLESS | (still works) | whilly run is non-interactive by default |
WHILLY_TIMEOUT | (removed) | Use process supervisor’s timeout |
WHILLY_STATE_FILE | (removed) | State lives in Postgres |
WHILLY_LOG_DIR | (deferred) | Audit lives in events table; file logs land in v4.1 |
| (new) | WHILLY_DATABASE_URL | Postgres DSN |
| (new) | WHILLY_CONTROL_URL | Control-plane base URL (worker side) |
| (new) | WHILLY_WORKER_TOKEN | Per-worker bearer token |
| (new) | WHILLY_WORKER_BOOTSTRAP_TOKEN | Cluster-join secret |
| (new) | WHILLY_PLAN_ID | Plan id worker draws from |
| (new) | WHILLY_WORKER_ID | Worker identity (default: <host>-<8-hex>) |
CLAUDE_BIN | CLAUDE_BIN | Same name, same semantics |
CLI surface mapping
| v3 invocation | v4 equivalent |
|---|---|
whilly --tasks tasks.json | whilly plan import tasks.json && whilly run --plan <id> |
whilly --all | spawn N whilly-worker --plan <id> processes |
whilly --resume | (no longer needed; state survives in Postgres) |
whilly --reset PLAN.json | (no v4 equivalent yet — DELETE FROM events/tasks WHERE plan_id=…) |
whilly --headless | whilly run is headless by default |
whilly --init "desc" | (deferred to v4.1; PRD wizard is v3-only) |
whilly --prd-wizard | (deferred to v4.1) |
whilly --workspace / --worktree | (removed) |
| v3 plan-level TRIZ challenge | whilly plan triz <plan_id> [--json] [--strict] |
| (new) | whilly plan show <plan_id> — ASCII DAG |
| (new) | whilly plan export <plan_id> > tasks.json |
| (new) | whilly dashboard --plan <plan_id> — Rich Live TUI |
| (new) | whilly-worker --connect URL --token X --plan <id> |
Breaking changes summary
- Python 3.12+ required (was 3.10+).
- Postgres is a hard runtime dependency for the control plane (was none).
- State files (
.whilly_state.jsonetc.) are not read. Plans must be re-imported viawhilly plan import. - tmux runner / git worktree workspace removed. Concurrency comes from spawning multiple workers.
- Single
pip install whilly-orchestratorno longer installs everything. Pick[worker]/[server]/[all]based on role. - State machine:
(COMPLETE, CLAIMED) → DONEis a valid edge.claimed → doneskipping IN_PROGRESS is now legal — required for the remote worker shape (no/tasks/{id}/startHTTP endpoint). - Audit log lives in
eventstable, notwhilly_logs/whilly_events.jsonl. - Console scripts:
whillyis now a sub-command dispatcher (plan,run,dashboard);whilly-workeris a separate binary for the remote-worker entry point. - Workers register out-of-band. v3 had no concept of registration; v4 expects a worker row in the
workerstable before the first claim. The/workers/registerendpoint mints one; or (for tests / demo) seed it via SQL — seedocs/demo-remote-worker.sh.
Pointers
- Architecture:
Whilly-v4-Architecture.md - HTTP protocol:
Whilly-v4-Worker-Protocol.md - Release checklist:
v4.0-release-checklist.md - PRD (full design rationale):
PRD-refactoring-1.md