forge-convoy
SKILL.md
FORGE Convoy Pattern
Work bundling for parallel component processing in multi-agent systems.
When to Use
- Processing multiple Figma components simultaneously
- Coordinating work across multiple workers
- Tracking progress of related tasks
- Enabling parallel execution without conflicts
Core Concept
A Convoy is a work bundle that:
- Groups related tasks (e.g., all components in a Figma frame)
- Tracks progress across multiple workers
- Survives agent restarts
- Coordinates without agent-to-agent communication
┌─────────────────────────────────────────────────────┐
│ CONVOY │
│ "Auth System Components" │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ Task 1 │ Task 2 │ Task 3 │ Task 4 │ │
│ │LoginForm│SignupBtn│AuthModal│UserMenu │ │
│ │ ✅ DONE │ 🔄 WIP │ ⏳ READY│ 🔒 BLOCKED│ │
│ └─────────┴─────────┴─────────┴─────────┘ │
└─────────────────────────────────────────────────────┘
FORGE Convoy Structure
interface ForgeConvoy {
id: string; // Hash-based: "convoy-a1b2"
name: string; // Human-readable name
figmaFileKey: string; // Source Figma file
frameId?: string; // Optional: specific frame
tasks: ConvoyTask[]; // Individual components
status: ConvoyStatus; // PENDING | IN_PROGRESS | COMPLETE | BLOCKED
created: string; // ISO timestamp
updated: string; // Last modification
// Coordination metadata
totalTasks: number;
completedTasks: number;
failedTasks: number;
blockedTasks: number;
}
interface ConvoyTask {
id: string; // "convoy-a1b2.1"
componentId: string; // Figma component ID
componentName: string; // Human-readable
status: TaskStatus; // READY | IN_PROGRESS | COMPLETE | FAILED | BLOCKED
assignedWorker?: string; // Worker ID if in progress
// Dependencies (Beads-style)
blockedBy: string[]; // Task IDs that must complete first
discoveredFrom?: string; // Parent task if found during work
// Results
result?: {
mpkPath?: string; // Generated Mendix package
evidencePath?: string; // Validation evidence
error?: string; // If failed
};
}
Convoy Lifecycle
┌──────────┐ ┌───────────────┐ ┌──────────┐
│ CREATED │────▶│ IN_PROGRESS │────▶│ COMPLETE │
└──────────┘ └───────────────┘ └──────────┘
│ │ │
│ ▼ │
│ ┌──────────┐ │
└──────────▶│ BLOCKED │◀──────────────┘
└──────────┘
State Transitions
| From | To | Trigger |
|---|---|---|
| CREATED | IN_PROGRESS | First task assigned |
| IN_PROGRESS | COMPLETE | All tasks complete |
| IN_PROGRESS | BLOCKED | Unresolvable dependency |
| BLOCKED | IN_PROGRESS | Blocker resolved |
Creating Convoys
From Figma File
// Mayor creates convoy from Figma analysis
const convoy = await createConvoy({
name: "Auth System Components",
figmaFileKey: "abc123",
components: figmaParser.extractComponents(figmaFile)
});
// Results in:
// convoy-a1b2
// ├── convoy-a1b2.1: LoginForm (READY)
// ├── convoy-a1b2.2: SignupButton (READY)
// ├── convoy-a1b2.3: AuthModal (BLOCKED by .1, .2)
// └── convoy-a1b2.4: UserMenu (READY)
With Dependencies
// Some components depend on others (shared styles, tokens)
await convoy.addDependency({
from: "convoy-a1b2.3", // AuthModal
to: "convoy-a1b2.1", // LoginForm (must complete first)
type: "blocks"
});
Sling: Dispatching Work
Sling = assign task to worker via ledger (not direct communication)
// Mayor slings task to available worker
await sling({
task: "convoy-a1b2.1",
rig: "forge-translator",
// Worker will find this in their hook
});
// Worker picks up task from hook
const myTask = await hook.getWork();
// { taskId: "convoy-a1b2.1", type: "translate", ... }
Sling Rules
- Only sling READY tasks (no open blockers)
- One task per worker at a time
- Worker writes result to ledger, then terminates
- Mayor checks ledger, slings next task
Parallel Processing
Convoys enable safe parallelism:
// Mayor finds all ready tasks
const readyTasks = convoy.tasks.filter(t =>
t.status === 'READY' &&
t.blockedBy.every(dep => getTask(dep).status === 'COMPLETE')
);
// Sling to multiple workers in parallel
await Promise.all(readyTasks.map(task =>
sling({ task: task.id, rig: 'forge-translator' })
));
// No conflicts because:
// - Each worker has isolated workspace
// - Workers don't know about each other
// - Results merge via ledger, not shared state
Discovered Work Pattern
Workers may discover additional work during execution:
// Translator finds missing asset
await ledger.createTask({
id: generateHashId(),
componentName: "MissingIcon",
status: "READY",
discoveredFrom: "convoy-a1b2.1", // Links back to parent
// Inherits parent's convoy
});
The discovered-from dependency:
- Preserves audit trail
- Inherits parent's source context
- Enables root cause analysis
Convoy Progress Tracking
// Mayor queries convoy status
const status = await convoy.getStatus();
// {
// total: 4,
// complete: 1,
// inProgress: 1,
// ready: 1,
// blocked: 1,
// failed: 0,
// percentComplete: 25
// }
// Ready work query (Beads-style)
const readyWork = await convoy.getReadyTasks();
// Returns tasks with no open blockers
Integration with Beads
Convoys can be stored as Beads for persistence:
# Create convoy as Beads epic
bd create "Auth System Convoy" -t epic -p 1
# Add component tasks as children
bd create "LoginForm" -p 1 # Auto-assigns bd-a1b2.1
bd create "SignupButton" -p 1 # Auto-assigns bd-a1b2.2
# Add blocking dependency
bd dep add bd-a1b2.3 bd-a1b2.1 --type blocks
# Query ready work
bd ready --json
FORGE Convoy CLI (Proposed)
# Create convoy from Figma
forge convoy create --figma abc123 --name "Auth System"
# List convoys
forge convoy list
# Show convoy progress
forge convoy show convoy-a1b2
# Sling ready tasks
forge convoy sling convoy-a1b2 --parallel 3
# Mark task complete
forge convoy complete convoy-a1b2.1 --result /path/to/mpk
Anti-Patterns
❌ Workers Communicating Directly
// Workers talking to each other
translatorWorker.notify(validatorWorker, "Done!");
// → Race condition, coupling
❌ Shared Convoy State in Memory
// Convoy in worker memory
worker.convoy = globalConvoyState;
worker.convoy.updateTask(...); // Conflicts
❌ Blocking on Other Workers
// Worker waiting for another worker
await otherWorker.waitForComplete(); // Deadlock risk
Correct Pattern
// ✅ All coordination via ledger
// Worker reads: ledger
// Worker writes: ledger
// Worker never: talks to other workers
async function executeTask(taskId: string) {
// 1. Read task from ledger
const task = await ledger.getTask(taskId);
// 2. Execute in isolation
const result = await translate(task.componentId);
// 3. Write result to ledger
await ledger.completeTask(taskId, result);
// 4. Terminate (Mayor will dispatch next)
process.exit(0);
}
References
- Gas Town Convoy: github.com/steveyegge/gastown
- Beads ready work: github.com/steveyegge/beads
- FORGE Epic 07: Agent Orchestration