Sling Dispatch
Instruction dispatch unit for the Gastown chipset. The sling takes a work item (bead) and routes it to an available polecat through a 7-stage pipeline. In hardware terms, the sling is the instruction fetch-decode-dispatch unit -- it resolves work items from state, selects an execution unit (polecat), prepares the execution context, and launches the agent. The sling does not execute work; it sets up the conditions for execution and confirms the dispatch was recorded.
Activation Triggers
This skill activates when:
- The mayor needs to assign work items to available polecats
- A convoy of beads is ready for dispatch to a rig
- A formula template needs expansion into ordered dispatch steps
- A partial dispatch needs recovery after a crash or session restart
Core Capabilities
The 7-Stage Dispatch Pipeline
Every dispatch flows through seven stages in strict order. Each stage completes before the next begins. If any stage fails, the pipeline halts at that stage and the partial state is recoverable.
FETCH ALLOCATE PREPARE HOOK STORE LAUNCH CONFIRM
| | | | | | |
v v v v v v v
resolve select or create update work write args start agent record
bead ID spawn idle isolated item status to durable session dispatch
to work polecat workspace to 'hooked', state file with GUPP in convoy
item (dir+ctx) set assignee (crash-safe) context tracking
Stage 1 -- Fetch:
Resolve the bead ID to a concrete work item from state. The bead must exist and must be in open status. If the bead is already hooked, in_progress, or done, the dispatch is a no-op (idempotency guarantee).
const state = new StateManager({ stateDir: '.chipset/state/' });
async function fetchBead(beadId: string): Promise<WorkItem | null> {
const workItem = await state.getWorkItem(beadId);
if (!workItem) {
// Bead does not exist -- dispatch fails
return null;
}
if (workItem.status !== 'open') {
// Already dispatched or completed -- idempotent no-op
return null;
}
return workItem;
}
Stage 2 -- Allocate:
Select an available polecat from the agent pool, or spawn a new one if the pool has capacity. The allocator prefers idle polecats over spawning new ones to minimize resource usage.
async function allocatePolecat(rig: string): Promise<AgentIdentity | null> {
// List all polecats for this rig
const polecats = await state.listAgents({ role: 'polecat', rig });
// Prefer idle polecats (already spawned, ready for work)
const idle = polecats.filter(p => p.status === 'idle');
if (idle.length > 0) {
return idle[0];
}
// Check if we can spawn a new polecat (within max_parallel limit)
const active = polecats.filter(p => p.status === 'active');
const config = await state.getChipsetConfig();
if (active.length >= config.dispatch.max_parallel) {
// Pool is full -- cannot dispatch right now
return null;
}
// Spawn a new polecat
return state.createAgent({
role: 'polecat',
rig,
status: 'idle',
});
}
Stage 3 -- Prepare:
Create the isolated workspace for the polecat. This includes setting up the working directory (git worktree or clean checkout) and assembling the execution context (bead details, convoy membership, relevant file paths).
async function prepareWorkspace(
polecat: AgentIdentity,
workItem: WorkItem
): Promise<WorkspaceContext> {
// Create a working branch for the polecat
const branchName = `polecat/${workItem.beadId}`;
// Assemble the execution context
const context: WorkspaceContext = {
agentId: polecat.id,
beadId: workItem.beadId,
branch: branchName,
title: workItem.title,
description: workItem.description,
priority: workItem.priority,
};
return context;
}
Stage 4 -- Hook:
Update the work item status to hooked and set the assignee. This is the binding step -- after this, the bead is claimed by the polecat and cannot be dispatched to another agent.
async function hookBead(
workItem: WorkItem,
polecat: AgentIdentity
): Promise<void> {
// Update work item status atomically
workItem.status = 'hooked';
workItem.assignee = polecat.id;
workItem.hookStatus = 'pending';
await state.updateWorkItem(workItem);
// Set the polecat's hook
await state.setHook(polecat.id, workItem.beadId, workItem.title);
}
This is the idempotency boundary. Re-dispatching a bead that is already hooked returns a no-op at Stage 1 (the fetch rejects non-open beads). The hook stage itself is atomic -- the work item status and hook file are written using the write-then-rename pattern.
Stage 5 -- Store:
Write the dispatch arguments to durable state. This creates a dispatch record that survives process crashes. If the sling crashes after Store but before Launch, the dispatch can be resumed from this record.
interface DispatchRecord {
beadId: string;
agentId: string;
branch: string;
context: WorkspaceContext;
stage: 'stored' | 'launched' | 'confirmed';
createdAt: string;
}
async function storeDispatch(
context: WorkspaceContext,
polecat: AgentIdentity
): Promise<void> {
const record: DispatchRecord = {
beadId: context.beadId,
agentId: polecat.id,
branch: context.branch,
context,
stage: 'stored',
createdAt: new Date().toISOString(),
};
// Write atomically to dispatch state
const recordPath = join(stateDir, 'dispatch', `${context.beadId}.json`);
await atomicWrite(recordPath, serializeSorted(record));
}
Stage 6 -- Launch:
Start the polecat's agent session with the GUPP execution context. The polecat receives its hook (set in Stage 4) and the dispatch context (stored in Stage 5). GUPP activates immediately -- the polecat begins work without waiting for confirmation.
async function launchPolecat(
polecat: AgentIdentity,
context: WorkspaceContext
): Promise<void> {
// Update agent status to active
await state.updateAgentStatus(polecat.id, 'active');
// Update dispatch record to 'launched'
const recordPath = join(stateDir, 'dispatch', `${context.beadId}.json`);
const record = await readJson<DispatchRecord>(recordPath);
record.stage = 'launched';
await atomicWrite(recordPath, serializeSorted(record));
// Send work assignment mail to polecat
const assignmentMail: AgentMessage = {
from: 'mayor',
to: polecat.id,
channel: 'mail',
payload: `ASSIGNED: ${context.beadId} - ${context.title}`,
timestamp: new Date().toISOString(),
durable: true,
};
await sendMail(assignmentMail);
// The polecat detects the hook via polling and enters GUPP mode
}
Stage 7 -- Confirm:
Write the dispatch confirmation to convoy tracking. This closes the dispatch pipeline and records the bead-to-agent assignment in the convoy's progress state.
async function confirmDispatch(
context: WorkspaceContext,
polecat: AgentIdentity,
convoyId?: string
): Promise<void> {
// Update dispatch record to 'confirmed'
const recordPath = join(stateDir, 'dispatch', `${context.beadId}.json`);
const record = await readJson<DispatchRecord>(recordPath);
record.stage = 'confirmed';
await atomicWrite(recordPath, serializeSorted(record));
// If part of a convoy, update convoy progress
if (convoyId) {
await state.updateConvoyProgress(convoyId);
}
// Notify the witness of the new dispatch
const witnessMail: AgentMessage = {
from: 'mayor',
to: 'witness',
channel: 'mail',
payload: `DISPATCH: ${context.beadId} -> ${polecat.id} on branch ${context.branch}`,
timestamp: new Date().toISOString(),
durable: true,
};
await sendMail(witnessM