In the previous chapter, Convoys (Work Logistics), we learned how to organize swarms of agents to work on batches of tasks. Now imagine you have five agents (Polecats) working on five different features. They all finish at the exact same time.
If they all try to push their code to the main branch simultaneously, chaos ensues.
In a traditional team, "Landing" code (merging it) is dangerous:
The Refinery is an autonomous agent that acts as the Gatekeeper (or Air Traffic Controller) for your codebase.
Think of the Refinery as a factory line. Raw code comes in from Polecats, gets refined (tested/squashed), and pure, clean code lands in the main branch.
The Refinery operates on a loop. It constantly watches the Ledger (Beads) for a specific type of issue: the Merge Request (MR).
In Gas Town, an MR isn't just a web page on GitHub. It is a Bead in the database, just like a task or a bug.
Since the Refinery is an automated agent, you mostly just watch it work. However, you can inspect the queue using the CLI.
# List beads labeled as merge requests
bd list --label=gt:merge-request --status=open
Output:
ID Title Status Assignee
GT-205 MR: Fix login button open gastown/refinery
GT-206 MR: Add database index open -
In this example, GT-205 is currently being processed (assigned to the refinery), and GT-206 is waiting in line.
The logic for the Refinery lives in internal/refinery/engineer.go. Let's break down how this agent processes the queue safely.
The Refinery polls the database for MRs that are ready. A "Ready" MR is one that is Open, Unassigned, and not blocked by other tasks.
// internal/refinery/engineer.go
func (e *Engineer) ListReadyMRs() ([]*MRInfo, error) {
// 1. Ask the database for beads with type="merge-request"
issues, _ := e.beads.ReadyWithType("merge-request")
var ready []*MRInfo
for _, issue := range issues {
// 2. Filter out ones that are already claimed or closed
if issue.Status == "open" && issue.Assignee == "" {
ready = append(ready, issueToMRInfo(issue))
}
}
return ready, nil
}
Gas Town prefers Squash Merges. If a Polecat made 50 messy commits ("fix typo", "try again", "oops"), we don't want that history in main. The Refinery compresses them into one clean commit.
// internal/refinery/engineer.go
func (e *Engineer) doMerge(ctx context.Context, branch, target string) ProcessResult {
// 1. Check for conflicts before trying
conflicts, _ := e.git.CheckConflicts(branch, target)
if len(conflicts) > 0 {
return ProcessResult{Conflict: true}
}
// 2. Perform the Squash Merge locally
// This takes all changes from 'branch' and stages them on 'target'
err := e.git.MergeSquash(branch, "Squash merge message")
// ... continue to testing ...
}
Before pushing, the Refinery acts as a quality gate. It executes the test command defined in your config.json.
// internal/refinery/engineer.go
func (e *Engineer) runTests(ctx context.Context) ProcessResult {
// 1. Get the command from config (e.g., "go test ./...")
cmdStr := e.config.TestCommand
// 2. Execute it in the shell
cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr)
// 3. Return success only if exit code is 0
if err := cmd.Run(); err != nil {
return ProcessResult{Success: false, TestsFailed: true}
}
return ProcessResult{Success: true}
}
What if the merge fails? The Refinery does not give up. It creates a Conflict Resolution Task.
This is a powerful concept: The Refinery automatically creates a new job, assigns it back to the workforce, and says, "Fix these conflicts."
// internal/refinery/engineer.go
func (e *Engineer) HandleMRInfoFailure(mr *MRInfo, result ProcessResult) {
if result.Conflict {
// 1. Create a new task for a Polecat
title := fmt.Sprintf("Resolve merge conflicts: %s", mr.Title)
task, _ := e.beads.Create(beads.CreateOptions{
Title: title,
Type: "task",
Priority: mr.Priority - 1, // Boost priority!
})
// 2. Block the MR until the task is done
e.beads.AddDependency(mr.ID, task.ID)
}
}
Why is this cool? The Refinery doesn't stop working. It pushes the problem MR aside, blocks it, and moves on to the next MR in the queue. The workflow never stalls.
By having a Refinery, you can have 10, 20, or 50 agents working simultaneously. As long as the Refinery is running, they will queue up neatly and land their code safely.
Now that our code is safely merged, the agents need a way to send messages about their success (or failure) to the rest of the town.
Next Chapter: Mail Router (Communication Bus)
Generated by Code IQ