Plan: /ship skill, warm dev-slot pool, chat surface badges, dev-queue title synthesis¶
URL: https://mkdocs.justinsforge.com/memory/plans/forge-ship-warm-slots-devqueue-2026-07-13/
- Date: 2026-07-13
- Approved scope decisions: PENDING JUSTIN APPROVAL (per feedback_infra_rewrites_plan_then_opus_workers: plan first, then 2-3 opus workers execute)
- North Star: dev-queue sessions go from prompt to live preview in seconds (warm slots), reviewed branches ship with one command (/ship), the chat surface shows real capacity (warm chats + warm dev slots), and queue tasks self-document (7-9 word titles).
- Builds on: dev-queue skill + forge_creatortrack_client.py (title subcommand, commit 99badab), forge_creatortrack_agent_bootstrap.sh, spawn pool (forge_spawn_pool.sh + forge-spawn-pool.timer, re-enabled 2026-07-13), session launcher API.
Verified integration points¶
creatortrack/scripts/deploy_prod.sh: npm build, systemctl restart creatortrack-prod, /login poll 30s, warm_prod.sh. Currently NO clean-main guard, NO migration run (by design, two prod breakages on record), NO heap cap baked in.- Merge convention (git log verified):
--no-fftwo-parent merges, messagemerge: <feature summary>; "merge ONCE" integrator model per reference_creatortrack_parallel_dev. forge/scripts/forge_session_launcher_api.py: PROMPT_BLOCKS lines 663-782 (devserver toggle 718-721, fanout 750-760);_pool_by_model()286-292 countsdata/spawn_pool/available/;_dev_slots()576-627 already reports per-slotserver_alive;/api/sessions485-553; UI static-mounted at 2011, restart required for Python edits.forge/sites/justinsforge.com/chat/launcher.js: warm-chats pill in render() 512-531, 15s poll 1180-1184; vanilla JS, no build step.forge_creatortrack_agent_bootstrap.shtiming: worktree/symlink/DB-clone/.env are instant;next devbinds in seconds; the warm-up shell compile (up to ~180s) dominates..env.localread once at start, so DB/port must stay pinned per slot;git switch -cat claim time is safe under a running server.forge_creatortrack_agent_teardown.shand the dev-slot reaper derive paths from BRANCH NAME and reap idle slots (45m idle / cap 4): both must learn slot identity for a warm pool to survive..claude/skills/dev-queue/SKILL.md: synthesis step slots between step 1 (pull) and step 2 (claim);forge_creatortrack_devqueue_run.pystays read-only.
PHASE 1, ship path¶
Task 1.1: Harden deploy_prod.sh (restore lost guardrails)¶
- Files:
/home/justinwieb/creatortrack/scripts/deploy_prod.sh - What: refuse unless on clean main (guard existed once, silently lost);
export NODE_OPTIONS=--max-old-space-size=6144before build (OOM gotcha);systemctl is-active creatortrack-prod.servicecheck after restart. - Verification: run from a dirty feature branch, expect loud refusal; run on clean main, expect normal deploy.
- Commit:
fix(deploy): clean-main guard, heap cap, post-restart active check
Task 1.2: /ship skill (orchestrator)¶
- Files:
/home/justinwieb/forge/.claude/skills/ship/SKILL.md - What:
/ship <task-id|branch>: preflight (CT task In Review, branch pushed, worktree clean, main clean) ->git merge --no-ffwithmerge: <summary>-> migrations gate (ifdb/migrationstouched: apply to lifeos via forge_workspace_migrate.py, verify core.schema_migrations, BEFORE deploy) -> foreground deploy_prod.sh (never kill mid-run, no atomic swap yet) -> post-verify (is-active, /login, warm routes, 2 authenticated curls) -> CT task gets structured deploy note + status Deployed (open question below) -> release/teardown the task's dev slot. Runtime executor: a sonnet worker; Fable/Justin only trigger. - Verification: dry-run mode against a no-op branch; then first real ship supervised by Justin.
- Commit:
feat(skills): ship orchestrator, merge + migrate-gate + deploy + verify
PHASE 2, warm dev-slot pool (3 slots)¶
Task 2.1: Slot registry + bootstrap slot mode¶
- Files:
forge/data/creatortrack_slots/slots.json(new),forge/scripts/forge_creatortrack_agent_bootstrap.sh - What: registry maps slot-id -> {worktree, port, db, branch, state: warm|claimed}. Bootstrap gains
--slot <n>: branchwarm-slot-N, port 3067-3069, DBct_warm_slot_N. Decouples identity from branch name (today teardown/reaper derive everything from branch, which breaks the moment a claimed slot switches branches). - Verification: bootstrap slot 1, registry entry appears, preview URL serves.
- Commit:
feat(ct-slots): slot registry + bootstrap --slot mode
Task 2.2: Pool maintainer timer¶
- Files:
forge/scripts/forge_creatortrack_warm_slots.sh(new), systemd user unit+timerforge-ct-warm-slots - What: every 15 min: ensure 3 slots in state=warm (bootstrap missing ones; warm-up compile runs off the critical path, so a claimed slot is replaced in the background).
- Verification: kill a warm slot's server, wait one timer tick, slot returns warm.
- Commit:
feat(ct-slots): warm pool maintainer (3 slots)
Task 2.3: Claim + release commands¶
- Files:
forge/scripts/forge_creatortrack_slot_claim.sh(new),forge_creatortrack_agent_teardown.sh - What: claim: pick a warm slot,
git switch -c feat/<name>(safe under running server, verified), mark claimed, print preview URL: seconds instead of minutes. Release: teardown via registry (not branch-derived paths), DB re-cloned from template, slot re-warms in background. DB/port stay pinned per slot. - Verification: claim -> branch correct, URL live, registry claimed; release -> slot back to warm within one timer tick.
- Commit:
feat(ct-slots): claim/release, registry-driven teardown
Task 2.4: Memory-aware reaper policy (Justin amendment 2026-07-13)¶
- Files: dev-slot reaper script/unit (locate via reference_creatortrack_dev_slot_reaper)
- What: reaping keyed to Console memory pressure, computed from MemAvailable/MemTotal (not "used", which buff/cache inflates). Under 60% used: NO idle reaping of any dev slot, warm or claimed. 60-85% used: idle timeout extends 45m -> 3h. Over 85% used: 45m idle reaping including warm slots, and the warm-slot maintainer skips refills until pressure drops. Claimed-slot cap raised 4 -> 5 and enforced in ALL bands (cap is slot hygiene, not RAM); warm slots never count toward the cap.
- Verification: test all three bands via an env override (e.g. REAPER_FAKE_MEM_PCT): idle slot survives in band 1, survives 2h in band 2, reaped after 45m in band 3; 6th claimed slot refused in every band.
- Commit:
fix(ct-reaper): memory-aware idle bands, warm-slot exemption, claimed cap 5
PHASE 3, chat.justinsforge surface¶
Task 3.1: Warm dev slots badge¶
- Files:
forge/scripts/forge_session_launcher_api.py,forge/sites/justinsforge.com/chat/launcher.js,launcher.css - What: fold
dev_slots_warm(from_dev_slots(), slots with server_alive + state=warm) into/api/sessions; secondfl-poolbadge next to warm chats: "N warm chats / N warm dev slots". Restart forge-session-launcher. - Verification: badge shows 3 with pool full; claim one, badge drops to 2 within one 15s poll.
- Commit:
feat(launcher): warm dev-slots badge
Task 3.2: devserver toggle reuses warm slots¶
- Files:
forge_session_launcher_api.pyPROMPT_BLOCKS (lines 718-721) - What: toggle text becomes: claim a warm dev slot via forge_creatortrack_slot_claim.sh if one is available, else bootstrap fresh; give the URL either way. Restart service.
- Verification: new session with toggle on claims a warm slot in seconds.
- Commit:
feat(launcher): devserver toggle prefers warm slots
PHASE 4, dev-queue title synthesis¶
Task 4.1: Step 1.5 in dev-queue skill¶
- Files:
forge/.claude/skills/dev-queue/SKILL.md - What: after pulling claim_now, before claiming: for each task, read notes; if marker
Original title:present, skip; elseappend <id> "Original title: <old>"thentitle <id> "<7-9 word synthesis>". Judgment stays in-session (script stays read-only). Marker-based idempotency, not title-length heuristics. - Verification: run dev-queue on a test task twice; second run does not re-rename or double-append.
- Commit:
feat(dev-queue): step 1.5 title synthesis with marker guard
Sequencing + execution¶
- Three OPUS workers, run SEQUENTIALLY (all commit to the same forge checkout; serializing avoids the shared-index commit hazard): W1 = Phase 1 + Phase 4, W2 = Phase 2, W3 = Phase 3.
- Each worker: build, verify per task, conventional commit per task, distilled report.
- Coordinator (Fable) reviews reports, smoke-tests the badge + a claim end-to-end, updates MEMORY.md registrations.
Out of scope¶
- Atomic-swap deploy (known debt, stays logged), warm CHAT pool size change (sizes file currently 2/2/2/2 = 8, bump to 9 is a one-line edit if wanted), any n8n involvement.
Open risk to flag¶
- RESOLVED 2026-07-13: /ship sets CT task status to "Deployed" (Justin confirmed). Regression loop: Justin drags a Deployed task back to Send to Forge with notes; the queue engine re-pulls it like any task, full history intact. /ship's deploy note should mention this loop.
- Warm slots hold ~15GB RAM baseline (3 x ~5GB). Fine after today's zombie cleanup (55GB free); Task 2.4's over-85% band has the maintainer skip refills under pressure.
- The spawn-pool timer was deliberately stopped Jul 10 13:13 with no documented reason; if that was quota protection, the refilled chat pool + warm dev slots increase background usage. Watch the quota tracker the first days.