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 |
|---|---|---|
FREERIDE_UPSTREAM_TIMEOUT |
4m (local OpenAI: 20m) |
Full upstream LLM request deadline, including response-body generation. Accepts Go durations, e.g. 4m or 240s. Keep the agent's LLM timeout longer than this; fallback candidates share the agent's remaining request time. Non-streaming body-read failures are logged and fall back to the next model. |
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.
rolePreferDifferentFrom makes QA prefer capable alternatives to models used by
polecat, planner, tester, architect, and setup:
rolePreferDifferentFrom:
qa: [polecat, planner, tester, architect, setup]Freeride records successful model selections for those roles and moves matching
models to the end of QA's entire eligible candidate chain, after provider
priority and emergency fallback selection. Their configured paid-first primaries
are also deferred before any usage has been observed. Shared models are still
tried if every independent candidate is unavailable or fails; the log marks these
attempts with [DIVERSITY]. Existing capability, cooldown, exclusion, and paid-model
rules still apply. QA's curated alternatives are configured in rolePrepend.qa;
with --allow-paid, eligible independent entries lead the independent tier ahead
of generic provider-priority routes.
History is in memory and resets on proxy restart. Because requests currently carry
a role but no rig identifier, it covers all rigs using this proxy. Recognized
provider wrappers and :free suffixes do not count as different models.
The QA shortlist was checked against the live OpenRouter catalog on 2026-11-04. Prices below are USD per million tokens, before caching:
| Model | Input | Output | Context |
|---|---|---|---|
z-ai/glm-5.3-flash |
$0.0269 | $0.0996 | 1,048,576 |
anthropic/claude-haiku-5.5 |
$0.10 | $0.50 | 1,000,000 |
deepseek/deepseek-v4.1-flash |
$0.045 | $0.50 | 1,048,576 |
minimax/minimax-m3.1-flash |
$0.15 | $0.60 | 1,048,576 |
Haiku's catalog rates rise to $0.50/$2.50 above 100,000 input tokens. MiniMax has time-dependent discounts. Reasoning tokens are billed as output. GLM, Haiku, and DeepSeek each correctly identified a planted inverted-boolean bug in a small live review probe; this verifies availability and basic review behavior, not broad QA quality. NVIDIA Nemotron 3.5 Super VL 120B remains another alternative, backed by successful calls in the local proxy log. Shared-model fallback is logged explicitly.
Run the standard-library-only Python script from the repository root:
# Check OpenRouter's live catalog, choose models, preview the diff, then confirm.
python3 scripts/recommend_qa_models.py
# Also make small billable requests to check that candidates catch a planted bug.
python3 scripts/recommend_qa_models.py --probe
# Report only: never prompt or write.
python3 scripts/recommend_qa_models.py --dry-run
# Adjust USD-per-million price ceilings, context requirements, or result count.
python3 scripts/recommend_qa_models.py --max-input 0.25 --max-output 0.75 --min-context 200000 --limit 10
# Include free endpoints in the shortlist as well.
python3 scripts/recommend_qa_models.py --include-free --probeThe script filters for text output, tool support, coding/agent descriptions, context
length, and price. Existing curated QA models also qualify without coding claims
in their descriptions. Free routes are opt-in with --include-free. It respects
excludeModels, avoids other roles' configured
primaries, and skips model IDs that Freeride would route to a different provider.
It ranks candidates by the estimated cost of a 50k-input / 2k-output-token review
and shows any catalog pricing overrides. These are discovery heuristics, not quality
benchmarks. --probe requires OPENROUTER_API_KEY in the environment or the .env
beside models.yaml; only passing candidates can then be selected.
In a terminal, enter model numbers in preference order. The script shows the exact
diff and asks “Apply these changes to models.yaml? [y/N]”. On confirmation it
updates only rolePrepend.qa, retains existing QA fallbacks, defers shared primary
models, and saves a timestamped models.yaml.bak-* backup before an atomic write.
Other roles and settings are preserved. Noninteractive runs only report; they never
write. Use --config /path/to/models.yaml for another installation. Freeride's
config watcher picks up accepted changes automatically; the diversity-routing code
itself requires a proxy rebuild/restart when first installed.
# 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.
Budgets belong to the workflow and rig, not the provider. Switching from
OpenRouter to Cerebras, Groq, Gemini, NVIDIA, Cloudflare, or a local compatible
endpoint does not create a fresh allowance. Each upstream attempt, including
fallbacks, consumes the same durable request counter. The defaults are 1000
upstream attempts, a $5 workflow cost allowance, and a $20 cost allowance shared
by workflows on the same rig. Gas Town additionally enforces its provider-neutral
request/token limits (GT_WORKFLOW_MAX_LLM_REQUESTS, default 1000, and
GT_WORKFLOW_MAX_LLM_TOKENS, default 20000000). Dollar coverage depends on the
provider's usage reporting and the pricing configured below.
| Route | Dollar accounting |
|---|---|
| OpenRouter | Uses catalog prices and provider price caps for admission; settles to reported usage.cost. Known free routes consume no dollar allowance. |
Direct provider with workflowPricing |
Reserves using configured prices; settles from token usage as an estimate. A reported usage.cost takes precedence. |
| Explicit zero input and output prices | Treated as a configured free route; no inferred free tier or subscription assumptions. |
| Unpriced direct provider | Cost unknown, never presented as free. Records reported cost when supplied, otherwise records an unknown-cost request. Request limits and Gas Town token limits still apply, but the dollar allowance cannot bound unknown charges. |
| Interrupted response or missing usage on a priced route | Retains the reservation and records unknown accounting coverage rather than assuming zero cost. |
Configure limits on the proxy process:
export FREERIDE_WORKFLOW_MAX_UPSTREAM_ATTEMPTS=1000
export FREERIDE_WORKFLOW_MAX_COST_USD=5
export FREERIDE_RIG_MAX_COST_USD=20
# OpenRouter admission price ceilings, not prices assigned to direct providers:
export FREERIDE_MAX_PROMPT_USD_PER_MILLION=1
export FREERIDE_MAX_COMPLETION_USD_PER_MILLION=5
# Optional persistent location shared by proxy processes on this host:
export FREERIDE_EXECUTION_BUDGET_PATH=/path/to/persistent/execution-budgets.jsonFor a one-workflow adjustment, use Gas Town's command instead of changing process-wide defaults:
gt mayor workflow budget wf-1 --tokens 40000000 --usd 10Either option may be used independently. The command leaves global defaults and
other workflows unchanged. Token overrides are stored in the Gas Town workflow
ledger; dollar overrides are stored in execution-budgets.json.limits beside
the proxy ledger and apply to OpenRouter and priced direct-provider calls. The
proxy reads dollar overrides for each admission and status request, so no proxy
restart is needed. If the proxy ledger uses a custom path, run the command on
the proxy host with --freeride-budget-path /path/to/execution-budgets.json.
Per-workflow dollar overrides do not change the shared rig allowance, unknown
provider costs, or provider account limits.
For direct providers, set prices in models.yaml, keyed by the full routing
model ID, or a provider wildcard such as cerebras/*. Exact IDs take precedence.
Both prices are required; zero must be explicit. Values below are illustrative,
not current provider tariffs—use your own tier/contract prices from
Cerebras, Groq, or
the relevant provider. No prices are guessed or copied between providers.
workflowPricing:
"cerebras/YOUR_MODEL_ID":
inputUSDPerMillion: 1.0
outputUSDPerMillion: 2.0
"groq/YOUR_MODEL_ID":
inputUSDPerMillion: 0.5
outputUSDPerMillion: 1.0
"local/my-free-endpoint":
inputUSDPerMillion: 0
outputUSDPerMillion: 0Token-based amounts are estimates: provider discounts, caching, free allowances,
and contract billing can differ. Paid non-token features and media require a
separate pricing policy; configured direct text routes reject those features.
The text policy permits one completion with at most 8192 output tokens. Cerebras
and Groq streams request terminal usage; Groq's x_groq.usage is also recognized.
Other compatible endpoints can use ordinary usage in JSON or SSE responses.
OpenRouter-specific provider.max_price fields are never sent to direct providers.
Inspect a workflow without making an inference request:
curl -s 'http://localhost:11434/api/budgets?rig=pwtest&workflow=wf-1'gt feed displays these figures and limits under each workflow, refreshing every
10 seconds. A local configured proxy is discovered automatically; set
GT_BUDGET_STATUS_URL for a remote budget status endpoint. Rebuild/install Gastown
and restart Freeride after upgrading. The feed also shows Gastown request/token
reservations and their limits; these are separate from dollar spend.
The API includes rig_accounted_cost_usd and rig_allowance_usd in addition to
workflow totals and limits, plus upstream_attempt_limit for retry/fallback attempts.
These are ledger amounts, not provider account credits.
The response includes accounted_cost_usd (settled costs/estimates plus outstanding
reservations), unknown_cost_requests, reported_requests, estimated_requests,
and the request count. accounting explicitly says cost unknown when any
observed call lacks cost coverage. X-Freeride-Cost-Accounting on inference
responses indicates reported, estimated, reserved, or unknown; streaming
headers reflect admission-time knowledge, while the status endpoint reflects
completed usage. Unpriced routes also emit a warning header and proxy log entry.
Coverage counters describe calls observed after this feature was installed;
older cost totals without coverage history are labelled as legacy accounting.
Without an explicit path, ledgers live in the operating system's user config
directory under freeride/execution-budgets.json. That file holds attempt counts;
.usd holds microdollars and .usd.coverage holds accounting coverage counts.
They use process locks and atomic replacement and persist across proxy restarts.
Completed responses, including streams, replace their reservation with reported
cost or a configured token estimate. Confirmed pre-inference rejections release
it. Corrupt accounting stops identified workflow requests. Never delete ledgers
while agents are active. Provider account-balance access is not required.
--allow-paid permits paid routing but does not bypass limits. A 402/403 or an
exhausted budget pauses the identified workflow. The saved reason appears in
gt mayor workflow status, the terminal feed, and the web console alongside the
current phase. Correct the reported cause before resuming; a resume does not
reset the accounting or redo completed phases. These controls apply to requests
carrying workflow identity, not to the entire provider account. Keep the proxy
private: omitted or forged identity headers are outside this accounting boundary.
See gastown workflow recovery for checkpoint batching, agent-side token/request reservations, installation and remaining architectural work.