A hands-on trial of multi-agent tooling, one actual coding agent, and a surprisingly good Mad Max fan website.
The prompt was simple: let's try Gas Town by building a Mad Max fan website that aggregates post-apocalyptic news.
The result was Wasteland Wire: an unofficial fan site with oversized condensed typography, a rust-and-sand palette, and an original road-to-the-sunset illustration. Underneath the wasteland aesthetic, it pulls real headlines from public RSS feeds. No invented articles. No decorative fake news cards.
Try the live site: wasteland-wire.onrender.com · Source: bdmorin/wasteland-wire. It runs on Render's free tier, so an idle instance can take about a minute to wake.
The website turned out well. Getting the agent orchestration running was less straightforward. This post records both sides of that experience—without treating workarounds as fixes.
First: what does Gas Town actually do? #
At one point during setup, the obvious question came up: is gt just a proxy that executes Claude?
Not quite. Claude does the coding; Gas Town organizes the work around it.
Gas Town's Go CLI creates managed project repositories called rigs, allocates worker Git worktrees, records assignments in Beads/Dolt, launches external coding runtimes in tmux, and provides messaging and lifecycle tools. Workers are called polecats. Optional AI roles coordinate tasks, monitor workers, or process the merge queue.
For this trial, the flow was:
Create a tracked issue
→ gt sling assigns it and creates a worker worktree
→ Claude implements the website
→ the worker records completion
→ a human-facing browser check verifies the result
That distinction matters: durable task state is not the same thing as durable model memory. And a separate worktree or tmux session is not a security sandbox.
We deliberately ran a narrow trial: one task, one executing worker, no automatic infrastructure boot, and no merge queue. There were failed worker-start attempts before the successful run, but this was not a multi-worker coordination benchmark.
The website we asked for #
Wasteland Wire had to be a working application, not just a visual mockup:
- Real RSS aggregation for Mad Max/post-apocalyptic entertainment, climate/environment, and science/resilience.
- Publisher names, publication dates, source links, and short plain-text excerpts.
- Category filters, search, newest/oldest sorting, and manual refresh.
- Honest , empty, and upstream-error states.
- Partial failures that do not erase headlines from working sources.
- Original artwork and an explicit unofficial-fan disclaimer.
- No paid news API or API keys.
The implementation uses a plain Node HTTP server, rss-parser, and vanilla HTML/CSS/JavaScript. Fonts are self-hosted. There is no frontend build pipeline.
Science and climate reporting are labelled as real-world news, not falsely presented as proof that an apocalypse is imminent.
Actually driving Gas Town: the commands and prompts #
Here is the operational story missing from a simple architecture diagram. Commands in this section were executed inside the container unless prefixed with docker. Output excerpts are abridged; task and worker names are from the actual run.
From “build a fan site” to a durable assignment
We did not ask a Mayor to plan the project. The human-facing assistant wrote the product brief and explicitly dispatched one worker. That brief lived at /gt/website-brief.md, was initially committed as the project's README, and is now preserved in docs/BRIEF.md.
Its opening instructions were:
Build and run an unofficial Mad Max fan website that aggregates real post-apocalyptic-themed news. This is a real working website, not a mockup. Theme: cinematic desert-industrial editorial, sand/charcoal/rust, oversized condensed headlines, strong typography, responsive layout, readable contrast. No copyrighted movie stills or claims of official affiliation. Original CSS/SVG artwork is preferred.
The brief supplied the functional requirements, verification criteria, one already-checked feed, and concrete boundaries: no external repository writes, deployment, GitHub PRs, additional agents, or merge to main. It also told the worker not to fabricate news and to verify actual feed entries before choosing sources.
We gave the project a disposable local bare origin, then registered it:
git init --initial-branch=main /gt/trial-source
cp /gt/website-brief.md /gt/trial-source/README.md
git -C /gt/trial-source add README.md
git -C /gt/trial-source commit -m "Define Wasteland Wire website requirements"
git clone --bare /gt/trial-source /gt/trial-origin.git
gt rig add wasteland file:///gt/trial-origin.git \
--local-repo /gt/trial-source --prefix ww --branch main
Gas Town created a shared bare repository, a mayor clone, a refinery worktree, a Beads directory, and agent bookkeeping. Creating those directories did not mean those AI roles were running.
The actual task creation command, run from the rig root, was:
bd create --json \
--title 'Build and run Wasteland Wire Mad Max fan news website' \
--type task --priority 2 \
--description 'Implement all requirements in README.md and /gt/website-brief.md. Real RSS aggregation, polished responsive UI, filters/search/refresh, source attribution and honest failure states. Use Node.js. Run and verify the server; preserve work on a feature branch, no merging or deployment. Do not launch additional agents.'
It returned ww-bx2. That ID—not a conversation floating in a terminal—became the assignment we could inspect, reassign, and eventually close.
Slinging the task: more than launching Claude
gt sling ww-bx2 wasteland --agent claude \
--no-boot --no-convoy --no-merge --dry-run
gt sling ww-bx2 wasteland --agent claude \
--no-boot --no-convoy --no-merge
The dry-run previewed the orchestration: instantiate mol-polecat-work, bond it to ww-bx2, update the issue to hooked with a worker assignee, and inject a startup prompt.
The real dispatch created worker rust, allocated its worktree, instantiated workflow ww-wisp-cad, attached the issue, and enabled no-merge mode. In other words, the coding session started inside an already assigned project/workflow context.
The flags mattered:
--no-boot: do not wake the rig's Witness/Refinery infrastructure.--no-convoy: do not create an automatic work-tracking convoy for this single task.--no-merge: preserve the feature branch for review instead of submitting it to the merge queue.
None is a security sandbox or an enforced spending cap.
What was the priming prompt?
The observed interactive Claude launch included Gas Town's role settings and an initial message whose actionable text was:
Run `gt prime --hook` and begin work on your hook.
Its generated banner identified the worker and rig. We are not reproducing the entire generated role document here; the key is that the startup message did not contain the whole website specification. It directed the agent to recover its role, attached assignment, and workflow from Gas Town.
The successful headless worker chrome actually executed these commands, as recorded in its transcript:
gt prime --hook 2>&1 | tail -150
gt prime --hook 2>&1 | grep -n "Step\|ww-bx2" | head -40
bd show ww-bx2
Those output-trimming pipelines were the worker's choices, not prerequisites for priming. The core operation was gt prime --hook.
The prime output contained the hooked issue, attached mol-polecat-work formula, and eight workflow steps:
- Load context and verify assignment.
- Set up working branch.
- Implement the solution.
- Commit all implementation changes.
- Self-review changes.
- Build and sanity check.
- Pre-merge rebase verification.
- Submit work and self-clean.
The output also directed the worker to inspect bd show ww-bx2, explained how to preserve findings in issue notes, and specified the eventual gt done handoff. Build/test gate variables were empty for this newly created rig; we had not configured a pre-existing project test command. The worker added and ran the site's own checks rather than inheriting a configured gate suite.
One revealing rough edge: the generic formula text described submitting to the merge queue even though the actual assignment had no_merge: true. The effective no-merge behavior was confirmed by the completion state, not by assuming every paragraph of the generic prompt reflected that override.
Watching and recovering the worker
We observed the actual session rather than trusting a successful dispatch:
gt peek wasteland/rust 70
gt session status wasteland/rust
That is how we discovered Claude was in onboarding instead of building the site. We attempted to advance it using the supported messaging interface:
gt nudge wasteland/rust '' --mode immediate
This was an onboarding workaround attempt, not an implementation instruction. It did not solve interactive startup. We stopped/restarted sessions while investigating and ultimately registered the headless runtime described below.
Re-dispatch retained the same task ID:
gt sling ww-bx2 wasteland --agent claude-build \
--force --no-boot --no-convoy --no-merge
The output made the state transition explicit:
Created polecat: chrome
Bead already hooked to wasteland/polecats/rust, forcing reassignment...
Sent LIFECYCLE:Shutdown to wasteland/witness for rust
Burning 1 stale molecule(s) from previous assignment: ww-wisp-cad
Formula wisp created: ww-wisp-p8v
Formula bonded to ww-bx2
Work attached to hook (status=hooked)
No-merge mode enabled (work stays on feature branch)
Sending that lifecycle message did not mean a Witness was running to act on it; we supervised the trial ourselves. Once chrome was executing, gt peek wasteland/chrome exposed its streamed tool activity. This is the useful orchestration distinction: assignment and workflow history survived a failed runtime launch and explicit reassignment.
Completion had its own recovery step
The worker rebased onto origin/main, ran npm run check, and invoked:
gt done --pre-verified --target main
The first attempt refused completion because it detected uncommitted files. Its attempted auto-commit failed. The worker inspected Git state, added Gas Town-only workspace paths to .gitignore (.beads/, .claude/, .runtime/, and root CLAUDE.md), committed that exclusion, and retried the same completion command.
This final remediation was absent from the initial account: the implementation was done, but runtime scaffolding still interfered with the clean-worktree completion check. Ignoring that scaffolding kept it out of the public website export.
We checked the durable result:
bd show ww-bx2
gt polecat status wasteland/chrome
The issue was CLOSED, with close reason “No-merge work completed; merge queue skipped.” Chrome was done, its session stopped, and its branch remained polecat/chrome/ww-bx2+murp42sq.
That is the end-to-end Gas Town story for this run: brief → bead → formula-backed assignment → primed worker → explicit recovery/reassignment → committed implementation → recorded no-merge completion. The remaining setup details below explain why getting to that path took repairs.
The setup: where the friction was #
1. Install the tools—but distinguish release from checkout
The initial machine had Git, Go, tmux, SQLite, Claude, Homebrew, and Docker tooling, but no gt, bd, or dolt on PATH.
brew install gastown
Homebrew installed Gas Town 1.1.0, Beads 1.3.1, and Dolt 2.4.0. Version and help commands worked.
However, the inspected checkout declared 1.2.1. Installing a packaged release is not testing that source tree. We built the repository's Docker image to exercise the checkout instead.
The container build took roughly 399 seconds on this run. Because Git metadata was excluded from the image build context, the resulting CLI reported a dev version label. That label alone does not identify a release.
2. Start the database explicitly
The container had an initialized headquarters, but gt dolt status reported that Dolt was not running.
gt dolt start
That started the isolated SQL server. We did not establish why it was stopped after bootstrap; an observed symptom is not a root-cause diagnosis.
3. Respect the schema-migration guard
Creating the website rig exposed a Beads compatibility issue: 17 pending migrations, from schema v49 to v66. Automatic migration was refused because the database was served through a shared Dolt server.
That refusal protects other clients: promoting the schema can lock out older binaries. In our case, the server was newly created and isolated, so an explicit migration was appropriate.
bd migrate schema
The command's working directory mattered. An attempt from the mayor clone reported no Beads database; running from the rig root verified schema v66. Task creation then succeeded.
This was a useful reminder: a working bd version command says nothing about whether bd can write to your actual database.
4. Account for bind-mount ownership
Git rejected the HQ bind mount as having dubious ownership. We added the exact /gt directory to Git's safe-directory list inside the disposable container account.
We did not disable that protection globally on the host.
5. Authentication does not cross the container boundary
Claude was authenticated on the host, but not inside the container. We did not copy host credentials.
The interactive login had to happen in a real terminal:
docker exec -it gastown-sandbox claude auth login
An automation-side attempt using -it failed because stdin was not a terminal. A non-TTY attempt produced an OAuth code prompt but was not suitable for unattended completion.
After the user's login, claude auth status reported success.
6. Authenticated is not the same as ready
The first gt sling created the worker and hooked the issue correctly. But the actual Claude session showed its first-run theme selector, then a login-selection screen—not task execution.
Advancing the wizard through Gas Town's messaging interface did not resolve the problem. We also attempted an onboarding-config repair. That uncovered another operational trap: docker cp can leave a configuration file owned by the wrong user. The agent encountered EACCES until ownership was corrected.
An orphaned login process from an earlier attempt was also found and stopped. We did not prove it caused the configuration rewrites observed during these attempts.
Even after the configuration was readable, interactive worker startup still failed with startup blocked: no tmux server running. The exact cause of the interactive exit remained unresolved.
7. Prove inference separately, then use a headless runtime
Before continuing, we tested authenticated Claude inference directly from the assigned worktree:
claude -p 'Reply with READY only.' --dangerously-skip-permissions
It returned READY. The same smoke check worked with Gas Town's settings file.
We then registered a headless runtime through Gas Town:
gt config agent set claude-build \
'claude -p --verbose --output-format stream-json --dangerously-skip-permissions'
Security note: automatic tool approval is appropriate here only because this was a disposable, isolated trial. It is not a recommendation for untrusted code or a credential-rich production environment.
The assignment was re-dispatched to a fresh worker with:
gt sling <task-id> wasteland --agent claude-build \
--force --no-boot --no-convoy --no-merge
This time the worker executed: streamed tool activity appeared, packages were installed, and website files were written.
The dispatch command still timed out after 90 seconds. The worker continued running. A mismatch between headless output and interactive readiness detection was our working interpretation—not a proved diagnosis.
The important distinction: headless execution worked; interactive startup was not fixed.
The result: a real feed, not just a nice screenshot #
The worker completed the task, recorded it as closed, and preserved the result on a feature branch. The merge queue was skipped as requested.
The website uses 11 fixed HTTPS feeds, including Google News searches, The Guardian, ScienceDaily, Yale Environment 360, Inside Climate News, Ars Technica, and Phys.org. Candidate feeds that were empty, malformed, noisy, or unavailable were rejected rather than silently replaced with fake data.
The server bounds feed fetches with a timeout and byte limit. Feed text is rendered as plain text; unsafe links are dropped. A failed source can retain its previous headlines, explicitly marked as cached copies.
We exercised the actual failure path during verification:
- The first host fetch succeeded for 10 of 11 sources, yielding 139 headlines. Phys.org timed out.
- The page named the failed source and kept the successful feeds visible.
- Manual refresh subsequently succeeded for all 11 sources, yielding 154 headlines.
Those counts are observations from this run, not promises about future upstream availability.
Verification beyond the worker's report #
The container lacked a library needed for a real headless browser. The worker exercised frontend behavior with jsdom and reported that visual verification still needed to happen.
We then ran the website from the bind-mounted worktree on the host and checked it in real Chromium:
- Desktop layout at 1440px and mobile layout at 390px.
- Category filtering and a Furiosa search returning nine headlines.
- No-match empty state and clear-filter recovery.
- Oldest-first sorting with ascending publication dates.
- Refresh controls disabled while fetching.
- Real source links and API responses.
- No browser errors observed during the exercised flows.
- Mobile document width matched the viewport, with no horizontal overflow observed.
npm run check also passed syntax checks and all 30 deterministic tests, covering parsing, safe URLs, bounded fetches, partial failure, stale caches, filtering, and HTTP behavior.
The point wasn't to accept “the agent said it worked.” We ran the resulting application and observed its behavior.
What this trial did—and did not—prove #
It proved a useful single-worker path: Gas Town tracked the task, allocated the worktree, launched an external runtime, and recorded completion. The coding agent produced a working, good-looking website.
It did not test multi-worker coordination, unattended recovery, or automatic merge processing. For one task, much of the orchestration benefit is modest compared with running Claude directly. Public hobby hosting was added afterward, as described below; that is not evidence of production hardening.
The feed also has editorial limitations. Some Guardian stories are visibly dated archive entries. Keyword searches occasionally match articles using “Mad Max” metaphorically rather than covering the films. The cache is in memory and resets on restart.
Where to host it simply #
The app needs a Node backend for RSS aggregation, so GitHub Pages alone is not a fit without changing the architecture.
The website source is public at bdmorin/wasteland-wire, with a free-tier render.yaml Blueprint. The user authorized Render and deployed that Blueprint; the dashboard reported the service live. The site is now available at wasteland-wire.onrender.com.
We checked the public deployment in real Chromium: the page and API returned 139 headlines from 10 of 11 feeds, correctly reporting a Phys.org connection failure while keeping other sources visible. Category filtering and a Furiosa search worked (eight matches on that public fetch); the mobile document width matched its 390px viewport, and no browser errors were observed in those flows. Feed/search counts vary with upstream responses, so the earlier local nine-match observation is not an invariant.
The Render web service uses:
| Setting | Value |
|---|---|
| Build command | npm ci |
| Start command | npm start |
| Environment | HOST=0.0.0.0 |
| Port | Use Render's supplied PORT |
| Health check | /api/health |
The existing server already supports HOST and PORT; Gas Town, Dolt, and Claude are not needed to host the finished site.
Render's free service is suitable for a hobby preview, but it sleeps after 15 minutes without traffic and can take about a minute to wake. Its usage limits apply, and restarting loses the in-memory news cache. Deploy your own copy.
The takeaway #
Install status, database compatibility, authentication, runtime readiness, and application correctness are separate checks. Passing one does not imply the others.
The best part of this experiment was the final website. The most useful part was keeping an honest remediation trail: what failed, what changed, what was observed, and what remained unresolved.
We used Gas Town to build a wasteland. The scenery was excellent. The road there needed a few repairs.