Skip to content

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-ff two-parent merges, message merge: <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 counts data/spawn_pool/available/; _dev_slots() 576-627 already reports per-slot server_alive; /api/sessions 485-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.sh timing: worktree/symlink/DB-clone/.env are instant; next dev binds in seconds; the warm-up shell compile (up to ~180s) dominates. .env.local read once at start, so DB/port must stay pinned per slot; git switch -c at claim time is safe under a running server.
  • forge_creatortrack_agent_teardown.sh and 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.py stays 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=6144 before build (OOM gotcha); systemctl is-active creatortrack-prod.service check 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-ff with merge: <summary> -> migrations gate (if db/migrations touched: 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>: branch warm-slot-N, port 3067-3069, DB ct_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+timer forge-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; second fl-pool badge 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.py PROMPT_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; else append <id> "Original title: <old>" then title <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

  1. 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.
  2. Each worker: build, verify per task, conventional commit per task, distilled report.
  3. 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

  1. 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.
  2. 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.
  3. 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.