λx.xDocs← app
DocsConceptsSpawning

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 flow
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 recency
sequenceDiagram participant C as Colonel participant S as Server participant R as Runner participant G as Grunt C->>S: spawn_grunt(directive_id=42) Note over S: enqueue spawn_request (pending) R-->>S: GET /runner/poll (outbound HTTPS) S-->>R: spawn_request row (SKIP LOCKED) R->>G: launch Claude Code + system prompt G->>S: claim_spawn(spawn_uuid) G->>S: declare_focus(branch, label) G->>S: claim_directive(42) G->>S: amend_directive(stage=claimed) Note over G: executes directive G->>S: amend_directive(stage=implementation)

The grunt boot protocol

Every grunt session runs the same four-step bootstrap before touching the directive:

  1. claim_spawn(spawn_uuid) — registers the tmux pane and role. First-writer-wins; if another session already claimed this UUID, the grunt stops.
  2. declare_focus(branch, session_label, session_color)— shows up on the dashboard so the Colonel can see what's running.
  3. claim_directive(entry_id) — atomic claim. If already claimed by another session, the grunt stops and writes a note.
  4. 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:

text
> use generate_pairing_token
# or: Dashboard → Settings → Runners → Connect a runner

The runner registers itself and shows up in Settings → Runners within ~30 seconds; confirm with spawner_health().

text
> use spawner_health

See 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:

text
> 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 →