Skip to content

Plan: "Start on Vector" option on chat.justinsforge.com session launcher

URL: https://mkdocs.justinsforge.com/memory/plans/vector-session-launcher-2026-07-01/

Date: 2026-07-01 Approved spec: New Chat sheet on chat.justinsforge.com gets a "Start on Vector" checkbox. Checked, the launcher API drops a job JSON into a queue at forge/data/vector-launcher/queue/ instead of spawning locally. A Vector-side PowerShell daemon (logon scheduled task, installed with schtasks.exe) polls the queue every ~15s over Vector's existing standing SSH into Console (no new keys in either direction), writes a heartbeat each poll, atomically claims jobs, launches a visible Windows Terminal running claude --model <m> --remote-control <name> --debug-file <log> in C:\Users\Justi\forge-vector-local\, fires a Windows toast, parses the bridge URL from the debug log, and writes a result JSON back to Console. The API surfaces the claude.ai/code URL through the existing job-polling UI. Stale heartbeat (>60s) fails fast with "Vector not reachable". Daemon launches Claude only, never arbitrary shell; every launch toasts and logs.

Out of scope: - Wake-on-LAN when Vector is asleep/off - Warm pool on Vector (cold boot only) - Listing or killing Vector sessions from the launcher UI (spawn-time URL only; sessions won't appear in the Active list, which reads Console tmux sockets) - Sol support (queue format stays machine-generic: jobs carry a machine field so Sol can bolt on later)

Contracts (shared by all tasks)

Queue dir layout on Console: forge/data/vector-launcher/{queue,claimed,results}/ + heartbeat.json.

Job file queue/<job_id>.json:

{"id": "<uuid>", "machine": "vector", "model": "opus|fable|sonnet|haiku",
 "name": "<slug or empty>", "prompt": "<string or empty>", "created_at": "<iso8601>"}

Result file results/<job_id>.json:

{"id": "<uuid>", "status": "ready|error", "url": "https://claude.ai/code/<id>",
 "session_name": "<name>", "error": "<msg if error>", "finished_at": "<iso8601>"}

Heartbeat heartbeat.json: {"ts": "<iso8601>", "host": "vector", "daemon_pid": <int>} rewritten every poll. API treats >60s stale as offline.

Claim = mv queue/<id>.json claimed/<id>.json over SSH (atomic on same filesystem; daemon restart cannot double-launch).

Task list

Task 1: Create queue directory structure and contract doc

  • Files: /home/justinwieb/forge/data/vector-launcher/queue/.gitkeep, /home/justinwieb/forge/data/vector-launcher/claimed/.gitkeep, /home/justinwieb/forge/data/vector-launcher/results/.gitkeep, /home/justinwieb/forge/data/vector-launcher/README.md
  • What: Create the three queue dirs with .gitkeep (per feedback_git_stash_empty_dir_wipe) and a README documenting the job/result/heartbeat JSON contracts above. Bare path, data/ is not mkdocs-indexed.
  • Verification: ls /home/justinwieb/forge/data/vector-launcher/queue /home/justinwieb/forge/data/vector-launcher/claimed /home/justinwieb/forge/data/vector-launcher/results
  • Commit: feat(vector-launcher): queue dir structure + job contract

Task 2: API accepts target:"vector" and queues jobs

  • Files: /home/justinwieb/forge/scripts/forge_session_launcher_api.py
  • What: Add target: str = "console" to SpawnRequest (validator: console|vector). In spawn_session, route target == "vector" to a new _spawn_vector_job(job_id, req): reject immediately if heartbeat.json missing/stale (>60s) with error: "Vector not reachable (daemon heartbeat stale)"; else write the job file, then poll results/<job_id>.json (2s interval, 180s timeout) and map it onto the existing JOBS statuses (booting -> ready/error). Reject resume/advanced combined with target: "vector" (unsupported, fail loud). Fully async, no threads.
  • Verification: curl -fsS -X POST http://127.0.0.1:7365/api/sessions -H "Authorization: Bearer $SESSION_LAUNCHER_TOKEN" -H 'Content-Type: application/json' -d '{"model":"opus","target":"vector"}' | jq . then curl .../api/jobs/<id> shows the stale-heartbeat error (daemon not deployed yet)
  • Commit: feat(session-launcher): target:"vector" job queue path

Task 3: UI checkbox "Start on Vector"

  • Files: /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.js, /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.css
  • What: Add a fl-check checkbox id="fl-vector" labeled "Start on Vector" to the New Session sheet (next to the existing Advanced checkbox, launcher.js ~line 452); include target: "vector" in the POST body when checked (~line 142). When checked, grey out the warm-pool count line (cold boot only) and disable the Advanced checkbox. Bump the versioned asset URL in index.html if that is the current cache-bust mechanism.
  • Verification: /screenshot https://chat.justinsforge.com after opening the sheet locally via http://127.0.0.1:7365/ui/; checkbox visible, POST body carries target (confirm in forge/logs/session-launcher.log)
  • Commit: feat(chat-ui): Start on Vector checkbox in new-session sheet

Task 4: Vector daemon PowerShell script

  • Files: /home/justinwieb/forge/scripts/vector/session-launcher/forge_vector_session_launcher.ps1
  • What: Poll loop (15s): ssh console to write heartbeat and list queue/*.json; claim via atomic mv; parse job; launch wt.exe (fallback conhost) running claude --model <m> --dangerously-skip-permissions --remote-control <name> --debug-file C:\Users\Justi\forge-vector-local\logs\rc-<id>.log "<prompt>" with CWD C:\Users\Justi\forge-vector-local\; BurntToast-free toast via msg-style fallback (plain New-BurntToastNotification only if module present, else [System.Windows.Forms.NotifyIcon]); poll the debug log up to 120s for the v2 session URL line (same regex family as forge_spawn_session.sh:136); write result JSON back over SSH (PowerShell here-string pipe, NOT heredoc, per reference_vector_claude_code.md). Any failure writes status:"error" with the message; every job appended to C:\Users\Justi\forge-vector-local\logs\launcher-daemon.log. Session name default vector-<4char> when blank.
  • Verification: PowerShell syntax check on Console: pwsh -NoProfile -Command "[scriptblock]::Create((Get-Content -Raw scripts/vector/session-launcher/forge_vector_session_launcher.ps1)) | Out-Null; 'parse ok'" (pwsh is installed on Console; full behavior verified in Task 6)
  • Commit: feat(vector-launcher): Vector-side poll daemon (PowerShell)

Task 5: Installer using schtasks.exe

  • Files: /home/justinwieb/forge/scripts/vector/session-launcher/install.ps1
  • What: Copies the daemon into C:\Users\Justi\forge-vector-local\session-launcher\, creates logon scheduled task ForgeVectorSessionLauncher via schtasks.exe /Create /SC ONLOGON (NEVER Register-ScheduledTask; stalls over SSH, per reference_sshrequest_vector_schtasks_gotcha), starts it immediately with schtasks.exe /Run, idempotent re-run (/F overwrite). Include an uninstall switch (/Delete /F).
  • Verification: pwsh -NoProfile parse check as in Task 4
  • Commit: feat(vector-launcher): schtasks installer for Vector daemon

Task 6: Deploy to Vector and end-to-end test

  • Files: none new (deployment + test)
  • What: /sshrequest vector for 1 hour to install session-launcher daemon (one-time push under the sanctioned grant model; Justin clicks Approve on Vector). scp the two scripts over, run install.ps1 over SSH, confirm heartbeat lands on Console within 30s. Then full path: check "Start on Vector" at chat.justinsforge.com, confirm terminal + toast on Vector, claude.ai/code URL returned in UI, session responds. Also verify fail-fast: stop the task (schtasks /End), wait 90s, spawn again, expect "Vector not reachable" in UI. Restart task after.
  • Verification: cat /home/justinwieb/forge/data/vector-launcher/heartbeat.json fresh ts; result JSON with status:"ready" and working URL; UI error string on the stopped-daemon test
  • Commit: chore(vector-launcher): deployed to Vector, e2e verified (any fixup edits included)

Task 7: Register and document

  • Files: /home/justinwieb/forge/memory/general/reference_vector_session_launcher.md (with mkdocs URL line), /home/justinwieb/.claude/projects/-home-justinwieb-forge/memory/MEMORY.md (one index line), /home/justinwieb/forge/memory/general/reference_session_launcher.md (add target:"vector" to the endpoint docs)
  • What: Topic file: architecture (pull model + why), file paths both sides, contracts, install/uninstall commands, known limitations (no Active-list visibility, no WoL), security posture. Update the launcher reference and MEMORY.md index. Run bash scripts/forge_eval_run.sh.
  • Verification: bash /home/justinwieb/forge/scripts/forge_eval_run.sh passes; grep -n "vector" /home/justinwieb/forge/memory/general/reference_session_launcher.md
  • Commit: docs(vector-launcher): register + document Vector session launcher