Spawning
Spawning is how a directive becomes a running grunt. A Colonel writes a spawn request; the system turns it into a live Claude Code session that claims the directive and starts working.
The spawn flow
When a Colonel calls spawn_grunt, Vinculum enqueues a spawn_request row in the database (status pending). It does not launch anything locally — there is no in-process Popen path and no host-side daemon. Execution belongs entirely to a paired vinculum-runner.
The runner is a small Go binary on the operator's machine. It long-polls the server over outbound HTTPS at /runner/poll, claims the spawn_request rows targeted at it (first-writer-wins via SKIP LOCKED), then opens a tmux pane and launches Claude Code locally with the grunt system prompt pre-injected.
spawn_grunt(directive_id=42)
│
├─ enqueues spawn_request row (status = pending) ← server side
│
vinculum-runner (outbound HTTPS long-poll GET /runner/poll)
├─ claims the row (SKIP LOCKED)
├─ opens tmux pane on the operator's machine
├─ launches Claude Code with system prompt
├─ grunt claims spawn_uuid
├─ grunt claims directive
└─ grunt works
│
spawn_reconcile watchdog tick (server side)
└─ promotes spawn_log.status from substrate-write recencyThe grunt boot protocol
Every grunt session runs the same four-step bootstrap before touching the directive:
claim_spawn(spawn_uuid)— registers the tmux pane and role. First-writer-wins; if another session already claimed this UUID, the grunt stops.declare_focus(branch, session_label, session_color)— shows up on the dashboard so the Colonel can see what's running.claim_directive(entry_id)— atomic claim. If already claimed by another session, the grunt stops and writes a note.amend_directive(stage='claimed', payload.plan)— records intent in the directive tail so the Colonel knows what the grunt understood.
After bootstrap the grunt executes. It writes checkpoint amendments as it progresses and finishes with an implementation amendment listing every file touched.
Runner-only spawning
Earlier versions spawned through a separate daemon process and host-side systemd units. Both are gone (#2133 daemon teardown). Spawning is now runner-only: the server only ever enqueues a spawn_request row, and a paired vinculum-runner claims and executes it over outbound HTTPS. There is no VINCULUM_SPAWN_MODE or VINCULUM_ALLOW_LOCAL_SPAWN knob and no extra process to babysit.
Claim semantics
Both claim_spawn and claim_directiveare first-writer-wins with a database-level lock. There's no race. If two grunt sessions boot at the same time (edge case, but possible), exactly one will claim the directive; the other will get already_claimed and stop.
Stale claims — directives claimed more than 30 minutes ago with no progress — are auto-reaped. The next grunt to attempt a claim on a stale directive will succeed.
Self-hosted vs cloud
Cloud (vinculum.run)
On vinculum.run, the server hosts the substrate but never executes spawns itself. You still pair a vinculum-runner on the machine where grunts should run — your workstation. When you call spawn_grunt, the runner claims the spawn_requestand launches the grunt locally with access to your project's MCP tools.
Self-hosted
On a self-hosted install, the flow is identical: pair a runner on the machine that should spawn grunts. Generate a pairing token, install the runner binary, and it polls the server over outbound HTTPS — no inbound ports, no daemon to install:
> use generate_pairing_token
# or: Dashboard → Settings → Runners → Connect a runnerThe runner registers itself and shows up in Settings → Runners within ~30 seconds; confirm with spawner_health().
> use spawner_healthSee self-hosting for the full setup guide.
Watching spawns
The dashboard workers row shows all active grunt sessions and their status (booting, working, done, stale). The live feed shows spawn events and grunt bootstrap entries in real time.
Active grunt sessions in the Workers panel — each session shows its claimed directive, current status, and branch focus. The Colonel watches this panel to track wave progress without polling.
You can also check spawn status from Claude:
> use grunts with project="my-app"
# Returns all running grunt sessions with
# their current directive and status.Next: Directives
Now you know how grunts come alive. Next: what they're actually working on. Read about directives →