A stand-alone, Ollama-compatible proxy that dynamically fetches and serves free models from Google Gemini API, Cloudflare Workers AI, Cerebras, OpenRouter, NVIDIA (NIM), and Ollama Cloud. Runs locally on port :11434, intercepting requests to the OpenAI-compatible endpoint (/v1/chat/completions) and the Ollama native model listing endpoint (/api/tags).
Use it standalone with any OpenAI-compatible client, or as the LLM backend for Gas Town multi-agent orchestration.
-
Cloudflare Workers AI (direct): Free-tier models (Llama 3.3 70B, Qwen 2.5 Coder 32B) via
CLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_API_TOKEN— no OpenRouter markup. Added tocloudflareModelsinmodels.yaml. -
Paid model fallback: When all free models (including Cloudflare, Gemini, NVIDIA) are in cooldown and
--allow-paidis set, the proxy falls back to paid models instead of returning 503. -
Cloud-first local GPU: With
--allow-local-openai, capable cloud models (70B NVIDIA, OpenRouter free tiers, large Cerebras, etc.) are tried first;localOpenAI(llama-server on:8080) is last-resort fallback only when cloud routes fail or are in cooldown. -
Weak-cloud blocking:
blockSmallCloudWhenLocalGPUinmodels.yamlskips nano/mini/8B cloud models when local GPU mode is on — so fallback does not mean “downgrade to 8B.” -
Config-only model IDs: Routing lists, role prepends, compat model stubs, score boosts, and model-name classification heuristics live in
models.yaml— not hardcoded in Go. -
Request sanitization: Anthropic
tools/systempayloads are normalized before upstream forwarding (sanitizeBody). -
Faster default tests:
go test ./...runs unit and in-process httptest suites (including Gemini direct routing tests); live proxy tests requireFREERIDE_INTEGRATION=1. Thescratch/tree is dev-only (//go:build ignore) and is not part of the test run. -
Google Gemini API (direct): Free-tier Flash models via
GEMINI_API_KEYandgeminiModelsinmodels.yaml— no OpenRouter markup. Gas Town polecat defaults togt-agent-gemini(google/gemini-3.5-flash).
Earlier (v1.2.0): headless gt-agent, NATS transport, Proxy-Magic tool extraction, strict zero-cost mode (503 when free tier exhausted).
- Cerebras budget / performance (from
models.yaml) - Gemini API direct (
geminiModelsinmodels.yaml, requiresGEMINI_API_KEY) - Groq API direct (
groqBudget/groqPerformanceinmodels.yaml, requiresGROQ_API_KEY) - Role prepends (
rolePrepend, when--allow-paid) — tried before the role'soriginalModelfor roles inrolePrependBeforeOriginal(defaultarchitect,planner,qa); for other roles (e.g.polecat) theoriginalModelstays first and prepends are fallback - Cloudflare Workers AI direct (
cloudflareModelsinmodels.yaml, requiresCLOUDFLARE_ACCOUNT_ID+CLOUDFLARE_API_TOKEN) - Reliable free + NVIDIA lists, Ollama cloud, original model
- Curated paid (
curatedPaid, when--allow-paid+ complex) - IDE bridges (
--allow-ide) - Local llama-server (
localOpenAI,--allow-local-openai) — after capable cloud
- Go 1.18+ (for building from source)
- A Cerebras API key (Optional, for fastest inference)
- A Groq API key (Optional, for fast inference fallback)
- A Gemini API key (optional, for free Google Flash models via
geminiModelsinmodels.yaml) - A Cloudflare API token (optional, for free Workers AI models via
cloudflareModelsinmodels.yaml) - An OpenRouter API key (for free OpenRouter models)
- An NVIDIA API key (for highest-performance free NIM models)
- An Ollama API key (for Ollama Cloud models like Qwen3 480B and DeepSeek V4)
This repository includes Gas Town as a git submodule at gastown/. The submodule is pinned to the stevef1uk/gastown fork (see .gitmodules).
A plain git clone does not download submodule contents — gastown/ will be empty until you initialize it.
git clone --recurse-submodules https://github.com/stevef1uk/freeride.git
cd freerideFrom the freeride repo root:
git submodule update --init --recursivetest -f gastown/go.mod && echo "gastown submodule OK"
cd gastown
make install # builds gt, gt-agent, … → ~/.local/binEnsure ~/.local/bin is on your PATH. After you change the submodule commit or pull freeride updates, refresh the submodule and reinstall:
git pull
git submodule update --init --recursive
cd gastown && git pull && make installTown root (~/gt) is separate: create it with gt install / gt rig add per Gas Town docs. Freeride only supplies the proxy; the submodule supplies the gt and gt-agent binaries and orchestrator templates synced by make install.
If you are setting up a new machine, you can build, start, and test the entire stack (Freeride proxy + Gas Town town + ping_rig e2e workflow) with one command.
IMPORTANT: Configure API keys in .env first. The proxy will not route cloud models without them.
-
Clone with the Gas Town submodule (see Cloning freeride), then configure keys:
cp .env.template .env # Edit .env — GEMINI_API_KEY, OPENROUTER_API_KEY, etc. -
Install host dependencies once (checked automatically before
make do_it_all):Tool Why Install Docker (daemon running) gt upstarts NATS ingt-natsDocker Desktop Dolt 1.82+ Beads SQL on :3307brew install doltor dolthub/doltGo 1.18+ Build freeride + gastown go.dev/dl Python 3 ping_rigpytest e2ebrew install python@3.12or python.orgcodeindex Implement-bead blast radius ( rig-flowpre_run)pip install codeindex— see Codeindex. Skip check only withGT_CODEINDEX=0.Git Rigs and checkpoints git-scm.com Optional preflight only:
make check-do-it-all-deps
-
Run the all-in-one setup:
make do_it_all
This will:
- Build and start Freeride on
:11434(waits until/v1/modelsresponds) - Build
gt/gt-agentfromgastown/and rungt install ~/gt - Run
gastown/e2e_workflow_test.shwith hardened bootstrap scripts underscripts/:gt down/gt up(starts Dockergt-nats, Dolt, daemon, orchestrator, rig agents)wait-for-gt-stack.sh --with-orchestrator— polls Freeride, Dolt, NATS healthz, and a single orchestrator MCPgt rig restart ping_rigafter the stack is readygt rig spec-index ping_rigso project_setup uses the Python stack from SPEC/architecture (not a generic Go scaffold)
- Build and start Freeride on
-
First run / slow Docker: Pulling
nats:latesthappens insidegt up(Docker blocks until the image is local). That can take several minutes with no extra output — normal. Aftergt upreturns, the wait script allows up to 120s (default) for NATS health and orchestrator readiness. On a slow network, set a longer timeout beforemake do_it_all:export WAIT_TIMEOUT_SEC=300 make do_it_allOptional: wait for the stack manually after a partial run:
export GT_ROOT=~/gt bash scripts/wait-for-gt-stack.sh --with-orchestrator
-
Stop local Ollama before step 3 if it uses port 11434 (
pkill ollamaor quit the menu-bar app on macOS). -
Fresh or deleted
~/gt: Safe to remove the town and re-run; bootstrap clears duplicategt orchestrator runprocesses beforegt upand does not kill the sole orchestrator immediately aftergt up.
See Gas Town Integration — make do_it_all bootstrap for troubleshooting (NATS, orchestrator, project_setup stack).
-
Build:
make build # Or manually: go build -o freeride . -
Configure API keys in a
.envfile in the directory where you start Freeride:cp .env.template .env # edit .env — set keys for the providers you use in models.yamlOPENROUTER_API_KEY=sk-or-v1-... GEMINI_API_KEY=... # from https://aistudio.google.com/apikey NVIDIA_API_KEY=nvapi-... OLLAMA_API_KEY=1b18... CEREBRAS_API_KEY=csk-... GROQ_API_KEY=gsk_... CLOUDFLARE_ACCOUNT_ID=... # from https://dash.cloudflare.com CLOUDFLARE_API_TOKEN=... # from https://dash.cloudflare.com/profile/api-tokens
On startup, Freeride reads
.envfrom the current working directory (KEY=valuelines;#comments are ignored). You can use shell exports instead if you prefer. -
Run (add
--allow-local-openaiif you uselocalOpenAIinmodels.yaml):make run # Or manually: ./freeride --debug --allow-local-openai > freeride_live.log 2>&1 &Stop local Ollama first if you have the Ollama app or
ollama serverunning — it uses the same default port 11434. Quit the menu-bar app (macOS) or runpkill ollamabefore starting Freeride, or Freeride will fail to bind and agents will hit the wrong server. -
Test:
make test # Or manually: go test ./... -v -count=1
To verify the proxy is running locally, use:
curl -s http://localhost:11434/v1/models | head -5
--debug: Verbose logging of requests, routing decisions, and API responses.--allow-paid: Allows paid models as fallback. Disabled by default (strict zero-cost mode).--allow-ide: AllowsideModelsentries inmodels.yaml(local IDE bridges) as a last-resort fallback. Disabled by default.--allow-local-openai: EnableslocalOpenAIfallback (after capable cloud) and appliesblockSmallCloudWhenLocalGPUfrommodels.yaml. Disabled by default. Does not force local over cloud — keep Gas TownLLM_MODELon a strong cloud id (e.g.meta/llama-3.3-70b-instruct).--check-models: Model doctor — verify everymodels.yamlid against live provider catalogs (OpenRouter, Groq, Cerebras, NVIDIA). See Model Health Check.
Freeride watches models.yaml for changes and reloads the config automatically — no restart required. A background poller checks file modification time every 2 seconds; when it detects a change, it re-reads and re-parses the file. Only cleanly-parsed configs are applied — a broken models.yaml leaves the previous working config in place.
Manual reload: Send SIGHUP to the Freeride process to trigger an immediate reload:
kill -HUP $(pgrep -x freeride)What reloads without restart:
rolePrepend/rolePrependBeforeOriginal(role fallback chains)reliableFree,curatedPaid,nvidiaReliable(model pools)geminiModels(direct Gemini routing)excludeModels,modelClassification(exclusions and heuristics)localOpenAI,ideModels,blockSmallCloudWhenLocalGPUfreeModelScoreBoost,compatModels
What requires a restart:
- CLI flags (
--allow-paid,--debug, etc.) .envAPI key changes (env vars are set once at startup)- Port (
PORTenv var)
Freeride can call Google’s Gemini API directly using its OpenAI-compatible endpoint. This is separate from OpenRouter’s google/* models: traffic goes to generativelanguage.googleapis.com, uses your AI Studio key, and qualifies for Google’s free tier (rate-limited; see Gemini API pricing).
OpenRouter no longer offers free Gemini Flash tiers; direct routing is the practical way to use Flash at zero token cost.
-
Create an API key at Google AI Studio.
-
Add to Freeride’s
.env(repo root, same directory you start./freeridefrom):GEMINI_API_KEY=your-key-here
GOOGLE_API_KEYis also accepted. Freeride trims whitespace around=(bashsource .envdoes not — useKEY=valuewith no space after=if you source manually). -
Ensure
geminiModelsinmodels.yamllists the routes you want (defaults ship in-repo):geminiModels: - id: "google/gemini-3.5-flash" # Freeride / client model name model: "gemini-3.5-flash" # exact name sent to Google - id: "google/gemini-2.5-flash-lite" model: "gemini-2.5-flash-lite"
-
Restart Freeride. On startup you should see:
Gemini API direct routing enabled.
If the key is missing, Gemini routes are skipped (other providers still work).
| Concept | Value |
|---|---|
| Client / Gas Town model id | google/gemini-3.5-flash (from geminiModels[].id) |
| Upstream API model | gemini-3.5-flash (from geminiModels[].model) |
| Upstream URL | https://generativelanguage.googleapis.com/v1beta/openai/chat/completions |
| Candidate tier | 0.15 — after Cerebras budget, before OpenRouter/NVIDIA fallbacks |
| Treated as “free” | Yes (for zero-cost mode; does not use OpenRouter pricing) |
Freeride tries candidates in order; the first non-cooldown upstream success wins. If the client requests google/gemini-3.5-flash, that id is tried early (Tier 0 when configured and not in cooldown).
Role: polecat — polecat is in massiveOnlyRoles; ids containing gemini pass the “massive model” filter. With default Gas Town config, polecat uses gt-agent-gemini so google/gemini-3.5-flash is the primary model, with Freeride fallbacks (e.g. google/gemini-2.5-flash-lite, then NVIDIA/OpenRouter 70B-class models) on errors or rate limits.
Role: architect / mayor / planner — Gemini is still in the waterfall unless excluded; set LLM_MODEL to google/gemini-3.5-flash on the agent profile to prefer it.
From the Freeride repo:
python3 scratch/test_gemini_key.pyExpect PASS lines for the configured models. A 400 API_KEY_INVALID means regenerate the key in AI Studio; 429 means free-tier quota — wait or use fallbacks.
Direct curl (replace $GEMINI_API_KEY):
curl -s "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" \
-H "Authorization: Bearer $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gemini-3.5-flash","messages":[{"role":"user","content":"Say GEMINI_OK"}],"max_tokens":32}'Fresh gt install maps polecat → gt-agent-gemini (LLM_MODEL=google/gemini-3.5-flash). See Gas Town Integration for full settings/config.json. Existing towns must add the gt-agent-gemini agent block and set "polecat": "gt-agent-gemini" in role_agents, then restart polecat sessions.
Edit models.yaml only — no Go changes required:
geminiModels:
- id: "google/gemini-2.5-flash" # what clients pass as model=
model: "gemini-2.5-flash" # must match a Google API model id
cooldown: "30s" # optional per-model cooldown overrideUse model ids from Google’s model docs. Not every id works on every account; test with scratch/test_gemini_key.py before relying on a route.
| Path | Key | Example id | Cost |
|---|---|---|---|
Direct (geminiModels) |
GEMINI_API_KEY |
google/gemini-3.5-flash |
Free tier (Google limits) |
| OpenRouter | OPENROUTER_API_KEY |
google/gemini-2.0-flash-001 |
Paid per token (or --allow-paid) |
Only ids listed under geminiModels use the direct API. Other google/* requests still go to OpenRouter.
- Thinking models:
gemini-3.5-flashmay use internal reasoning tokens; use adequatemax_tokensfor short replies in tests. - Tools: Gemini supports tool-style requests; Freeride does not apply NVIDIA-style
tool_choicestripping to direct Gemini routes. - Discovery: When the key is set,
google/gemini-*ids appear on/v1/modelsand/api/tagswithowned_by: google-gemini.
Freeride can call Cloudflare Workers AI directly using its OpenAI-compatible endpoint. This provides free inference for capable open-source models (Llama 3.3, Qwen 2.5, etc.) at zero token cost — separate from OpenRouter's paid tiers.
-
Create a Cloudflare account at dash.cloudflare.com.
-
Create an API token at Cloudflare API Tokens with Workers AI:Read permission.
-
Add to Freeride's
.env(repo root, same directory you start./freeridefrom):CLOUDFLARE_ACCOUNT_ID=your-account-id CLOUDFLARE_API_TOKEN=your-api-token
-
Ensure
cloudflareModelsinmodels.yamllists the routes you want (defaults ship in-repo):cloudflareModels: - id: "cloudflare/llama-3.3-70b" # Freeride / client model name model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast" # exact name sent to Cloudflare - id: "cloudflare/qwen-2.5-coder-32b" model: "@cf/qwen/qwen2.5-coder-32b-instruct"
-
Add Cloudflare models to
rolePrependinmodels.yaml(optional, for explicit routing):rolePrepend: polecat: - "cloudflare/llama-3.3-70b" architect: - "cloudflare/qwen-2.5-coder-32b"
-
Restart Freeride. On startup you should see:
Loaded ... Cloudflare direct models.
If credentials are missing or invalid, Cloudflare routes are skipped (other providers still work).
| Concept | Value |
|---|---|
| Client / Gas Town model id | cloudflare/llama-3.3-70b (from cloudflareModels[].id) |
| Upstream API model | @cf/meta/llama-3.3-70b-instruct-fp8-fast (from cloudflareModels[].model) |
| Upstream URL | https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions |
| Candidate tier | Tier 0 — direct model (like Gemini), tried before OpenRouter/NVIDIA fallbacks |
| Treated as "free" | Yes (Cloudflare Workers AI free tier; no token cost) |
Freeride tries candidates in order; the first non-cooldown upstream success wins. If the client requests cloudflare/llama-3.3-70b, that id is tried early (Tier 0 when configured and not in cooldown).
Paid fallback: When all free models (including Cloudflare) are in cooldown and --allow-paid is set, the proxy falls back to paid models instead of returning 503.
curl -s http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "cloudflare/llama-3.3-70b",
"messages": [{"role": "user", "content": "Say CLOUDFLARE_OK"}],
"max_tokens": 32
}'Expect a response with model cloudflare/llama-3.3-70b (or a fallback model if Cloudflare is in cooldown). A 401 means the API token is invalid or missing Workers AI permission.
Direct curl (replace credentials):
curl -s "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/v1/chat/completions" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"model":"@cf/meta/llama-3.3-70b-instruct-fp8-fast","messages":[{"role":"user","content":"Say CLOUDFLARE_OK"}],"max_tokens":32}'Edit models.yaml only — no Go changes required:
cloudflareModels:
- id: "cloudflare/llama-3.3-70b"
model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
- id: "cloudflare/qwen-2.5-coder-32b"
model: "@cf/qwen/qwen2.5-coder-32b-instruct"
cooldown: "30s" # optional per-model cooldown overrideUse model ids from Cloudflare Workers AI models. Not every id works on every account; test with a direct curl before relying on a route.
- Free tier: Cloudflare Workers AI has a generous free tier; see pricing.
- Tools: Cloudflare supports tool-style requests; Freeride forwards tool parameters as-is.
- Discovery: When configured,
cloudflare/*ids appear on/v1/modelsand/api/tagswithowned_by: cloudflare.
Freeride supports routing to Groq for fast inference, typically used as a fallback when Cerebras is unavailable or rate-limited.
- Create an API key at GroqCloud.
- Add it to Freeride's
.env:GROQ_API_KEY=your-key-here
- Ensure your
models.yamlincludes the Groq routes undergroqBudgetorgroqPerformance(defaults ship in-repo).
When configured, Freeride will prioritize Groq models as defined in your routing order (e.g., Tier 0.6).
For interactive terminal use with a TUI:
export OPENAI_BASE_URL="http://localhost:11434/v1"
export OPENAI_API_KEY="dummy"
opencode --model openai/gpt-4oPoint any client that supports OpenAI-compatible endpoints to:
- Base URL:
http://localhost:11434/v1 - API Key:
dummy(or any non-empty string) - Model:
gpt-4oorclaude-3-5-sonnet-20241022(see Power Model Spoofing below)
Freeride serves as the LLM backend for Gas Town multi-agent orchestration. The proxy runs on :11434; agents communicate via NATS on :4222.
Submodule required: Install the gastown/ tree first — see Cloning freeride (Gas Town submodule) above (git clone --recurse-submodules or git submodule update --init --recursive, then cd gastown && make install).
Freeride .env: Gas Town gt-agent sessions only need LLM_ENDPOINT / LLM_MODEL in settings/config.json (see below). Provider API keys live in Freeride’s .env — start the proxy from your Freeride repo (or any directory that contains a .env with GEMINI_API_KEY, OPENROUTER_API_KEY, NVIDIA_API_KEY, etc.). gt install does not create this file; copy .env.template → .env in the Freeride tree before gt up. Polecat implementation work expects GEMINI_API_KEY when using gt-agent-gemini (see Google Gemini API).
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Gas Town │────▶│ NATS │────▶│ gt-agent │
│ (Mayor, │ │ (port 4222) │ │ (headless) │
│ Witness, │ └──────────────┘ │ Drains nudges │
│ Deacon) │ │ Calls LLM │
└─────────────┘ │ Executes work │
│ Exits cleanly │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Freeride Proxy │
│ (port 11434) │
│ Routes to free │
│ Gemini/Cerebras│
│ Groq/OpenRouter│
│ NIM/Ollama │
└─────────────────┘
| Agent | Role | Transport | Use Case |
|---|---|---|---|
| gt-agent | Headless worker | NATS | Automated background tasks (Mayor, Deacon, Witness, Refinery, Polecats) |
| opencode | Interactive TUI | NATS | Interactive crew members requiring human oversight |
Create or update settings/config.json in your Gas Town project root:
{
"type": "town-settings",
"version": 1,
"session_transport": "nats",
"default_agent": "gt-agent-local",
"role_agents": {
"polecat": "gt-agent-gemini",
"mayor": "gt-agent-local",
"deacon": "gt-agent-local",
"witness": "gt-agent-local",
"refinery": "gt-agent-local",
"crew": "opencode"
},
"agents": {
"opencode": {
"command": "/home/YOURNAME/.opencode/bin/opencode",
"args": ["--model", "openai/gpt-4o"],
"env": {
"OPENAI_BASE_URL": "http://localhost:11434/v1",
"OPENAI_API_BASE": "http://localhost:11434/v1",
"OPENAI_API_KEY": "dummy",
"OPENCODE_PERMISSION": "{\"*\":\"allow\"}"
}
},
"gt-agent-local": {
"command": "/home/YOURNAME/.local/bin/gt-agent",
"args": [],
"env": {
"LLM_ENDPOINT": "http://localhost:11434/v1/chat/completions",
"LLM_MODEL": "meta/llama-3.2-11b-vision-instruct"
}
},
"gt-agent-gemini": {
"command": "/home/YOURNAME/.local/bin/gt-agent",
"args": [],
"env": {
"LLM_ENDPOINT": "http://localhost:11434/v1/chat/completions",
"LLM_MODEL": "google/gemini-3.5-flash"
}
}
}
}Fresh gt install defaults map polecat → gt-agent-gemini (google/gemini-3.5-flash via Freeride direct Gemini routing). Requires GEMINI_API_KEY in Freeride’s .env — see Google Gemini API. Existing towns: set "polecat": "gt-agent-gemini" in role_agents and add the gt-agent-gemini agent block above.
Key settings:
session_transport: Set to"nats"for NATS-based session management (no tmux required)default_agent:"gt-agent-local"for headless automated workrole_agents.crew:"opencode"for interactive TUI-based crew membersgt-agent-local.env.LLM_ENDPOINT: Points directly to Freeride's OpenAI-compatible endpoint
Default agent-to-model mappings live in gastown/freeride_models.json (applied at gt install and whenever DefaultFreerideAgents() / DefaultFreerideRoleAgents() are called). Edit this file to change which LLM each gt-agent profile uses — no recompile needed:
{
"agents": {
"gt-agent-fast": {
"model": "ollama/ministral-3:8b"
},
"gt-agent-mayor-fast": {
"model": "ollama/ministral-3:8b",
"extra_env": {
"LLM_TURN_TIMEOUT": "30s",
"GT_AGENT_CMD_TIMEOUT": "75s"
}
}
},
"role_agents": {
"polecat": "gt-agent-gemini",
"mayor": "gt-agent-local"
}
}The file is discovered at gastown/freeride_models.json relative to the working directory. Override the path with the FREERIDE_MODELS_CONFIG environment variable. If the file is missing or unreadable, compiled-in defaults are used as a fallback.
Freeride ships helper scripts used when FREERIDE_ROOT is set (as make do_it_all does):
| Script | Role |
|---|---|
scripts/check-do-it-all-deps.sh |
Fail fast if Docker (daemon), Dolt, Go, Python 3, Git, .env, submodule, or codeindex (unless GT_CODEINDEX=0) are missing; prints install links. Run via make check-do-it-all-deps or at start of do_it_all / e2e. |
scripts/wait-for-gt-stack.sh |
Poll Freeride (:11434), Dolt (:3307), NATS (:4222 + http://127.0.0.1:8222/healthz). --freeride-only before gt up; --with-orchestrator after gt up. |
scripts/ensure-gt-orchestrator-singleton.sh |
Stops duplicate gt orchestrator run processes only (leaves a single process alone). Run before gt down / gt up, not immediately after gt up. |
Environment variables:
| Variable | Default | Purpose |
|---|---|---|
WAIT_TIMEOUT_SEC |
120 |
Max seconds per wait phase (NATS, orchestrator). Use 300 when Docker may still be pulling or starting gt-nats on first boot. |
GT_ROOT / GT_DIR |
~/gt |
Town root for orchestrator checks |
FREERIDE_ROOT |
(set by Makefile) | Directory containing scripts/ for e2e bootstrap |
Docker image pull: docker run nats:latest is invoked synchronously during gt up. Until the pull finishes, gt up appears hung — that is expected. The wait script runs after gt up and only checks that ports and /healthz respond; it does not run docker pull itself.
Verify NATS after bootstrap:
curl -s http://127.0.0.1:8222/healthz
docker ps --filter name=gt-nats
cd ~/gt && gt mayor workflow listIf orchestrator wait loops at “0 orchestrator processes”: Ensure you are not running ensure-gt-orchestrator-singleton.sh right after gt up (e2e no longer does). Upgrade to the latest freeride scripts, or run cd ~/gt && gt orchestrator start.
# 0. Gas Town CLI from the submodule (once per machine / after submodule updates)
git submodule update --init --recursive
cd gastown && make install && cd ..
# 1. Ensure Freeride has API keys (.env in the directory you start it from)
cp .env.template .env # once, in the freeride repo; edit with your keys
# 2. Stop local Ollama if running (same port 11434), then start Freeride
pkill ollama 2>/dev/null || true # or quit the Ollama menu-bar app on macOS
./freeride --debug > freeride_live.log 2>&1 &
# 3. Ensure NATS is available (Docker or standalone)
# Docker: docker run -d --name gt-nats -p 4222:4222 nats:latest
# 4. Start Gas Town services (from your town root, e.g. ~/gt)
gt up
# 5. Check status
gt status
# 6. Assign work to a polecat (automatic via Mayor, or manual)
gt hook de-123 defender/polecats/obsidianFor structured rig delivery (SPEC → design → plan → implement → QA), Gas Town runs a central workflow FSM (rig-flow) instead of each agent self-dispatching via hooks and mail. Agents use NATS sessions with gt-agent --orchestrated; there is no tmux requirement when session_transport is "nats".
Required (install once per machine; most services are started by gt up):
| Dependency | Purpose | Notes |
|---|---|---|
| Docker | Runs NATS | gt up starts container gt-nats (nats:latest, ports 4222 client / 8222 monitor). Docker daemon must be running before gt up. First pull of nats:latest blocks inside gt up until complete; make do_it_all then waits up to WAIT_TIMEOUT_SEC (default 120s, use 300 on slow networks) for healthz on 8222. |
| Dolt 1.82+ | Beads / issue storage | SQL server on port 3307; started and managed by the Gas Town daemon. Install: brew install dolt (macOS) or dolthub/dolt. |
beads (bd) 0.55+ |
Issue CLI | Planner, polecat, and QA run bd create / bd list / bd close. Installed with Gas Town (make install or brew install gastown). |
| Git 2.25+ | Rigs, worktrees, checkpoints | Orchestrator commits on FSM transitions and pushes on completed. Worktree support required for polecats. |
| Go 1.18+ | Build gt / gt-agent |
From the gastown/ submodule: cd gastown && make install → ~/.local/bin. |
| Freeride proxy | LLM backend | Runs on :11434; start from this repo so .env API keys load. Stop local Ollama first (same port). Polecat default model uses Gemini direct (GEMINI_API_KEY in .env). |
Gas Town town (~/gt) |
Orchestrator + rig state | gt install ~/gt --git, then "session_transport": "nats" in settings/config.json. Templates sync to {townRoot}/orchestrator/. |
Rig toolchain (per project language — scaffolded during project_setup, not host prerequisites):
| Dependency | When |
|---|---|
Go (go, go mod, go test) |
Go rigs — module init/tidy in setup; implement + QA run tests |
| Python 3 + venv + pip | Python rigs — venv + requirements.txt in setup; pytest/unittest in implement + QA |
Optional host tools (same machine as the polecat NATS session; see Polecat host tools below):
| Tool | Purpose |
|---|---|
codeindex (pip install codeindex) |
Dependency blast radius in implement-bead prompts; disable with GT_CODEINDEX=0 |
| goimports | Auto-fix unused imports after native EDIT:/WRITE: on Go rigs |
| Standalone NATS | Only if you cannot use Docker — run nats-server on 4222 yourself; gt up still expects NATS reachable |
Not required for rig-flow: tmux (legacy transport only when "session_transport": "tmux").
Port conflict: Local Ollama (app or ollama serve) and Freeride both default to 11434. Use one or the other — quit Ollama before ./freeride, or agents will talk to the wrong listener.
Full documentation lives in the gastown submodule:
- Orchestrator (concept) — quickstart, QA outcomes, reset
- rig-flow operator notes — YAML hooks, timeouts, stall recovery
- Orchestrator (design) — implementation detail
Town root (~/gt or your GT_ROOT) is separate from this freeride repo. After submodule install, sync templates into the town:
cd gastown && make install # builds gt, gt-agent; copies orchestrator/templates + prompts
cd ~/gt # your town root
gt orchestrator sync --update-changedBring up services and the orchestrator MCP process (gt up starts NATS, daemon, and gt orchestrator run; PID in daemon/orchestrator.pid):
pkill ollama 2>/dev/null || true # Freeride needs :11434 — stop local Ollama first
./freeride --debug > freeride_live.log 2>&1 & # from freeride repo (API keys in .env)
cd ~/gt
gt up
gt orchestrator status # running PID + MCP ping
gt status -v # NATS sessions (e.g. te-<rig>-polecat), not tmuxStart a rig pipeline (example rig testgt3):
gt mayor workflow start rig-flow --rig testgt3
gt mayor workflow statusRig agents only when a workflow is running — use orchestrator-only mode to skip legacy town hq-architect / hq-qa / hq-polecat:
gt up --orchestrator-onlyPer-rig pipeline sessions (NATS) include te-<rig>-polecat, te-<rig>-architect, te-<rig>-qa, plus town hq-planner / hq-setup for planning and project setup. Tail logs under ~/gt/<rig>/polecat/typescript, etc. (see the concept doc table).
Incremental bug fixes: For existing source files, the polecat must use native EDIT: / WRITE: (or sed -i / patch as fallback — not full-file cat > … <<'EOF' rewrites). gt-agent enforces this automatically when the file is already on disk; new files still use heredoc.
Install on the same machine that runs gt-agent for rig-flow implementation (the per-rig polecat NATS session, e.g. ~/gt/<rig>/polecat/). These are not part of planner project_setup (module/venv only); they help the polecat during implement.
codeindex is optional. When the codeindex CLI is on PATH, gt-agent:
- Runs
refresh_codeindexat the start of each implementation task (rig-flowpre_run). - Builds or refreshes
{rig}/mayor/rig/codeindex.jsonfrom the profilelayout_root(e.g.linkshelf/). - Injects a Codeindex blast radius section into each implement-bead prompt (
codeindex impacton the active file).
One-time install:
pip install codeindex
# or: pipx install codeindex
codeindex --help # must succeed; gt-agent uses exec.LookPath("codeindex")Disable without uninstalling:
export GT_CODEINDEX=0 # polecat agent env in settings/config.json, or shell before gt upManual refresh (debugging a rig worktree):
export GT_ROOT=~/gt
RIG=testgt3
cd "$GT_ROOT/$RIG/mayor/rig"
codeindex analyze linkshelf --output codeindex.json
codeindex symbols linkshelf --inline --index codeindex.json
codeindex impact internal/api/handlers.go --index codeindex.jsonUse paths relative to the layout root passed to analyze (here linkshelf/, so internal/api/handlers.go not linkshelf/internal/...). If impact says the file is missing, re-run analyze after pulling polecat edits or check that layout_root in {rig}/mayor/rig/.gastown/workflow-profile.json matches your tree.
After updating gastown templates or this integration, sync into the town and reinstall binaries:
cd gastown && make install
cd ~/gt && gt orchestrator sync --update-changedMore detail: rig-flow operator notes — Codeindex (native EDIT tools section).
When goimports is on PATH, gt-agent runs it on the package after native EDIT:/WRITE: if verify reports unused imports (common on *_test.go while another bead owns production code).
go install golang.org/x/tools/cmd/goimports@latestOn each rig-flow FSM transition, gt orchestrator run may git commit dirty files in {rig}/mayor/rig/; on completed it **git push**es to origin. Polecat/QA do not push — the orchestrator does.
| Variable | Where to set | Effect |
|---|---|---|
GT_SKIP_WORKFLOW_GIT_COMMIT=1 |
Environment of gt orchestrator run (or parent of gt up auto-start) |
No checkpoint commits |
GT_WORKFLOW_SKIP_PUSH=1 |
Same | Still commit locally; no push on completed |
gt rig add / gt rig add --adopt append mayor/rig ignore rules (.beads/, *.db, codeindex.json, build server, QA progress JSON) so checkpoints stay source-only. For an existing rig: gt rig add <name> --adopt refreshes rules, then git rm -r --cached any junk still tracked. Details: gastown/docs/design/orchestrator.md.
Parked rigs do not start agents. If gt up reports skipped (rig parked), the rig was intentionally paused:
gt rig status testgt3
gt rig unpark testgt3
gt up --orchestrator-only
# or: gt rig start testgt3Useful checks when work stalls:
gt orchestrator status
gt mayor workflow status
export BEADS_DIR=~/gt/testgt3/.beads && cd ~/gt/testgt3/mayor/rig && bd list --status=in_progress
tail -f ~/gt/logs/orchestrator.logFull rig rewind (beads, instances, dev servers): bash gastown/scripts/reset-rig-orchestrator.sh --force from a checkout that includes the submodule (see gastown script header for GT_ROOT / RIG).
Rig deletions and registry writes are audited so a wipe (e.g. rm -rf of a rig or
gt rig remove mid-session) can be traced after the fact. Everything lives
outside the town under ~/.config/gt-watchdog/ (override with
GT_WATCHDOG_DIR) so rm -rf ~/gt cannot destroy the evidence.
| Artifact | Contents |
|---|---|
exec-audit.jsonl |
Every agent shell command verbatim (command, workdir, role, PID, outcome) from runOrchestratedCommand |
rigs-audit.jsonl |
Every rigs.json write with caller stack, PID/PPID, rig names + count (SaveRigsConfig) |
rig-canary.log |
inotify deletion/rename events for town rig paths, with best-effort actor PID |
.enabled |
Presence enables auditing; remove to disable (checked live by gt/gt-agent writers) |
Control script (systemd --user, needs XDG_RUNTIME_DIR for systemctl --user):
~/.config/gt-watchdog/rig-watchdog-ctl.sh status # enabled state, sizes, services
~/.config/gt-watchdog/rig-watchdog-ctl.sh enable # touch .enabled; start canary + purge timer
~/.config/gt-watchdog/rig-watchdog-ctl.sh disable # stop services; remove .enabled
~/.config/gt-watchdog/rig-watchdog-ctl.sh purge [days] # rotate/trim audit logs (default keep 7 days)Disk management: audit logs are capped at ~10MB each (writers stop appending
past the cap). A daily systemd timer (gt-watchdog-purge.timer) runs
rig_purge.sh, gzipping any log over the cap into *.gz archives and deleting
archives older than the retention window. rig-watchdog-ctl.sh purge 14 forces
a run with a 14-day window.
Source gating: internal/config/watchdog.go (WatchdogDir /
WatchdogEnabled / MaxAuditFileBytes) is used by both the internal/config
rigs-audit writer and cmd/gt-agent/orchestrated_audit.go exec-audit writer.
Test binaries (.test) and temp-town paths are skipped automatically.
Note: a hardlink mirror of rigs was tried and removed — it did not survive wipe-then-recreate cycles (the timer overwrote the pre-wipe snapshot with the empty recreated rig) and only duplicated session-log content already captured by the exec audit.
curl -s http://localhost:11434/v1/models | head -5tail -f freeride_live.log | grep -E "Attempting|\[LOCAL\]|succeeded|ERROR"
# Table of attempts vs completions (handles role= and [LOCAL] log lines):
python3 scripts/freeride_proxy_model_stats.py freeride_live.log
python3 scripts/freeride_proxy_model_stats.py freeride_live.log --roles --watch 5# Should show gt-agent processes for headless agents
ps aux | grep gt-agent | grep -v grep
# Should show opencode only for interactive crew
ps aux | grep opencode | grep -v grep| Symptom | Cause | Fix |
|---|---|---|
User not found (401) |
Invalid OpenRouter key | Generate a new key at openrouter.ai/settings/keys |
Insufficient credits (402) |
OpenRouter balance is $0 | Add credits at openrouter.ai/settings/credits |
Rate limited (429) |
Free model overloaded | Wait 10s or use NVIDIA models only |
No models available |
All models in cooldown | Check cooldowns.json and restart proxy |
404 Not Found |
Model no longer exists | Update models.yaml to remove deprecated models |
| Agent not starting | NATS unavailable | docker ps --filter name=gt-nats; curl http://127.0.0.1:8222/healthz; gt up |
make do_it_all stuck after NATS OK |
Orchestrator not ready | Wait for OK Orchestrator or WAIT lines; export WAIT_TIMEOUT_SEC=300; cd ~/gt && gt orchestrator start |
fetch_task error: no servers |
Agents before NATS | gt down && gt up; bash scripts/wait-for-gt-stack.sh --with-orchestrator; gt rig restart <rig> |
| project_setup runs Go on Python rig | Thin profile / dual prompt | gt rig spec-index <rig> --force; remove stray go.mod under mayor/rig/ |
| Proxy not responding | Freeride not running | Run ./freeride --debug > freeride_live.log 2>&1 & |
All model IDs and routing policy are in models.yaml — edit that file to add/remove models, role overrides, local GPU settings, or model-name classification heuristics. Go code only implements tier logic and provider prefixes.
Typical setup with llama.cpp llama-server on port 8080:
# Terminal 1 — llama-server (example)
cd ~/dev/llama.cpp/build/bin
./llama-server --hf-repo unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF \
--hf-file Qwen3-Coder-30B-A3B-Instruct-Q4_K_M.gguf \
-ngl 30 --no-mmap -c 8192 -fa on --host 0.0.0.0 --port 8080
# Get the upstream JSON model name (not the GGUF filename):
curl -s http://127.0.0.1:8080/v1/models | jq -r '.data[0].id'In models.yaml, localOpenAI.id is the route name clients may use; localOpenAI.model must match the server's /v1/models id:
localOpenAI:
- id: "local/qwen3-coder-30b"
endpoint: "http://127.0.0.1:8080"
model: "unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF" # from curl above
cooldown: "30m"Run Freeride with ./freeride --debug --allow-local-openai. Traffic uses cloud first; local GPU only when cloud is unavailable or cooled down.
# Freeride Model Configuration
massiveOnlyRoles: [architect, mayor, planner, polecat]
# Roles whose rolePrepend models are tried BEFORE their originalModel (--allow-paid only).
# Roles not listed keep their originalModel first and use rolePrepend as fallback (e.g. polecat).
rolePrependBeforeOriginal: [architect, planner, qa]
rolePrepend:
polecat: ["cerebras/gpt-oss-120b", "deepseek/deepseek-v4-flash"] # only with --allow-paid; deepseek tried first
architect: ["openai/gpt-5.6-luna", "cerebras/gpt-oss-120b"]
# Priority 0.15: Google Gemini API free tier (requires GEMINI_API_KEY)
geminiModels:
- id: "google/gemini-3.5-flash"
model: "gemini-3.5-flash"
- id: "google/gemini-2.5-flash-lite"
model: "gemini-2.5-flash-lite"
# Priority 0.1: Free/Ultra-cheap Cerebras (High speed, zero/low cost)
cerebrasBudget:
- "cerebras/llama3.1-8b"
- "cerebras/gpt-oss-120b"
- "cerebras/qwen-3-235b-a22b-instruct-2507"
# Priority 0.5: Paid Cerebras Performance (Used for COMPLEX only)
# Note: These require the --allow-paid flag to be used.
cerebrasPerformance:
- "cerebras/llama3.3-70b"
- "cerebras/llama3.1-70b"
# Priority 0.6: Groq (Used for fast inference when Cerebras fails or is missing)
# Note: Requires GROQ_API_KEY environment variable.
groqBudget:
- "groq/llama-3.1-8b-instant"
groqPerformance:
- "groq/qwen3-32b"
# Priority 1: Specifically requested reliable free models (OpenRouter)
reliableFree:
- "meta-llama/llama-3.3-70b-instruct:free"
- "deepseek/deepseek-v3:free"
# Priority 2: Reliable NVIDIA models
nvidiaReliable:
- "meta/llama-3.3-70b-instruct"
- "nvidia/llama-3.1-70b-instruct"
# Models that should be tried even if they are paid (if --allow-paid is set)
curatedPaid:
- "openai/gpt-4o-mini"
- "google/gemini-2.0-flash-001"
- "anthropic/claude-3.5-sonnet"
# Models to exclude even if they are free
excludeModels:
- "google/gemma-4-26b-a4b-it:free" # Currently broken 401
# Model-name classification heuristics (substrings matched on lowercase model ids).
# Markers joined with "+" require ALL substrings (e.g. "llama-3.2+70b").
modelClassification:
nvidiaChatPrefixes: ["nvidia/", "meta/", "google/", "mistralai/", "microsoft/", "deepseek/"]
nvidiaChatExcluded: ["embed", "safety", "guard", "clip", "vila", "riva", "calibration", "pixel", "neva"]
nvidiaChatMarkers: ["instruct", "nemotron", "chat", "coder"]
toolSupportMarkers: ["nemotron", "llama-3.3", "llama-3.2+70b", "deepseek", "qwen2.5", "qwen3"]
complexModelHints: ["sonnet", "gpt-4o", "opus", "o1-"]
openRouterExcluded: ["lyria", "liquid"]
cerebrasBudgetMarkers: ["8b", "preview", "oss", "qwen-3"]
weakModelMarkers: ["-1b-", "-3b-", "-7b-", "-8b-", "-11b-", "-12b-", "nano", "mini"]
blockSmallCloudWhenLocalGPU:
models: ["cerebras/llama3.1-8b", ...]
patterns: ["nano", "mini", "-8b", ...]
localOpenAI: [] # or see local llama-server section aboveVerified working models (NVIDIA):
meta/llama-3.3-70b-instruct✅meta/llama-3.2-11b-vision-instruct✅ollama/qwen3-coder:480b✅ollama/deepseek-v4-pro✅
The proxy auto-discovers models from OpenRouter, NVIDIA, and Ollama Cloud; geminiModels are listed when GEMINI_API_KEY is set. models.yaml prioritizes the listed models. Optional localOpenAI backends are not used unless you pass --allow-local-openai.
Providers retire and add models constantly — a dead id in a rolePrepend chain burns seconds on 404 fallbacks each request. Freeride ships a doctor that checks every id in models.yaml against each provider's live /v1/models catalog:
./freeride -check-models # report only
./freeride -check-models -probe-models # + tiny live ping per model
./freeride -check-models -apply-models # backup + propose fixes, prompt y/N
./freeride -check-models -apply-models -yes # non-interactive- Checks OpenRouter (public), Groq / Cerebras / NVIDIA NIM (needs
*_API_KEYin.env).ollama/*and local entries are skipped. - Reports
OK/MISSING/SKIP(known-bad inexcludeModelsor no key). ForMISSINGwith a confident same-vendor peer (≥4 shared name tokens, notweakModelMarkers, notembed/guard/safetyfamilies) it suggests a replacement. MISSINGwith no suggestion is offered for pruning — removing the dead entry lets the chain fall through to the next live candidate.-apply-modelssavesmodels.yaml.bak-YYYYMMDD-HHMMSSbefore any write, replaces exact quoted ids (comments preserved,excludeModelsuntouched), and asks for approval unless-yes. Restart freeride to reload.- Run weekly or when
freeride_live.logshows bursts of 404/429 fallbacks.
Freeride advertises compat model ids from compatModels in models.yaml (defaults include claude-3-5-sonnet-20241022 and gpt-4o). Select those names in clients that gate tools by model name. The proxy still routes to free or configured backends under the hood.
The proxy features transparent, proxy-level auto-recovery. If a request to a free model returns a rate limit (429) or server error (5xx), the proxy automatically intercepts the failure, places the failing model in cooldown, and retries the exact same request using the next highest-ranked free model.
State Persistence: Cooldowns are saved to cooldowns.json and persist across restarts.
Free models (like Llama 3.3 or Mixtral) often struggle with strict JSON-based tool calling, frequently choosing to "talk about" running a command instead of actually triggering the tool.
Freeride solves this by implementing Proxy-Magic:
- Markdown Extraction: If a model returns a markdown bash block (
bash ...), the proxy automatically converts it into arun_terminal_commandtool call. - Conversational Extraction: The proxy uses aggressive regex to catch conversational intent, such as:
- "I will now run
gt hook" - "I'm going to run
bd list"
- "I will now run
- Deduplication: If a model both talks about and uses the tool, the proxy deduplicates the requests to prevent double-execution.
This mechanism ensures that the Gas Town agent ecosystem remains fully autonomous even when running on zero-cost models.
GET /api/tags: Lists available free models (formatted as Ollama tags).POST /v1/chat/completions: Standard OpenAI chat endpoint.POST /v1/messages: Anthropic Messages endpoint (with full SSE translation).GET /v1/models: Returns the current ranked list of all available free models.
go test ./... -v -count=1Runs in milliseconds:
candidate_test.go— cloud-first candidate order, local last, weak-cloud blockinggemini_direct_test.go— Gemini API routing, polecat candidate order,/api/tagsdiscovery, auth env keyslocal_openai_test.go— local route hits upstream, mapsmodelfieldproxy_protocol_test.go— Anthropic tools, large system prompts, role routing (httptest mocks)
Requires Freeride listening (default http://localhost:11434):
./freeride --debug --allow-local-openai &
FREERIDE_INTEGRATION=1 go test . -v -count=1Optional: FREERIDE_TEST_URL=http://localhost:11434
Live tests in proxy_test.go are skipped without FREERIDE_INTEGRATION=1 so CI and offline runs stay fast. They may also skip if all cloud models are in cooldown (see cooldowns.json).
MIT License.