In the previous chapter, Beads & Dolt (The Ledger), we learned how to record tasks and issues in a distributed database. We now have a pile of tasks (Beads) sitting in our ledger.
But a pile of tasks is just a list. It doesn't get the work done. You don't want to manually assign every single task to an agent, wait for it to finish, and then assign the next one. That is micromanagement.
In Gas Town, we automate this flow using Convoys.
Imagine you are moving a library. You have 500 books (Beads) to move to a new room.
In software, a feature often consists of 10 different tasks (frontend, backend, database, tests). Managing these individually is tedious.
A Convoy is a logistical container. It is the delivery truck of Gas Town.
Let's look at how to manage a batch of work.
Let's say we have three tasks (Beads) created in the previous chapter: GT-101, GT-102, and GT-103. We want to group them into a "Release 1.0" milestone.
# Syntax: gt convoy create "Name" [issue-ids...]
gt convoy create "Release 1.0" GT-101 GT-102 GT-103
What happens? Gas Town creates a parent tracking object (the Convoy) and links the three tasks to it.
You can view the status of your logistics operation using the list command. This opens a visual TUI (Text User Interface).
gt convoy list
Output:
Convoys
โถ 1. ๐ Release 1.0: (1/3 completed)
If you press Enter to expand it:
โผ 1. ๐ Release 1.0: (1/3 completed)
โโ โ GT-101: Design Database schema
โโ โ GT-102: Write API endpoints [IN PROGRESS]
โโ โ GT-103: Update Documentation [OPEN]
This view tells you exactly where the blockage is. One task is done, one is being worked on, and one is waiting.
Did you forget a task? You can throw it onto the moving truck.
gt convoy add <convoy-id> GT-104
Convoys are not just static lists; they are Reactive Observers.
When a Polecat finishes a task, it doesn't just stop. The Convoy notices the completion and immediately checks: "Is there anything else in the truck that is ready to go?"
If yes, it "slings" (assigns) that task to the rig.
Let's look at how Gas Town implements this "Observer" pattern. The logic lives in internal/convoy/observer.go.
When an issue is closed, the system calls CheckConvoysForIssue. This function looks for any convoys tracking that specific issue.
// internal/convoy/observer.go
func CheckConvoysForIssue(townRoot, issueID, observer string, log func(...)) {
// 1. Find which convoy cares about this issue
convoyIDs := getTrackingConvoys(townRoot, issueID)
for _, id := range convoyIDs {
// 2. Check if the convoy is now 100% complete
runConvoyCheck(townRoot, id)
// 3. If not complete, feed the next item!
if !isConvoyClosed(townRoot, id) {
feedNextReadyIssue(townRoot, id, observer, log)
}
}
}
The feedNextReadyIssue function is the engine. It looks for tasks that are "Open" but have no "Assignee" yet.
// internal/convoy/observer.go
func feedNextReadyIssue(townRoot, convoyID, observer string, logger func(...)) {
// Get all tasks in this convoy
tracked := getConvoyTrackedIssues(townRoot, convoyID)
for _, issue := range tracked {
// Find first task that is OPEN and unassigned
if issue.Status == "open" && issue.Assignee == "" {
// Dispatch it!
dispatchIssue(townRoot, issue.ID, rigName)
return // We only feed one at a time to prevent flooding
}
}
}
Why is this cool? Because of this loop, you (the human) only need to start the first task. As soon as the agent finishes it, the Convoy automatically hands them the second task. The work flows continuously without your intervention.
How does gt convoy list look so nice? It uses a library called Bubbletea. In internal/tui/convoy/view.go, we convert raw data into icons.
// internal/tui/convoy/view.go
func statusToIcon(status string) string {
switch status {
case "open":
return "๐" // The Truck
case "closed":
return "โ" // Done
case "in_progress":
return "โ" // Working
default:
return "โ" // Waiting
}
}
We have Rigs (workspaces), Polecats (workers), Molecules (instructions), Beads (ledger), and Convoys (logistics). But what happens when five Polecats try to merge their code into the main branch at the same time? We need a traffic controller.
Next Chapter: Refinery (The Merge Engineer)
Generated by Code IQ