Overview
The convoy backend infrastructure is fully implemented in the TownDO — createConvoy(), onBeadClosed() landing detection, convoy_metadata, bead_dependencies tracking. But none of it is reachable from the Mayor. The Mayor slings independent beads with no grouping, no progress tracking, and no way to check on batched work.
This issue wires convoys end-to-end: batch slinging with auto-convoy creation, Mayor visibility tools, prompt updates, and dead code cleanup.
Parent: #204
Current State
- Mayor
gt_sling: Creates one bead, assigns one polecat, dispatches. No convoy.
- Mayor prompt: Instructs the Mayor to break work into multiple beads (5-bead decomposition example). But says nothing about convoys.
- Mayor tools: No convoy tools exist. No
gt_create_convoy, no gt_list_convoys, no gt_convoy_status.
TownDO.createConvoy(): Fully implemented — creates convoy bead, convoy_metadata, bead_dependencies.
TownDO.onBeadClosed(): Fully implemented — counts closed tracked beads, updates progress, auto-lands convoy.
updateBeadStatus(): Wired — closing any bead checks bead_dependencies for convoy links and calls onBeadClosed().
- Dead code:
town-convoys.table.ts, town-convoy-beads.table.ts (pre-refactor tables, never imported), orphaned convoy_id field on CreateBeadBody.
The backend works. The Mayor can't reach it.
Implementation
1. Add gt_sling_batch tool to the Mayor plugin
A new Mayor tool that creates multiple beads + a convoy in one call:
// container/plugin/mayor-tools.ts
{
name: "gt_sling_batch",
description: "Sling multiple beads as a tracked convoy. Use this when a task should be broken into parallel sub-tasks.",
parameters: {
rig_id: { type: "string", description: "Target rig ID" },
convoy_title: { type: "string", description: "Title for the convoy (the overall task)" },
tasks: {
type: "array",
items: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" },
},
},
description: "List of individual beads to create and assign to polecats",
},
},
}
2. Add MayorGastownClient.slingBatch()
// container/plugin/client.ts
async slingBatch(rigId: string, convoyTitle: string, tasks: { title: string; body?: string }[]): Promise<SlingBatchResult> {
return this.post(`/api/mayor/${this.townId}/tools/sling-batch`, { rig_id: rigId, convoy_title: convoyTitle, tasks });
}
3. Add Mayor sling-batch HTTP route
POST /api/mayor/:townId/tools/sling-batch
Handler calls TownDO.slingConvoy().
4. Add TownDO.slingConvoy()
Atomic operation:
- Create convoy bead (
type='convoy', labels=['gt:convoy']) + convoy_metadata row
- For each task: create bead (
type='issue') + bead_dependencies row (dependency_type='tracks') linking bead → convoy
- For each bead: get or create polecat agent + hook bead + dispatch
- Set
convoy_metadata.total_beads = task count
- Return convoy bead + all created beads + assigned agents
5. Add convoy visibility tools
gt_list_convoys — List active convoys:
{
name: "gt_list_convoys",
description: "List active convoys with progress. Shows how many beads are closed vs total.",
}
// Returns: [{ convoy_id, title, total_beads, closed_beads, status, created_at }]
gt_convoy_status — Detailed convoy view:
{
name: "gt_convoy_status",
description: "Show detailed status of a convoy: each tracked bead with status and assignee.",
parameters: { convoy_id: { type: "string" } },
}
// Returns: { convoy_id, title, total, closed, beads: [{ bead_id, title, status, assignee }] }
6. Add client methods and HTTP routes for visibility tools
GET /api/mayor/:townId/tools/convoys → TownDO.listConvoys()
GET /api/mayor/:townId/tools/convoys/:id → TownDO.getConvoyStatus()
7. Update Mayor system prompt
Add convoy instructions:
- When breaking work into multiple tasks, use
gt_sling_batch to group them into a convoy
- Use
gt_list_convoys to check on active batches of work
- Use
gt_convoy_status for detailed progress on a specific convoy
- Convoys land automatically when all tracked beads close — no manual management needed
- When a user asks "how's X going?", check the relevant convoy first
8. Dead code cleanup
Remove:
src/db/tables/town-convoys.table.ts — pre-refactor standalone table, never imported
src/db/tables/town-convoy-beads.table.ts — pre-refactor standalone table, never imported
convoy_id field from CreateBeadBody in src/handlers/rig-beads.handler.ts — accepted but silently dropped
End-to-End Flow
User: "Refactor the auth module"
→ Mayor breaks into 5 tasks
→ gt_sling_batch(rig_id, convoy_title: "Refactor auth module", tasks: [...])
→ TownDO.slingConvoy()
→ create convoy bead + convoy_metadata(total_beads=5)
→ for each task: create issue bead + bead_dependency(tracks) + polecat + dispatch
→ Mayor: "Created convoy 'Refactor auth module' with 5 beads, all dispatched."
[Polecats work, beads close one by one]
→ updateBeadStatus('closed') → onBeadClosed() → convoy_metadata.closed_beads++
→ Dashboard: "Refactor auth module: 3/5"
[Last bead closes]
→ onBeadClosed() → closed_beads == total_beads → convoy lands
→ Dashboard notification: "Convoy 'Refactor auth module' landed"
User: "How's the auth refactor?"
→ Mayor calls gt_convoy_status(convoy_id)
→ "4 of 5 beads closed. Toast is working on the test update. The middleware fix is in the merge queue."
Acceptance Criteria
Overview
The convoy backend infrastructure is fully implemented in the TownDO —
createConvoy(),onBeadClosed()landing detection,convoy_metadata,bead_dependenciestracking. But none of it is reachable from the Mayor. The Mayor slings independent beads with no grouping, no progress tracking, and no way to check on batched work.This issue wires convoys end-to-end: batch slinging with auto-convoy creation, Mayor visibility tools, prompt updates, and dead code cleanup.
Parent: #204
Current State
gt_sling: Creates one bead, assigns one polecat, dispatches. No convoy.gt_create_convoy, nogt_list_convoys, nogt_convoy_status.TownDO.createConvoy(): Fully implemented — creates convoy bead,convoy_metadata,bead_dependencies.TownDO.onBeadClosed(): Fully implemented — counts closed tracked beads, updates progress, auto-lands convoy.updateBeadStatus(): Wired — closing any bead checksbead_dependenciesfor convoy links and callsonBeadClosed().town-convoys.table.ts,town-convoy-beads.table.ts(pre-refactor tables, never imported), orphanedconvoy_idfield onCreateBeadBody.The backend works. The Mayor can't reach it.
Implementation
1. Add
gt_sling_batchtool to the Mayor pluginA new Mayor tool that creates multiple beads + a convoy in one call:
2. Add
MayorGastownClient.slingBatch()3. Add Mayor sling-batch HTTP route
Handler calls
TownDO.slingConvoy().4. Add
TownDO.slingConvoy()Atomic operation:
type='convoy',labels=['gt:convoy']) +convoy_metadatarowtype='issue') +bead_dependenciesrow (dependency_type='tracks') linking bead → convoyconvoy_metadata.total_beads= task count5. Add convoy visibility tools
gt_list_convoys— List active convoys:gt_convoy_status— Detailed convoy view:6. Add client methods and HTTP routes for visibility tools
7. Update Mayor system prompt
Add convoy instructions:
gt_sling_batchto group them into a convoygt_list_convoysto check on active batches of workgt_convoy_statusfor detailed progress on a specific convoy8. Dead code cleanup
Remove:
src/db/tables/town-convoys.table.ts— pre-refactor standalone table, never importedsrc/db/tables/town-convoy-beads.table.ts— pre-refactor standalone table, never importedconvoy_idfield fromCreateBeadBodyinsrc/handlers/rig-beads.handler.ts— accepted but silently droppedEnd-to-End Flow
Acceptance Criteria
gt_sling_batchtool exists in Mayor plugin and creates N beads + 1 convoy atomicallygt_list_convoystool returns active convoys with progress countsgt_convoy_statustool returns detailed bead-level status for a convoyTownDO.slingConvoy()creates beads, convoy, dependencies, assigns polecats, and dispatchestown-convoys.table.ts,town-convoy-beads.table.ts, orphanedconvoy_idonCreateBeadBody