Thanks for your interest in contributing! Gas Town is experimental software, and we welcome contributions that help explore these ideas.
- Fork the repository
- Clone your fork
- Install prerequisites (see README.md)
- Build and test:
go build -o gt ./cmd/gt && go test ./...
We use a direct-to-main workflow for trusted contributors. For external contributors:
- Create a feature branch from
main - Make your changes
- Ensure tests pass:
go test ./... - Submit a pull request
Never create PRs from your fork's main branch. Always create a dedicated branch for each PR:
# Good - dedicated branch per PR
git checkout -b fix/deacon-startup upstream/main
git checkout -b feat/auto-seance upstream/main
# Bad - PR from main accumulates unrelated commits
git checkout main # Don't PR from here!Why this matters:
- PRs from
mainaccumulate ALL commits pushed to your fork - Multiple contributors pushing to the same fork's
maincreates chaos - Reviewers can't tell which commits belong to which PR
- You can't have multiple PRs open simultaneously
Branch naming conventions:
fix/*- Bug fixesfeat/*- New featuresrefactor/*- Code restructuringdocs/*- Documentation only
- Follow standard Go conventions (
gofmt,go vet) - Keep functions focused and small
- Add comments for non-obvious logic
- Include tests for new functionality
Gas Town follows two core principles that shape every contribution. Understanding these will save you (and reviewers) time.
Go provides transport. Agents provide cognition.
Gas Town's Go code handles plumbing: tmux sessions, message delivery, hooks,
nudges, file transport, and observability primitives (like bd show --json).
All reasoning, judgment calls, and decision-making happen in the AI agents via
molecule formulas and role templates.
This means:
- No hardcoded thresholds in Go. Don't write
if age > 5*time.Minuteto decide if an agent is stuck. Expose the age as data and let the agent decide. - No heuristics in Go. Don't write detection logic that pattern-matches agent behavior. Give agents the tools to observe, and let them reason.
- Formulas over subcommands. If the feature is "detect X and do Y," it's
probably a molecule step, not a new
gtsubcommand.
The test: Before adding Go code, ask yourself — "Am I adding transport or cognition?" If the answer is cognition, it should be a molecule step or formula instruction instead.
For the full rationale, see Zero Framework Cognition.
Gas Town bets on models getting smarter, not on hand-crafted heuristics getting more elaborate. If an AI agent can observe data and reason about it, we expose the data (transport) rather than encoding the reasoning (cognition). Today's clumsy heuristic is tomorrow's technical debt — but a clean observability primitive ages well.
Examples:
| Good (transport) | Bad (cognition in Go) |
|---|---|
gt nudge <session> "message" |
Go code deciding when to nudge |
bd show --json exposing step status |
Go code deciding what step status means |
tmux has-session checking liveness |
Go code with hardcoded "stuck after N minutes" |
Good first contributions:
- Bug fixes with clear reproduction steps
- Documentation improvements
- Test coverage for untested code paths
- Small, focused features
For larger changes, please open an issue first to discuss the approach.
- Use present tense ("Add feature" not "Added feature")
- Keep the first line under 72 characters
- Reference issues when applicable:
Fix timeout bug (gt-xxx)
Run the full test suite before submitting:
go test ./...For specific packages:
go test ./internal/wisp/...
go test ./cmd/gt/...Integration tests (tagged //go:build integration) require external resources
that may not be available in every environment. Use the helpers in
internal/testutil to skip gracefully when prerequisites are missing:
| Helper | When to use |
|---|---|
testutil.RequireDoltContainer(t) |
Test needs a running Dolt SQL server (starts a Docker container) |
testutil.StartIsolatedDoltContainer(t) |
Test needs its own isolated Dolt instance (per-test container) |
testutil.RequireTownEnv(t) |
Test needs a live Gas Town workspace (checks workspace.FindFromCwd + rigs.json); returns root path |
requireDoltServer (in internal/cmd) is a local wrapper around
testutil.RequireDoltContainer used by the cmd package's integration tests.
When to use which guard:
- Tests that connect to Dolt (create databases, run SQL) →
RequireDoltContainerorStartIsolatedDoltContainer - Tests that need a real Gas Town directory tree (shell out to
gt/bdwith workspace detection) →RequireTownEnv - Tests that create their own temporary town via
t.TempDir()→ no guard needed (they are self-contained)
For packages with many Dolt-dependent tests, prefer adding
testutil.EnsureDoltContainerForTestMain() in a TestMain function so all
tests in the package share a single container.
Releases are cut from tags of the form vX.Y.Z. See RELEASING.md
for the full workflow. One guardrail to know about:
make check-version-tagverifies theVersionconstant ininternal/cmd/version.gomatches the tag at HEAD. The release workflow runs this before GoReleaser and fails the release on mismatch. Prevents recurrence of #3459. Run it locally after bumping if you want to catch drift before pushing the tag.
Open an issue for questions about contributing. We're happy to help!