# Coming from Gas Town Source: https://docs.gascity.com/getting-started/coming-from-gastown Map Gas Town's roles, mechanisms, layout, commands, and workflows onto Gas City's primitives. If you have run Gas Town, you already know its roles, its `~/gt/...` layout, and its `gt` commands. This page carries that knowledge across to Gas City. Gas City is the platform that machinery was extracted into. Two things changed. First, the orchestrator hardcodes **zero roles** — every role you knew is now configuration, and you express Gas Town (or any orchestration) on top of a few primitives. Second, and bigger: the orchestrator can now run a formula as a **graph across many agents, out of your session** — decomposing a job into beads, fanning the ready ones out in parallel, gating each step on its dependencies, and retrying failures to completion. Your single-agent, in-session formulas still run (v1); this fleet orchestration (v2) is what's new. Because it is a platform, a feature added to Gas City lifts *every* orchestrator built on it — Gas Town included. For the system-level mental model first, read [How Gas City Works](/getting-started/how-gas-city-works). ## Mapping tables The five tables map Gas Town onto Gas City one domain at a time. Every entry on the right is **configuration** — a user-configured agent plus a prompt template — not a built-in platform type. ### Roles | Gas Town role | What it did | Gas City equivalent | | - | - | - | | **Mayor** | Planner/coordinator; the human's point of contact | Configured agent + coordinating prompt (e.g. the Gastown pack's `mayor`). Reach it with `gc session attach mayor`. | | **Deacon** | Watchdog: stall detection, restart, SLA enforcement | Orchestrator health patrol + config thresholds; optionally a configured agent. You tune thresholds, not run a role. | | **Witness** | Lifecycle observer; publishes health/transition events | Events + waits, formulas, session scale config. Modeling a "witness" on top is optional pack behavior. | | **Refinery** | Post-processor; reshapes raw agent output | Configured agent + a formula or order post-processing step. A workflow step, not a standing role. | | **Polecat** | Ephemeral on-demand worker, often in a worktree | Scalable/transient agent config (a pool — `min`/`max_active_sessions`). An operating *style*. | | **Crew** | Persistent worker pool claiming from a queue | Persistent named agent config. An operating *style*. | | **Dog** | Integration / external-messaging relay | Core-pack exec orders (most relay work needs no LLM); the Gastown pack's `dog` pool covers work that does need an agent. | ### Mechanisms and behaviors *What those roles and features actually **do**, and where that logic now lives.* | Gas Town behavior | Gas City equivalent | Notes | | - | - | - | | Deacon watchdog logic | Orchestrator health patrol + reconciliation | Stall detection, restart-with-backoff, reconcile-to-desired-state are orchestrator concerns, not a role agent. | | Witness lifecycle tracking | Waits, formulas, session scale config, orchestrator wake/sleep, events | Mechanisms are first-class; modeling a "witness" on them is optional. | | Plugin (scheduled/event/conditional automation) | Order — exec order or formula order | **Exec order** for shell or orchestrator-side logic; **formula order** to instantiate agent-driven work. | | Convoy as orchestration runtime | Convoy beads + `gc sling` + formulas | Convoys stay bead-backed grouping and lineage; no special convoy runtime to use. | | Formula runner inside Town workflows | In-process formula compiler + orchestrator execution | Gas City compiles and runs formulas over the convoy's beads itself. For v2 formulas (host-enabled by default), the orchestrator executes control beads; agents execute work beads. See [Choosing a Compiler Contract](/guides/understanding-formulas#choosing-a-compiler-contract). | | Path-derived identity | Explicit agent identity, rig scope, env, bead metadata | Do not port code or prompts that assume directory path implies who the agent is. | ### Filesystem and state *Gas Town encodes architecture into directories; Gas City treats directories as an implementation detail.* | Gas Town location | Gas City equivalent | Notes | | - | - | - | | `~/gt/...` directory tree | City directory + `.gc/` runtime state | A city is a directory holding `city.toml` and `.gc/`. Rigs are registered in `city.toml`; each `[[rigs]]` has a `path` defaulting under `rigs/` but allowed anywhere (absolute or city-relative). Live state is queried, not read from a fixed home tree. | | Town config | `pack.toml` (reusable behavior) + `city.toml` (this city's deployment) | One town config splits along a definition/deployment seam. | | Rig config | `city.toml` `[[rigs]]` entries + `.gc/` (machine-local path bindings) | *Which* rigs and their scale is deployment; *where* a rig lives on this machine is a local binding. | | Role homes | `agents//` (`agent.toml` + `prompt.template.md`) in the root city pack or an imported pack | Only the agent *definition* lives here. No on-disk role "home"; identity is not path-derived. | | Role home dirs (e.g. `~/gt/mayor/`) | `dir` (identity scope) + `work_dir` (session working dir, only when needed) | Set both in `agent.toml` (or patch per-rig in `city.toml`): `dir` carries scope/identity; `work_dir` only when a role truly needs filesystem isolation. | | Role-specific startup files | Prompt templates, overlays, provider hooks, `pre_start`, `session_setup`, `gc prime` | Startup shaping is explicit and provider-aware, not inferred from disk location. | ### Workflows *The operator verbs — the things you actually type. (Formulas are a mechanism, above; these are day-to-day moves.)* The transcripts below show them end to end. | Gas Town workflow | Gas City command | Deeper | | - | - | - | | Spin up a worker | `gc start` + a persistent agent config (`agents//`) | [Tutorial 02 — Agents](/tutorials/02-agents), [Shareable Packs](/guides/shareable-packs) | | Send a task to the mayor | `gc sling mayor ""` (or `bd create` + a bead hook) | [`gc sling`](/reference/cli#gc-sling), [Tutorial 06 — Beads](/tutorials/06-beads) | | Inspect what's stuck | `gc session list`, then `gc session peek ` | [`gc session list`](/reference/cli#gc-session-list) | | Restart a stalled agent | `gc session reset ` (or let health patrol auto-restart) | [`gc session reset`](/reference/cli#gc-session-reset) | | Share config across teams | A shareable pack (`pack.toml` + `agents//`), imported by each city | [Shareable Packs](/guides/shareable-packs) | | Run a one-shot job | A formula or exec order, dispatched on demand | [Tutorial 07 — Orders](/tutorials/07-orders), [Tutorial 05 — Formulas](/tutorials/05-formulas) | | Watch live agent output | `gc session attach ` (interactive) or `gc session peek ` (snapshot) | [`gc session attach`](/reference/cli#gc-session-attach) | A first work cycle — start the city, hand the mayor a task, watch it land: ```bash theme={"dark"} gc start # boots the city under the supervisor, reconciles gc sling mayor "add a /health endpoint" # creates a task bead from text, routes it to mayor gc session list # see what's running (mayor, pools, …) gc session attach mayor # interactive live view of the mayor working ``` Triage a stuck agent without attaching: ```bash theme={"dark"} gc session list # find the suspect by name/state gc session peek mayor --lines 80 # snapshot of recent output (default 50 lines) gc session reset mayor # fresh restart; bead, alias, mail, and queued work survive gc events --follow # system-wide live feed (or let health patrol act on its own) ``` `gc session peek` takes `--lines` for a point-in-time snapshot — there is no `--follow`. For a continuously updating view, `gc session attach`. For the system-wide live feed, `gc events --follow`. ### Commands The full `gt` → `gc`/`bd` mapping lives in the **[Gas Town → Gas City Command Map](/reference/gastown-command-map)**. ## What usually maps cleanly **Roles become pack agents.** Adding a role follows an escalation ladder — stop at the first rung that solves your problem: ```text theme={"dark"} edit local city.toml → include a pack that already solves most of it → override the stamped agent (local-only change) → edit the pack (change the shared default for everyone) → add formulas/orders for workflow automation ``` **Start with the root city pack plus `city.toml`, not an imported pack.** Edit a pack only when the change should become the reusable default for every consumer. ```toml theme={"dark"} # pack.toml — imports reusable packs, defines city-specific behavior # agents// — city-owned named agents # city.toml — deployment: rigs, substrates, scale # .gc/ — site bindings such as local rig paths ``` **Plugins become orders** — the most important practical translation. "Run something automatically on a schedule, on an event, or when a condition holds" is an order: **exec order** for shell/orchestrator-side logic, **formula order** for agent-driven work. Exec orders matter most — they run non-agent commands with no prompt, no session, no extra role agent. **Convoys stay bead-shaped.** Keep the convoy mental model for tracking work; the implementation boundary moved. Convoys are bead-backed grouping and lineage, `gc sling` creates convoy structure while routing, and formulas/orders/waits compose around that bead graph. The orchestration that runs over it is the orchestrator's control dispatcher — it executes the control beads (check, retry, fan-out, tally, drain) that drive a convoy's work to completion across many agents. **Crew and polecats are operating modes, not types.** *Crew* = persistent named agents; *polecats* = scalable or transient agents, often with worktrees. The platform does not force the distinction — a pack can adopt, relax, or replace it. ## Where Gas City deliberately differs **The orchestrator owns infrastructure behavior.** It is the canonical owner of reconciling desired→running sessions, session scaling, order evaluation, health patrol, and garbage-collecting ephemeral run beads (the v1 *wisp* container). If something is fundamentally platform infrastructure, put it on the orchestrator path rather than inventing another deacon-like role. **Filesystem layout is not the architecture.** Use `dir` (in `agent.toml`, or patched per-rig in `city.toml`) for scope and identity; `work_dir` only when the session must run elsewhere; bead metadata for durable handoff state. | Use a separate `work_dir` when… | Not when… | | - | - | | the role mutates a repo and needs an isolated worktree | "Gas Town has a separate folder for this role" | | provider scratch files would collide with another role | | | the role needs a durable sandbox independent from the rig root | | **Roles are examples, not platform law.** The Gastown pack ships familiar roles as an example operating model, not a type system. Adding a behavior means editing a pack, formula, order, or prompt — not adding a hardcoded role. A **local city change** edits `city.toml` (rig overrides, patches, a city-specific agent); a **shared product change** edits the pack for a better default everywhere. Most onboarding work is local. ## Common translation patterns | Old Town instinct | Ask first | Default answer | | - | - | - | | "I need a new dog" | Can this be an exec order? | Prefer the order — trigger logic, history, orchestrator ownership, no agent slot. Reach for a scalable agent only if it needs a long-lived session, rich interactive context, or repeated agent judgment. | | "I need a witness-like lifecycle manager" | Which parts are orchestrator infra vs. bead transitions vs. formula logic vs. prompt guidance? | Only orchestrator infrastructure belongs in platform code; the rest lives in the pack. | | "I need another special directory tree" | Do I really? | Canonical repo root from the rig; isolated `work_dir` only for roles that mutate repos or need provider-file isolation; explicit env and metadata, never path inference. | | "I need to run something without an agent" | Could an exec order do it? | Use an exec order before inventing a plugin, helper role, or hidden session. | ### "How do I get to my mayor?" ```bash theme={"dark"} gc session attach mayor ``` The Mayor session is the familiar Gas Town entry point — an interactive session with full city context that you coordinate from. It is one window onto the city; the orchestrator is still running the fleet and driving formula graphs to completion behind it. The CLI is plumbing for reaching that session. City-scoped Gastown agents (`mayor`, `deacon`, `boot`) attach the same way; `gc session list` shows what is running. This replaces `gt session at mayor/` or `tmux attach -t gt-mayor`. ## What not to port literally These Town habits create unnecessary complexity in Gas City: * exact `~/gt/...` directory trees * path-derived identity * new hardcoded role names in platform code * plugin systems when an order is enough * special helper agents for work that is really a shell command * duplicating durable state outside beads when labels or metadata suffice The most common mistake is importing Town's surface area instead of re-expressing the intent in Gas City's primitives. ## Editing Gastown config The common edits — registering rigs, scaling pools, swapping providers, patching agents, tweaking prompts — live in [Gastown on Gas City: Config Recipes](/guides/gastown-config-recipes). ## Fast ramp checklist The shortest path to effective: 1. Read [How Gas City Works](/getting-started/how-gas-city-works) for the six primitives in user terms. 2. Skim the [CLI reference](/reference/cli) alongside the [Command Map](/reference/gastown-command-map) so `gt` → `gc` muscle memory transfers. 3. Read [Tutorial 07 — Orders](/tutorials/07-orders) and remap "plugins" → "orders". 4. Read [Tutorial 05 — Formulas](/tutorials/05-formulas): Gas City compiles and instantiates formulas itself; for v2 formulas the orchestrator drives control beads while agents execute work beads. 5. Work through [Tutorial 02 — Agents](/tutorials/02-agents) and [Shareable Packs](/guides/shareable-packs) for the `agents//` layout end to end. 6. Read [A Complete Gastown Example](/guides/gastown-config-recipes#a-complete-gastown-example) — city, root pack, and nested pack assembled into one runnable topology. # Web dashboard Source: https://docs.gascity.com/getting-started/dashboard The supervisor hosts a built-in web dashboard for all your cities. The Gas City supervisor hosts a built-in web dashboard. It is a single-page app compiled into the `gc` binary and served by the supervisor on its own listener, so there is nothing extra to install or run. ## Open it Start the supervisor, then open the URL it prints: ```bash theme={"dark"} gc supervisor start # Supervisor API listening on http://127.0.0.1:8372 # Dashboard: http://127.0.0.1:8372/ ``` If the supervisor is already running, `gc dashboard` opens it in your browser and prints the URL too: ```bash theme={"dark"} gc dashboard # Opened the dashboard in your browser: http://127.0.0.1:8372 ``` Pass `--no-open` to print the URL without launching a browser (useful over SSH or in scripts): ```bash theme={"dark"} gc dashboard --no-open # The dashboard is served by the gc supervisor at http://127.0.0.1:8372 ``` `gc dashboard` does not start a server — it points your browser at the running supervisor. The supervisor is the host, and one supervisor serves every registered city. Pick the city you want from the switcher in the dashboard header. If the supervisor is not running, `gc dashboard` prints how to start it instead of opening a (dead) URL. ## What it shows The dashboard reads the supervisor's typed API directly (same origin), so it reflects live state: agents and their sessions, beads, mail, formula runs, and a health view (system, local tools, per-rig store health, and the dolt store trend). ## Security posture The dashboard is served on the supervisor's bind address, which defaults to loopback (`127.0.0.1`). It is intended for local, single-operator use: * It is same-origin with the API; browser mutations carry the supervisor's `X-GC-Request` CSRF header. * When the supervisor binds a non-localhost address without `allow_mutations`, it runs read-only and the dashboard disables its mutating controls. ## Turn it off Set `GC_SUPERVISOR_DASHBOARD=0` before starting the supervisor to run a typed-API-only supervisor with no embedded dashboard. # FAQ Source: https://docs.gascity.com/getting-started/faq Quick answers to the questions newcomers ask most — what Gas City adds over a single coding agent, what it runs on, and where to start. ## Why do I need Gas City when I already have a coding agent? A coding agent gives you one session: a faster pair of hands, steered live, and gone when it crashes. Gas City turns a fleet of them into a **software factory**. You write down how a job gets done once — a [formula](/guides/understanding-formulas) — and an orchestrator runs it across many agents *outside your session*: it decomposes the job, runs the independent pieces in parallel, reviews and gap-checks the result, and retries what fails until the work is done. You describe a feature once and come back to a finished branch. [How Gas City Works](/getting-started/how-gas-city-works) is the full mental model. ## Couldn't I get the same thing from a bash loop or CI? A loop can respawn an agent, but it has no model of the work: every iteration starts blind, and a crash loses whatever the last iteration knew. The orchestrator runs a formula as a *graph* — it holds each step until its dependencies close, fans the ready steps out to many agents at once, retries failures, and keeps every unit of work in a durable store, so progress survives any crash on either side. CI is complementary rather than competitive: CI verifies a change after you make it; a formula is what produces the change. See [Understanding Formulas](/guides/understanding-formulas) for what the orchestrator does that a script cannot. ## How does Gas City relate to Gas Town? Gas City is the platform Gas Town's machinery was extracted into. The platform hardcodes zero roles — every role Gas Town wired into code (mayor, crew, and the rest) is now configuration expressed as a [pack](/guides/understanding-packs), so the same engine runs Gas Town, Ralph, or whatever you configure. If you know Gas Town, [Coming from Gas Town](/getting-started/coming-from-gastown) maps its roles, commands, and layout onto Gas City one table at a time. ## Which coding agents does it work with? Sixteen built-in harnesses, including Claude Code, Codex CLI, Gemini CLI, Cursor Agent, GitHub Copilot, Sourcegraph AMP, OpenCode, Grok, Kimi Code, and Pi — [Harness Recipes](/guides/harness-recipes) has the copy-paste setup for each. Agents run under the logins or API keys you already have, and each agent picks its own harness, so a mixed fleet is just configuration. ## Do I have to write Go, or any code at all? No. Everything user-facing is configuration: TOML files (`city.toml`, `pack.toml`) declare your agents, formulas, and orders, and markdown prompt templates define what each role does. A "reviewer" or "planner" is a prompt you wrote, not a plugin you compiled. Start with [Configuring an Agent](/guides/configuring-an-agent). ## Do I need tmux? What else does it depend on? Yes — agent sessions run in tmux. The full runtime set is tmux, jq, git, dolt, bd (the beads CLI), and flock; `brew install gascity` installs all of them for you. For the lightest possible start, `GC_BEADS=file` skips the dolt + bd pair. [Installation](/getting-started/installation) has the exact versions and the non-Homebrew paths. ## What happens when an agent crashes mid-job? Nothing is lost. Every unit of work is a **bead** in a durable store that outlives any session: if an agent dies, its beads stay open and a fresh agent picks up the same work; if the orchestrator restarts, it adopts the live sessions it finds and resumes from the store. Sessions are disposable — the work they did is not. The [Bead section of How Gas City Works](/getting-started/how-gas-city-works#bead) explains why the system converges. ## Can I use it with my existing repos? Yes. Register any project as a **rig** with `gc rig add ` — its directory can live anywhere on disk, and each rig gets its own bead namespace and agent scope, so work in one project stays isolated from the others. [Tutorial 01](/tutorials/01-cities-and-rigs) walks through it. ## Is it open source? What does it cost? Gas City is MIT-licensed and free — [github.com/gastownhall/gascity](https://github.com/gastownhall/gascity). The only spend is the model usage of the agents you run, billed through the harness credentials you already use. ## Where do I start? [Installation](/getting-started/installation), then the [Quickstart](/getting-started/quickstart) — it boots your first city in a few minutes. When you want the guided path, the [Tutorials](/tutorials/index) build a complete city up, command by command. # How Gas City Works Source: https://docs.gascity.com/getting-started/how-gas-city-works The orientation for Gas City — how it orchestrates fleets of agents, and the six primitives that compose into that orchestration. Gas City **orchestrates fleets of coding agents** through real engineering work. You write a **formula** — a method for how a job gets done — and the **orchestrator** runs it as a graph: it decomposes the job into beads, fans the ready ones out to as many agents as the work allows, holds each step back until its dependencies close, retries what fails, drains convoys in parallel, and drives the whole graph to completion *outside your session*. [Orders](/tutorials/07-orders) trigger formulas on a schedule or an event; health patrol keeps the fleet alive. **This orchestration is the point.** What makes it a *platform* and not one fixed orchestrator: the orchestrator hardcodes **zero roles** — no built-in "manager" or "reviewer." Every role is configuration supplied through a **Pack**, and the whole orchestration is composed from six primitives, so the same engine becomes Gas Town, Ralph, or whatever you configure. ## The machinery underneath Three pieces of role-agnostic plumbing run the primitives, and you configure no role around any of them. | Machinery | What it does | | - | - | | **Orchestrator** | runs formulas, drives each bead graph forward, and reconciles live agents against what your config declares | | **Bead store** | durable work — every unit of work is a bead that survives an agent crash, so the orchestrator always has ground truth to resume from | | **Event bus** | fires activity outward so humans and agents can watch what's happening | None of this machinery knows what your agents do. It's the substrate the six primitives sit on. Notice the shape of the loop: the orchestrator acts on sessions — spawning, stopping, restarting them — but reads their progress from the bead store and event bus rather than being called back directly. The loop closes through shared state, which is why work survives a crash on either side. ## The six primitives | Primitive | Role | Is | Key idea | | - | - | - | - | | **Agent** | WHO | a configured worker — name, provider, prompt template, scope | pure configuration, so define as many as you like; the platform assumes none exists | | **Bead** | WHAT | one unit of work — ID, title, status, type | the universal substrate: tasks, mail, sessions, convoys are all beads differing only by `type` | | **Formula** | HOW | a reusable, written-down method applied over work | applying it *produces* work: a formula materializes as beads that outlive the file and any session | | **Rig** | WHERE | an external project (usually a git repo) registered with the city | each rig gets its own bead namespace and agent scope | | **Pack** | CONFIGURES | the unit of configuration — declares agents, formulas, orders | the City *is* a pack: the one rooted at this deployment | | **Event** | OBSERVE | an outbound notification fired by activity | *fired, not polled*; humans and agents both watch the stream | The six primitives and how they relate: Packs declare agents, formulas, and orders; a Formula operates over a convoy of Beads, fanning work out to Agents that execute in a Rig; Events are fired so humans and agents can observe. **Packs** declare the agents, formulas, and orders; the local pack is your **City**, which can pull in shared packs through imports. A **Formula** operates over a convoy of **Beads**, fanning work out to **Agents** that execute in a **Rig**; an **Order** automates *when* a formula runs; and **Events** fire so humans and agents can observe the whole thing. ### Agent An **agent** is *who* does the work — a worker a pack defines as a prompt plus a scope and a provider. The prompt template is its entire behavioral spec; because the platform has no hardcoded roles, a "reviewer" or a "planner" is nothing more than the prompt you wrote for it. When an agent is *running* it is a **session** — a live process the platform can start, stop, prompt, and observe. The engine backing that session is its **provider**; when the orchestrator restarts, it *adopts* the live sessions it finds — creating a session bead for each — rather than respawning them. A single agent can be scaled into a **pool** of identical workers sharing one queue: each tick the orchestrator runs the agent's `scale_check` query to measure demand and sizes the pool to it — up to `max_active_sessions`, never below a `min_active_sessions` floor — retiring sessions that fall idle. Sessions are disposable; the work they did survives them, because work lives in beads. ### Bead A **bead** is *what* the work is — one unit with an ID, title, status, and type, moving `open` → `in_progress` → `closed`. Beads are also the universal store: tasks, inter-agent mail, running sessions, and convoys are all beads that differ only by `type`, sharing one query interface. A **convoy** is a container bead that groups related work so you track a batch as a unit. **Dependencies** are blocking `needs` edges: a bead with an open blocker is invisible to agents until that blocker closes — which is how ordering happens with no central scheduler. Because work persists in beads, the system converges: if an agent dies, its beads stay open and a fresh agent picks up the same work. ### Formula A **formula** is *how* a job gets done — a reusable, written-down method, a TOML file of steps and their dependencies. Applying it compiles the steps into a graph and materializes them as beads; from that moment a **run** is independent of the file and of any session. The orchestrator drives the run outside your session, fanning ready steps out to many agents at once and gating each on its dependencies. **Sling** (`gc sling`) is the dispatch op that creates *and* routes in one motion. An **Order** automates *when* a formula runs, pairing a trigger (cooldown, cron, condition, event, or manual) with the formula to fire — no human runs a verb. **Health patrol** is one kind of order: each tick the orchestrator evaluates due triggers and fires them. ### Rig A **rig** is *where* the work happens — an external project, usually a git repo, registered with the city with `gc rig add `. A rig carries a repo, its own **bead namespace**, and an **agent scope**. Its directory can live anywhere on disk, inside or outside the city. Isolation is by bead-ID prefix, not a separate database: the city and all its rigs share one underlying store, and reads and writes are filtered to the current scope's prefix. Work slung in one rig stays logically isolated from the others, and rig-scoped agents are instantiated once per rig. ### Pack A **pack** is what *configures* the system — a directory with a `pack.toml` that declares agents, formulas, and orders, plus the support files they need. The local (root) pack is your **City**: the pack rooted at the city directory, where the city keeps its own definitions alongside its deployment settings. **Imports** are named dependencies on shared packs; an imported pack's agents, formulas, and orders read exactly like locally declared ones, so a city reuses behavior defined elsewhere without copying files. The same engine becomes a different orchestrator purely by swapping which packs it loads. ### Event An **event** is how you *observe* what's happening — an immutable, append-only record fired by city activity, not something the other primitives consume. Every event carries a monotonically increasing sequence number, so a watcher can replay the stream from any point. Beads fire `bead.created` / `bead.closed`, sessions fire `session.woke` / `session.crashed`, convoys fire `convoy.created` / `convoy.closed`, and orders fire `order.fired` / `order.completed`. Humans watch the stream with `bd show --watch`, the `gc events --follow` CLI, or the dashboard; agents and bd hooks observe and emit too. Events also close the automation loop: an event-triggered order *reads* the stream to decide when its formula runs — so the same notifications humans watch can drive the fleet, with no specific agent role required. ## Where to go next * [Tutorials](/tutorials/index) — the guided, end-to-end path through every primitive above. * [Understanding Formulas](/guides/understanding-formulas) — the full guide to formulas, runs, sling, and orders. * [Understanding Packs](/guides/understanding-packs) — how packs, cities, and imports compose. * [Reference](/reference/index) — command, config, formula, and provider lookup. # Installation Source: https://docs.gascity.com/getting-started/installation Install Gas City from Homebrew, a release tarball, or source. ## Which method should I use? | Method | Best for | Installs deps? | Auto-upgrades? | | - | - | - | - | | [Homebrew](#homebrew-recommended) | macOS / Linux daily use | Yes (runtime deps) | `brew upgrade` | | [Direct download](#direct-download) | CI, containers, air-gapped hosts | No | Manual | | [Source build](#build-from-source) | Contributors, bleeding-edge | No | Manual | **Most users should use Homebrew.** It installs all runtime dependencies automatically and keeps `gc` on your PATH. Choose direct download when you cannot use Homebrew (CI images, Docker layers, machines without package managers). Choose source when you need unreleased changes or plan to contribute. ## Prerequisites Gas City requires a small set of runtime tools. Homebrew installs all of them for you; the other methods require manual installation. | Tool | Required | Min version | macOS | Linux | Notes | | - | - | - | - | - | - | | tmux | Yes | — | `brew install tmux` | `apt install tmux` | Session management | | jq | Yes | — | `brew install jq` | `apt install jq` | JSON processing | | git | Yes | — | (built-in) | (built-in) | Version control | | dolt | Yes | 2.1.0 or newer | `brew install dolt` | [releases](https://github.com/dolthub/dolt/releases) | Beads data plane | | bd (Beads CLI) | Yes | 1.0.4 minimum; 1.3.1 tested | `brew install beads` | [releases](https://github.com/gastownhall/beads/releases) | Issue tracking | | flock | Yes | — | `brew install flock` | (built-in via util-linux) | File locking | | gh | Optional | — | `brew install gh` | [cli.github.com](https://cli.github.com/) | GitHub gate checks | | Go 1.26+ | Source only | 1.26 | `brew install go` | [golang.org](https://go.dev/dl/) | Compiler | | make | Source only | — | (built-in) | `apt install make` (or `build-essential`) | Drives `make install` | Use a final Dolt 2.1.0 or newer. Gas City's managed Dolt checks reject older and pre-release builds because they are below the managed bd/Dolt compatibility floor; releases before 1.86.2 can also miss the upstream GC/writer deadlock fix in dolthub/dolt commit `ccf7bde206`, which can hang `dolt_backup sync` under heavy write load. The exact versions CI pins are in [`deps.env`](https://github.com/gastownhall/gascity/blob/main/deps.env). The tested bd is v1.3.1: `brew install beads` (or `brew upgrade beads`) installs it, or download `bd` for your platform from the [v1.3.1 release assets](https://github.com/gastownhall/beads/releases/tag/v1.3.1). With bd v1.3.0 Gas City falls back from its native store to the bd CLI, and proxied `bd backup` and closed-wisp purge are skipped. Gas City 1.4.2 pairs the native store with Beads 1.3.0, and main pairs it with Beads 1.3.1, which keeps the same schema. When upgrading an existing shared database from Beads 1.2.2, coordinate the upgrade of all clients using that database, then run `bd migrate schema` from the workspace. The tested upgrade moves schema 53 to 66 and preserves existing beads. Older clients cannot use the migrated schema. Fresh workspaces initialize directly at the new schema. To install the exact release archive pinned by CI: ```bash theme={"dark"} set -a && . ./deps.env && set +a && .github/scripts/install-bd-archive.sh "$BD_VERSION" ``` ## Homebrew (recommended) ```bash theme={"dark"} brew install gascity ``` This taps the `gastownhall/gascity` formula, downloads the matching `gc` release asset, and installs all six runtime dependencies (tmux, jq, git, dolt, flock, beads). Once Gas City is accepted into homebrew-core, the normal install path will be `brew install gascity`; the `gastownhall/gascity` tap remains available for emergency updates. Verify the installation: ```bash theme={"dark"} gc version ``` If you use Oh My Zsh with the `git` plugin, `gc` may already be an alias for `git commit --verbose`. Run `command gc version` once to bypass the alias. For a persistent fix, add `unalias gc 2>/dev/null` or `zstyle ':omz:plugins:git' aliases no 'gc'` after Oh My Zsh loads in `~/.zshrc`, or put that line in a file such as `~/.oh-my-zsh/custom/gascity.zsh`. ### Upgrading via Homebrew ```bash theme={"dark"} brew update brew upgrade gascity ``` After upgrading, restart any running city so the supervisor picks up the new binary: ```bash theme={"dark"} gc service restart # restarts the launchd/systemd service ``` `gc start` auto-regenerates the service file on each invocation, so a `brew upgrade` followed by `gc start` always picks up template changes (see [v0.13.3 release notes](https://github.com/gastownhall/gascity/releases/tag/v0.13.3)). ### Uninstalling via Homebrew ```bash theme={"dark"} gc stop # stop running city first brew uninstall gascity brew untap gastownhall/gascity # remove the tap ``` ## Direct download Release tarballs are published for every tagged version. Supported platforms: | OS | Architecture | Archive name | | - | - | - | | macOS (darwin) | Apple Silicon (arm64) | `gascity_VERSION_darwin_arm64.tar.gz` | | macOS (darwin) | Intel (amd64) | `gascity_VERSION_darwin_amd64.tar.gz` | | Linux | x86\_64 (amd64) | `gascity_VERSION_linux_amd64.tar.gz` | | Linux | ARM (arm64) | `gascity_VERSION_linux_arm64.tar.gz` | ### Download and install ```bash theme={"dark"} # Set the version you want (check https://github.com/gastownhall/gascity/releases) VERSION=1.4.0 # Detect platform OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) case "$ARCH" in x86_64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac # Download and extract curl -fsSLO "https://github.com/gastownhall/gascity/releases/download/v${VERSION}/gascity_${VERSION}_${OS}_${ARCH}.tar.gz" tar -xzf "gascity_${VERSION}_${OS}_${ARCH}.tar.gz" # Move to a directory on your PATH sudo install -m 755 gc /usr/local/bin/gc # Verify gc version ``` ### Verify release artifacts Homebrew verifies release checksums from the formula automatically. For direct downloads, verify the archive before installing it: ```bash theme={"dark"} ARCHIVE="gascity_${VERSION}_${OS}_${ARCH}.tar.gz" CHECKSUMS="gascity_${VERSION}_checksums.txt" curl -fsSLO "https://github.com/gastownhall/gascity/releases/download/v${VERSION}/${CHECKSUMS}" grep " ${ARCHIVE}$" "${CHECKSUMS}" > "${ARCHIVE}.sha256" if command -v sha256sum >/dev/null 2>&1; then sha256sum -c "${ARCHIVE}.sha256" else shasum -a 256 -c "${ARCHIVE}.sha256" fi ``` Release archives are also published with GitHub artifact attestations. If you have the GitHub CLI installed, verify the downloaded archive against the `gastownhall/gascity` repository: ```bash theme={"dark"} gh attestation verify "${ARCHIVE}" --repo gastownhall/gascity ``` Each release also includes an SPDX SBOM asset: ```bash theme={"dark"} curl -fsSLO "https://github.com/gastownhall/gascity/releases/download/v${VERSION}/gascity-v${VERSION}.spdx.json" ``` ### Upgrading a direct-download install Repeat the download steps above with the new version number. The `gc` binary is a single static file — overwriting it is safe. You still need to install the [prerequisites](#prerequisites) separately when using direct download. Homebrew handles this automatically. ## Build from source Requires `make` and Go 1.26+ (pinned in `go.mod` as 1.26.4). ```bash theme={"dark"} git clone https://github.com/gastownhall/gascity.git cd gascity make install # builds and installs to $(GOPATH)/bin/gc gc version ``` To build without installing globally: ```bash theme={"dark"} make build # outputs bin/gc in the repo root ./bin/gc version ``` On macOS, `make build` signs the binary with a stable local codesigning identity when one is available, which helps macOS remember local permission grants across rebuilds. Without a stable identity, the build leaves Go's linker-produced signature unchanged. Set `GC_SIGN_IDENTITY=` to choose a specific certificate, `GC_SIGN_IDENTIFIER=` to use a separate local TCC identity, or `GC_ADHOC_SIGN=1` to opt into ad-hoc signing for a local experiment. Successful local signing also removes stale `com.apple.provenance` metadata when present. ### Contributor setup After building, install the dev toolchain and pre-commit hooks: ```bash theme={"dark"} make setup make check # runs fmt, lint, vet, and unit tests ``` See [CONTRIBUTING.md](https://github.com/gastownhall/gascity/blob/main/CONTRIBUTING.md) for the full contributor workflow, and [Bazel quickstart](https://github.com/gastownhall/gascity/blob/main/engdocs/bazel-quickstart.md) to set up the remote build cache — warm `bazel test //...` runs complete in under a second by sharing compiled artifacts across worktrees and CI. ## Verify your installation Regardless of install method, confirm everything is working: ```bash theme={"dark"} gc version # should print the installed version and commit ``` If that runs `git commit` instead of Gas City, your shell has a `gc` alias. Use `command gc version` for this check and see [Troubleshooting](/getting-started/troubleshooting#oh-my-zsh-git-plugin-hides-gc) for the permanent fix. Then create your first city: ```bash theme={"dark"} gc init ~/my-city cd ~/my-city ``` `gc init` registers the city with the supervisor, which then starts it. By the time the command returns, the city is running. See the [Quickstart](/getting-started/quickstart) for a complete walkthrough. Gas City ships a JSONL archive that snapshots every bead database for disaster recovery. By default it runs in local-only mode and keeps commits on this host. To enable off-box backup, see [JSONL archive push failures](/getting-started/troubleshooting#jsonl-archive-push-failures). ## Docs preview The docs site uses [Mintlify](https://mintlify.com). Preview locally from the repo root: ```bash theme={"dark"} ./mint.sh dev ``` Or run a link check without starting the server: ```bash theme={"dark"} make check-docs ``` # Quickstart Source: https://docs.gascity.com/getting-started/quickstart Create a city, add a rig, and route work in a few minutes. This guide assumes you have already installed Gas City and its prerequisites. If you haven't, start with the [Installation](/getting-started/installation) page. You will need `gc`, `tmux`, `git`, `jq`, and a beads provider (`bd` + `dolt` by default, or set `GC_BEADS=file` to skip them). Oh My Zsh's `git` plugin defines a `gc` alias for `git commit --verbose`. If `gc version` or `gc init` opens git commit instead of Gas City, use `command gc ...` temporarily and remove the alias after Oh My Zsh loads. See [Troubleshooting](/getting-started/troubleshooting#oh-my-zsh-git-plugin-hides-gc). ## 1. Create a City ```bash theme={"dark"} gc init ~/bright-lights cd ~/bright-lights ``` `gc init` bootstraps the city directory, registers it with the supervisor, and starts the orchestrator. The city is running as soon as init completes. ## 2. Add a Rig ```bash theme={"dark"} mkdir ~/hello-world && cd ~/hello-world && git init && cd - gc rig add ~/hello-world ``` A rig is an external project directory registered with the city. It gets its own beads database, hook installation, and routing context. ## 3. Sling Work ```bash theme={"dark"} cd ~/hello-world gc sling claude "Create a script that prints hello world" ``` `gc sling` creates a work item (a bead) and routes it to an agent. Gas City starts a session, delivers the task, and the agent executes it. This is the smallest possible job: one bead, one agent. Gas City's real power is orchestration -- you write a **formula** (the method for getting a job done) and the **orchestrator** runs it as a graph: decomposing the work into beads, fanning the ready ones out to many agents at once, gating each step on its dependencies, retrying failures, and driving the whole thing to completion outside your session. See [Formulas](/tutorials/05-formulas) and [Orders](/tutorials/07-orders). ## 4. Watch an Agent Work ```bash theme={"dark"} gc bd show --watch ``` For a fuller walkthrough of cities and rigs, continue to [Tutorial 01](/tutorials/01-cities-and-rigs). To see Gas City do the thing it exists for -- the orchestrator running a formula as a graph across many agents -- jump to [Formulas](/tutorials/05-formulas) and then [Orders](/tutorials/07-orders), which trigger formulas on a schedule or event. # Troubleshooting Source: https://docs.gascity.com/getting-started/troubleshooting Common installation and setup issues and how to fix them. If `gc start` fails after install, use the [`gc start` failure walkthrough](/troubleshooting/gc-start-walkthrough) to match the final `FATAL:` line to the likely cause and resolution. ## Run the Built-in Doctor `gc doctor` checks your city for structural, config, dependency, and runtime issues. It is always the best first step: ```bash theme={"dark"} gc doctor gc doctor --verbose # extra detail gc doctor --fix # attempt automatic repairs ``` ## Add City-Local Doctor Checks Use `[[doctor.check]]` in `city.toml` for a workspace-specific health check that does not need to be packaged as a reusable pack doctor. Provide the bare check name; `gc doctor` adds the `local:` prefix in output. ```toml theme={"dark"} [doctor] [[doctor.check]] name = "gopath-symlink" description = "Verify the GOPATH symlink used by local build scripts" script = "scripts/check-gopath.sh" fix = "scripts/fix-gopath.sh" ``` The `script` and optional `fix` paths are relative to the city root. Absolute paths and paths that escape the city directory are rejected and reported as named `StatusError` check results. Local checks reuse the same script protocol as pack doctor checks: | Exit code | Result | | - | - | | 0 | OK | | 1 | Warning | | 2 or higher | Error | The first stdout line becomes the check message. Additional stdout lines are shown by `gc doctor --verbose`. ## "does not import required builtin pack(s)" Warning Builtin packs compose only through explicit pinned `[imports]` in `pack.toml` — nothing splices them into config composition implicitly. `gc init` writes the imports (plus a matching `packs.lock`) for new cities: ```toml theme={"dark"} [imports.core] source = "https://github.com/gastownhall/gascity.git//internal/bootstrap/packs/core" version = "sha:" [imports.bd] source = "https://github.com/gastownhall/gascity.git//examples/bd" version = "sha:" ``` (The `bd` entry is written only for bd-provider cities, the default; non-bd providers get only `core`.) If a required import is missing — typically in a city created before the imports became explicit — config load still self-heals the user-global pack cache and prints a once-per-city warning: ``` warning: this city does not import required builtin pack(s) core; run "gc doctor --fix" to add the missing import(s) ``` Run the suggested fix: ```bash theme={"dark"} gc doctor --fix ``` The `builtin-pack-imports` doctor check migrates the city to the imports model: it strips legacy `workspace.includes` entries pointing at the retired per-city `.gc/system/packs` tree, adds the missing pinned import(s) to `pack.toml`, and refreshes `packs.lock` and the cache. Leftover `.gc/system/packs` directories on disk are pruned automatically. ## `gascity-pack-binding` Doctor Warning Cities created by gc v1.4.x import the public Gas City pack under the key `gascity`: ```toml theme={"dark"} [imports.gascity] source = "https://github.com/gastownhall/gascity-packs/tree/main/gascity" ``` The import key namespaces the pack's commands and skills, and the pack ecosystem is written against `gc`: the pack documents skill `gc.mayor`, and role prompts in Gas City pack releases after 0.1.6 run `gc gc claim`. Under `[imports.gascity]` that command is `gc gascity claim`, so role workers fail their first claim once the pack is bumped past 0.1.6. Current `gc init` writes `[imports.gc]`. `gc doctor` reports this as a warning; the city keeps working at the pinned 0.1.6 pack. Run: ```bash theme={"dark"} gc doctor --fix ``` The `gascity-pack-binding` check renames the key to `[imports.gc]` in `pack.toml` (or in a city.toml root `[imports]` override), keeping the source, version, and every other field. `packs.lock` is keyed by source, so no reinstall is needed. The check identifies the pack by its source (any https, http, or SSH spelling of `gastownhall/gascity-packs` with the `gascity` pack directory), not by the key name, so a fork or a different pack bound as `gascity` is left alone, and it never touches rig imports or `[defaults.rig.imports]`. The fix changes nothing and explains why when: * `[imports.gc]` already imports a different pack. Rename one of the imports by hand. * `[imports.gc]` already imports the Gas City pack with a different version or settings. Remove one of the two imports by hand. An exact duplicate (same source and version) is removed automatically. * A string value in `pack.toml` or `city.toml` is shaped like a `gascity.`-qualified name, for example a patch targeting `gascity.`. The message names the file and key. Update the value to `gc.` and rerun, or rename the import by hand if the value is unrelated. Comments are ignored. ## "command not found" After Install If `gc` is installed but your shell cannot find it, the binary is not on your `PATH`. **Homebrew** puts binaries in a directory that is usually already on your PATH. Run `brew --prefix` to confirm, then check that `$(brew --prefix)/bin` appears in your `PATH`. **Direct download** requires you to move or symlink the binary into a directory on your PATH: ```bash theme={"dark"} install -m 755 gc ~/.local/bin/gc # or /usr/local/bin/gc ``` Then verify: ```bash theme={"dark"} which gc gc version ``` If you use a non-standard shell (fish, nushell), check that shell's PATH configuration rather than `~/.bashrc` or `~/.zshrc`. ## Oh My Zsh Git Plugin Hides `gc` Oh My Zsh's `git` plugin defines `gc` as an alias for `git commit --verbose`. When that alias is active, commands like `gc version`, `gc init`, or `gc start` run git instead of the Gas City binary. Temporary workaround: ```bash theme={"dark"} command gc version command gc init ~/my-city ``` `command` bypasses shell aliases for that invocation. Persistent fix in `~/.zshrc`: ```bash theme={"dark"} source "$ZSH/oh-my-zsh.sh" unalias gc 2>/dev/null ``` The `unalias` line must come **after** Oh My Zsh loads. If it appears before `source "$ZSH/oh-my-zsh.sh"`, the `git` plugin recreates the alias later. Oh My Zsh also loads files in `$ZSH_CUSTOM` after built-in plugins, so this is a good alternative: ```bash theme={"dark"} mkdir -p ~/.oh-my-zsh/custom printf '%s\n' 'unalias gc 2>/dev/null' > ~/.oh-my-zsh/custom/gascity.zsh ``` If you do not use Oh My Zsh git aliases, you can also remove `git` from the `plugins=(...)` list. ## Missing Prerequisites `gc init` and `gc start` check for required tools and report any that are missing. You can also run `gc doctor` inside an existing city for a fuller check. ### Always required | Tool | macOS | Debian / Ubuntu | | - | - | - | | tmux | `brew install tmux` | `apt install tmux` | | git | `brew install git` | `apt install git` | | jq | `brew install jq` | `apt install jq` | | pgrep | included | `apt install procps` | | lsof | included | `apt install lsof` | ### Required for the default beads provider (`bd`) | Tool | Min version | macOS | Linux | | - | - | - | - | | dolt | 2.1.0 or newer | `brew install dolt` | [releases](https://github.com/dolthub/dolt/releases) | | bd | 1.0.4 | [releases](https://github.com/gastownhall/beads/releases) | [releases](https://github.com/gastownhall/beads/releases) | | flock | -- | `brew install flock` | `apt install util-linux` | ### Optional for GitHub gates | Tool | macOS | Linux | | - | - | - | | gh | `brew install gh` | [cli.github.com](https://cli.github.com/) | Gas City can run without `gh`. The core pack's maintenance orders skip GitHub gate checks when the GitHub CLI is not installed. If you do not want to install dolt, bd, and flock, switch to the file-based store: ```bash theme={"dark"} export GC_BEADS=file ``` Or add this to your `city.toml`: ```toml theme={"dark"} [beads] provider = "file" ``` The file provider is fine for trying Gas City locally. The `bd` provider adds durable versioned storage and is recommended for real work. ## Dolt Version Too Old Gas City requires a final Dolt 2.1.0 or newer. Older and pre-release builds are below the managed bd/Dolt compatibility floor; releases before 1.86.2 can also miss the upstream GC/writer deadlock fix in dolthub/dolt commit `ccf7bde206`, which can hang `dolt_backup sync` under heavy write load. Check your version: ```bash theme={"dark"} dolt version ``` Upgrade via Homebrew (`brew upgrade dolt`) or download a newer release from [dolthub/dolt/releases](https://github.com/dolthub/dolt/releases). ## `bd` Version Too Old Gas City requires `bd` 1.0.4 or newer. The bd-backed store relies on ephemeral-bead support used by order tracking, including `bd create --ephemeral` and `bd query ephemeral=true`, so older binaries can fail order tracking and the cleanup of those ephemeral beads. Check your version: ```bash theme={"dark"} bd version ``` Upgrade via Homebrew (`brew upgrade beads`) or download a newer release from [gastownhall/beads/releases](https://github.com/gastownhall/beads/releases). ## Native Store Falls Back Because Hooks Are Installed Native `bd` store selection intentionally falls back to the subprocess-backed store when executable `.beads/hooks/on_create`, `.beads/hooks/on_update`, or `.beads/hooks/on_close` scripts are present. Those hooks historically emitted bead events for external `bd` writes; the native in-process store does not run shell hooks. For orchestrator-managed Gas City deployments, confirm that the orchestrator is wrapping stores with `CachingStore` and emitting `bead.created`, `bead.updated`, `bead.closed`, and `bead.deleted` events to the event bus. After that migration is verified, remove the executable hook scripts from the city or rig `.beads/hooks/` directory to allow native store adoption. Keep `GC_BEADS_FORCE_FALLBACK=1` set when a deployment still depends on those hook scripts directly. ## Native Store Falls Back Because Dolt Is in Embedded Mode **Symptom:** `gc status` or `gc session list` is slower than expected, or `gc doctor` reports `native_store_unavailable gate=dolt_mode_safe`. Gas City's native in-process beads store requires that `bd context` reports `dolt_mode=server`. When `bd` is configured with an embedded Dolt instance (the default for a freshly installed `bd`), the `dolt_mode_safe` gate fails and Gas City falls back to invoking the `bd` CLI as a subprocess for every store operation. Each subprocess call adds tens to hundreds of milliseconds of overhead, which accumulates noticeably during `gc status` and `gc session list`. **Remedy:** Run `bd` against a Dolt SQL server so that `bd context` reports `dolt_mode=server`: ```bash theme={"dark"} # Start a local Dolt SQL server (one-time setup) dolt sql-server --port 28231 # Confirm bd resolves server mode bd context --json | grep dolt_mode # expected: "dolt_mode": "server" ``` Once `bd context` reports `dolt_mode=server`, `gc doctor` will clear the `dolt_mode_safe` gate and Gas City will use the native store automatically on the next start. The `gascity_native_beads` build tag visible in some source files is a test-only mechanism — it requires `GC_NATIVE_DOLTLITE_BEADS=true` and is not a supported operator path. Use Dolt server mode instead. ## `dolt_mode_safe` Preflight Gate Fails The native in-process store is unavailable after `gc start`, and the supervisor log shows: ``` native_store_unavailable gate=dolt_mode_safe reason="dolt_mode=embedded; native store requires Dolt server mode (bd context must report dolt_mode=server) — falling back to per-call bd. See troubleshooting." ``` `gc status --json | jq .beads` reports `"preflight_gate":"dolt_mode_safe"` with `"native_store_eligible":false`. The `dolt_mode_safe` gate keys off the `dolt_mode` value that `bd context` reports; a value of `embedded` fails the gate. The gate reads this from `bd context`, not from `.beads/config.yaml`, so hand-editing the config file is not the supported repair. Confirm what `bd` currently reports: ```bash theme={"dark"} bd context --json | jq .dolt_mode # should print "server" ``` **Remedy:** This is the same native-store fallback covered under [Native Store Falls Back Because Dolt Is in Embedded Mode](#native-store-falls-back-because-dolt-is-in-embedded-mode). Follow that section to run `bd` against a Dolt SQL server so `bd context` reports `dolt_mode=server`, then apply and re-check: ```bash theme={"dark"} gc restart ``` Do not bypass or disable the `dolt_mode_safe` check — it guards the store-mode contract that keeps `bd` and gc in agreement. ## flock Not Found (macOS) macOS does not ship `flock`. Install it via Homebrew: ```bash theme={"dark"} brew install flock ``` Alternatively, switch to the file-based beads provider (see above) to skip the flock requirement entirely. ## Cursor MCP Tools Still Prompt or Appear Unavailable The built-in `cursor` provider starts `cursor-agent` with `-f --trust` so an unattended worker does not stop at Cursor's workspace-trust dialog. Use it only for workspaces whose contents you trust. The flag does not approve MCP servers; Cursor's MCP approval prompt remains enabled by default. For unattended Cursor pool workers, opt in only after confirming that every workspace and user/global MCP server visible to Cursor is trusted. The `--approve-mcps` flag approves every visible server, including servers projected from Gas City's catalog into `.cursor/mcp.json` and servers from `~/.cursor/mcp.json`. ```toml theme={"dark"} [providers.cursor.option_defaults] mcp_approval = "approve" ``` If you override Cursor `args` directly, the override replaces the built-in args. Include `-f --trust` yourself and add `--approve-mcps` only for the same explicit MCP trust decision. Agent-level `args` overrides behave the same way. Existing Cursor sessions keep the command fingerprint they were created with. The supervisor reconciler restarts sessions automatically after the fingerprint changes. Drain the pool first when you need a controlled handoff rather than waiting for the next automatic restart. ## `gc version` Prints Unexpected Output If `gc version` prints git progress lines (`Enumerating objects...`) instead of a clean version string, upgrade to Gas City v0.13.4 or later. This was a bug where remote pack fetches wrote git sideband output to the terminal, fixed in [PR #141](https://github.com/gastownhall/gascity/pull/141). ## Provider Credentials Dropped When the Supervisor Starts Symptom: agents authenticate fine when you launch a city from your normal interactive shell, but fail to authenticate (or silently fall back to a different provider) when the city is started by the supervisor at login or after a reboot. Cause: the supervisor service file (launchd plist / systemd unit) captures provider credentials by snapshotting the environment of the shell that ran `gc start` (or `gc supervisor install`). A credential that is only present in an interactive shell — for example sourced from an rc file that the login service manager never reads — is not in that snapshot, so it never reaches the supervised process. Fix: put the durable credentials in a machine-local secrets file at `${GC_HOME}/secrets.env` (defaults to `~/.gc/secrets.env`). On every service file regeneration, `gc` merges this file into the supervisor environment, so the value survives a reboot regardless of which shell ran `gc start`. ```bash theme={"dark"} # ~/.gc/secrets.env (chmod 600) ANTHROPIC_API_KEY=sk-ant-... OPENAI_API_KEY=sk-... ``` The file uses dotenv syntax: `KEY=VALUE` per line, `#` comments, blank lines, an optional `export ` prefix, and optional surrounding quotes. Only keys that are already eligible for the supervisor environment are merged — provider credentials (recognized by their standard prefixes such as `ANTHROPIC_`, `OPENAI_`, `GEMINI_`) plus any keys you opt in via `GC_SUPERVISOR_ENV`; any other key in the file is ignored. A value exported in the calling shell still takes precedence over the file, and `GC_SUPERVISOR_OMIT_PROVIDER_CREDS=1` suppresses provider credentials from both sources. Apply the change by regenerating the service file: ```bash theme={"dark"} gc service restart # restarts the launchd/systemd service ``` ## A Custom Environment Variable Doesn't Reach Agent Sessions Symptom: a non-`GC_`-prefixed variable you've exported and confirmed is set (e.g. in your shell, `~/.bash_env`, or a systemd/launchd unit) never shows up inside a spawned agent session's environment, even though `gc supervisor run` itself can see it. Cause: `passthroughEnv` only forwards a variable into a session if it is either `GC_`-prefixed, part of the small fixed provider/locale/XDG set, or named in `GC_SUPERVISOR_ENV` — the same opt-in `gc supervisor install` uses to widen the persisted service-file env (see above). Everything else is dropped silently, by design: an unbounded sweep of the calling environment would leak whatever secrets happen to be sitting in the supervisor's process, into every agent session. Fix: name the variable in `GC_SUPERVISOR_ENV` in the environment the supervisor daemon itself runs with, then restart it so the daemon process picks up both the opt-in list and the variable's value: ```bash theme={"dark"} export GC_SUPERVISOR_ENV=GC_SUPERVISOR_ENV,MY_CUSTOM_VAR # the list names itself so it survives restarts; comma or space separated export MY_CUSTOM_VAR=/path/to/thing gc supervisor install # regenerates the service file with both persisted gc supervisor stop && gc supervisor start # restart the supervisor so it inherits them ``` Sessions that are already running keep their old environment; restart them to receive a newly forwarded variable. `GC_SUPERVISOR_ENV` itself needs to be present in the supervisor daemon's own environment for this to survive a later restart — it is not automatically persisted into the generated service file the way `PATH`/`GC_HOME` are. If you rely on a managed service file, either list `GC_SUPERVISOR_ENV` among its own opted-in names (`GC_SUPERVISOR_ENV=GC_SUPERVISOR_ENV,MY_CUSTOM_VAR`) or set it directly in the unit's `Environment=` lines. For a value that's the same on every city, `[workspace.env]` in `city.toml` is usually simpler than an opt-in — it doesn't depend on the supervisor's own process environment at all: ```toml theme={"dark"} [workspace.env] MY_CUSTOM_VAR = "/path/to/thing" # or, to read it from whatever the supervisor's own environment holds: MY_CUSTOM_VAR = "$MY_CUSTOM_VAR" ``` ## Supervisor Log Written Twice (journald + supervisor.log) `gc supervisor run` tees its output into `${GC_HOME}/supervisor.log` (defaults to `~/.gc/supervisor.log`) so `gc supervisor logs` works no matter how the supervisor was started. Under a hand-managed systemd unit with `StandardOutput=journal`, that tee becomes a second copy of every line: journald keeps one, and `supervisor.log` grows without rotation. Set `GC_SUPERVISOR_LOG_TEE=0` in the supervisor's environment to disable the tee so the service manager's log is the single sink. Only the literal value `0` disables it; any other value (or unset) keeps the default tee. ```ini theme={"dark"} # hand-managed ~/.config/systemd/user/gascity-supervisor.service [Service] StandardOutput=journal StandardError=journal Environment=GC_SUPERVISOR_LOG_TEE=0 ``` Scope and caveats: * **The variable matters in two places.** The supervisor process's environment controls the tee. The shell running `gc supervisor logs` controls only what that command reports: when the variable is set there and `supervisor.log` exists, the file is tailed with a staleness warning; when the file is absent, the command points at the service manager's log (`journalctl --user -u gascity-supervisor.service` on Linux) instead. A unit's `Environment=` lines are invisible to your interactive shell, so export the variable in both for coherent behavior. * **Service files generated by `gc supervisor install` or `gc start` do not need — and do not honor — the opt-out.** Generated units redirect supervisor output straight into `supervisor.log` (systemd `StandardOutput=append:`, launchd `StandardOutPath`), and the tee already suppresses itself when its output is that same file, so `supervisor.log` is the single sink in those shapes. The variable is not captured into generated service files automatically; it exists for units you manage by hand. * **To persist the variable into a generated service file anyway** — for example as a starting point you then hand-edit to `StandardOutput=journal` — opt it in explicitly and regenerate: ```bash theme={"dark"} export GC_SUPERVISOR_LOG_TEE=0 GC_SUPERVISOR_ENV=GC_SUPERVISOR_LOG_TEE gc supervisor install ``` Note that `gc start` regenerates the service file with the file-redirect defaults, so a hand-edited unit at gc's service path stays journal-only only on hosts where gc never manages the unit. ## Delegating the Supervisor Lifecycle to an Operator-Managed systemd Unit By default `gc` owns the supervisor lifecycle: `gc start` installs and starts a per-user service (`gascity-supervisor`), and binary-drift detection restarts that service directly. Hosts that run the supervisor under an operator-managed systemd unit instead — for example a hardened system service with its own restart policy — can delegate the lifecycle: ```bash theme={"dark"} GC_SUPERVISOR_SYSTEMD_UNIT=gascity-prod.service # unit that owns the supervisor GC_SUPERVISOR_SYSTEMD_SCOPE=system # "system" (default) or "user" ``` With the unit configured: * `gc supervisor start` and the `gc start` ensure path run `systemctl [--user] start ` (bounded, so a wedged unit cannot hold the CLI indefinitely) and wait for the control socket to answer. When the socket stays unreachable — the usual situation for a system-scope unit running under a different user — start falls back to the same liveness evidence `gc supervisor status` trusts: an active unit, then the supervisor HTTP API. Only when all three are silent does start fail. gc never writes, loads, or daemon-reloads its own service files in delegated mode; `gc supervisor install` refuses to run, and `gc supervisor uninstall` only removes gc's own legacy service. * `gc supervisor stop` runs `systemctl [--user] stop ` synchronously, bounded by `--wait-timeout` (default 30s) whether or not `--wait` is set, then verifies a previously-running supervisor actually exited. A live supervisor the unit does not manage (common mid-migration) fails the stop with its PID instead of reporting a false "Supervisor stopped.", and stop with nothing running keeps the legacy exit-1 "supervisor is not running" contract. * The `gc start` drift auto-restart runs `systemctl try-restart ` (a unit the operator stopped stays stopped) and fails unless the restart verifiably resolved the drift: a supervisor that was not replaced, a replacement still serving the drifted build (the unit's `ExecStart` launches a stale binary), or an unverifiable post-restart probe each fail instead of declaring "ready" while a stale supervisor keeps serving. * `gc supervisor status` probes the delegated unit (`systemctl [--user] is-active `) when the control socket is unreachable — the usual situation for a system-scope unit running under a different user — and reports a broken delegation config (a warning in text mode, a `config_error` field in `--json`) instead of a bare "not running". An invalid `GC_SUPERVISOR_SYSTEMD_SCOPE` value is a hard error on every lifecycle path; gc never silently falls back to the default unit. Setting `GC_SUPERVISOR_SYSTEMD_UNIT` on a non-Linux platform is the same kind of hard error — delegation is a systemd contract. ## JSONL Archive Push Failures The core pack runs `jsonl-export` every 15 minutes to export each bead store (the city and every rig) with `bd export` into a text-diffable JSONL snapshot inside a local git repository (the "JSONL archive"): one issue per line, with its labels, dependencies and comments. The archive serves as a disaster-recovery backup: a snapshot from any commit restores with `gc bd import `. `jsonl-export` (every 15 minutes) and `reaper` (every 30 minutes) ship in the core pack, so they are active in every city by default — including cities that previously ran them only via the opt-in gastown maintenance pack. Both reach every bead store through `gc bd`, so they work the same on bd-owned proxied, gc-managed and mixed cities. On cities whose beads provider is not bd (for example `[beads] provider = "file"`), both orders skip with a one-line message and an `order.skipped` event instead of running. To turn them off entirely, skip them by name in `city.toml`: ```toml theme={"dark"} [orders] skip = ["jsonl-export", "reaper"] ``` Cities that had skipped the old formula orders (`mol-dog-jsonl`, `mol-dog-reaper`) stay opted out; the renamed orders honor the legacy skip entries. ### Local-only vs push mode The archive operates in one of two modes, detected from the state of its git remotes on every run: * **Local-only (default).** No `origin` remote is configured. Commits are created and retained on the host but never leave the machine. This mode is safe to run indefinitely; its only limitation is that the archive is not backed up off-box, so a disk failure on this host loses the archive alongside the live Dolt data. * **Push.** An `origin` remote is configured. Each run rebases onto `origin/main` and pushes new commits so the archive survives a host loss. On each run `jsonl-export` logs the active mode to stderr on transitions (e.g. after you add or remove `origin`) and re-logs it at least weekly so that an operator reading the log file can always find the current mode. ### Enabling off-box backup Pick a repository that only this host will push to (the archive contains bead content and should not be shared across cities). Then: ```bash theme={"dark"} # Create a private repo on your git host (example: GitHub via gh) gh repo create my-city-jsonl-archive --private # Point the archive at it (run from anywhere inside your city) ARCHIVE="$(gc status --json | jq -r '.city_path')/.gc/runtime/packs/core/jsonl-archive" git -C "$ARCHIVE" remote add origin git@github.com:/my-city-jsonl-archive.git # Seed the remote with the existing local history git -C "$ARCHIVE" push -u origin main ``` On the next 15-minute tick, `jsonl-export` detects the new `origin`, logs `archive running in push mode`, and resumes pushing every run. On cities migrated from the gastown maintenance pack, the archive stays at its legacy location — `.gc/runtime/packs/maintenance/jsonl-archive` (or `.gc/jsonl-archive` for pre-pack cities) — and the `packs/core/jsonl-archive` path above does not exist. Point `ARCHIVE` at the legacy path instead; `gc doctor` reports the resolved archive path for the city. ### Switching back to local-only Remove the remote: ```bash theme={"dark"} git -C "$ARCHIVE" remote remove origin ``` Re-detection is automatic on the next run — no state-file edits are required. The next log line will read `archive running in local-only mode`. If push mode had accumulated failures before the remote was removed, local-only detection clears that stale failure counter while retaining `pending_archive_push` so deferred commits are still pushed if `origin` returns. ### Reading a `JSONL push failed [HIGH]` escalation When push mode is active and `git push` fails `GC_JSONL_MAX_PUSH_FAILURES` times in a row (default: 3), the default human escalation mailbox receives an `ESCALATION: JSONL push failed [HIGH]` message with a body shaped like: ``` Order: jsonl-export Archive: /path/to/archive Consecutive failures: 3 (threshold: 3) Last git push stderr: Remediation: - Check remote: git -C remote -v - Verify remote is reachable and credentials are valid - Temporarily suppress: export GC_JSONL_MAX_PUSH_FAILURES=99 - See docs/getting-started/troubleshooting.md#jsonl-archive-push-failures ``` Transient ref-update races are retried before the escalation counter is incremented. By default, each retry sleeps for a random delay from 1 to 5 seconds. Set `GC_JSONL_PUSH_RETRY_DELAY_MIN` to change the lower bound and `GC_JSONL_PUSH_RETRY_DELAY_SPAN` to change the random span added above that minimum. The exporter sends one HIGH escalation for a still-unresolved push failure. It continues recording `consecutive_push_failures` and `pending_archive_push` in state, but does not mail the same failure on every tick. A successful push or a switch back to local-only mode clears the escalation marker. ### Maintenance escalation and completion routing Core maintenance scripts route alerts through a generic escalation hook instead of mailing a hardcoded role. Orders inherit the orchestrator's environment, so set these at orchestrator start to customize routing: * `GC_ESCALATION_RECIPIENT` — mail recipient for escalations (default: `human`, the reserved human mailbox). An agent recipient is woken with `--notify`; the `human` default is a mailbox with no session behind it, so nothing is woken and the escalation waits until somebody reads that inbox. Point this at the agent that surfaces alerts (the manager, on a Slack-connected city) if you want maintenance advisories acted on rather than filed. * `GC_ESCALATE_SCRIPT` — absolute path to an escalation script to run instead of searching packs. * `GC_ESCALATE_SEARCH_PACKS` — space-separated pack names searched (in order) for an `assets/scripts/escalate.sh` override (default: `gastown maintenance bd core`). A pack earlier in the list wins. * `GC_ESCALATE_SEND_TIMEOUT_SECS` — wall-clock bound on one escalation send (default: 30). The wake can outlive the send it follows, and escalations run inline in maintenance orders, so the bound keeps a slow wake from stalling the run that raised the alarm. The mail is written before the wake blocks, so a tripped bound costs the wake, not the message, and the script still exits 0. * `GC_MAINTENANCE_DONE_TARGET` — session target to nudge with `MAINTENANCE_DONE:`/warn summaries when a maintenance run completes (default: unset, no completion nudge). Deployments that relied on the old hardcoded completion nudges to a health-patrol session should set this to restore that loop. Common root causes, in rough order of frequency: * **Credentials rotated or expired.** SSH key removed from the remote host, HTTPS token expired. The captured stderr usually reads `Permission denied (publickey)` or `remote: Invalid username or password`. * **Remote URL typo or deleted repo.** stderr reads `does not appear to be a git repository` or `repository not found`. * **Network partition.** stderr reads `Could not resolve host` or a connection-timeout message. If the host is also firewalled from the rest of the internet, this will recover once connectivity returns. * **Diverged history.** Very unusual — the archive rebases onto `origin/main` automatically — but if the remote was force-pushed from another host, rebase may fail with a conflict. Inspecting the archive and resolving manually is the only option. If the underlying problem cannot be fixed immediately (e.g., the remote host is down for scheduled maintenance), set `GC_JSONL_MAX_PUSH_FAILURES=99` in the orchestrator's environment and restart the city with `gc restart`. That bumps the escalation threshold from 3 to 99, which at the current 15-minute tick rate is \~24 hours of silence. ## WSL (Windows Subsystem for Linux) Gas City works under WSL 2 with a standard Ubuntu or Debian distribution. Install prerequisites using the Linux column in the tables above. tmux requires a working terminal — use Windows Terminal or another WSL-aware terminal emulator. ## Build From Source Fails Building from source requires `make` and Go 1.26.4 or newer: ```bash theme={"dark"} make --version go version ``` If `make` is missing, install it (`apt install make` on Debian/Ubuntu, or `xcode-select --install` on macOS). If your Go version is too old, update it from [go.dev/dl](https://go.dev/dl/) or via your package manager. Then: ```bash theme={"dark"} make build ./bin/gc version ``` See [CONTRIBUTING.md](https://github.com/gastownhall/gascity/blob/main/CONTRIBUTING.md) for the full contributor setup. ## Slung Beads Not Reaching Agents (managed-city mode) If `gc sling` accepts work but agents don't process it — especially if your supervisor log shows `rigStores=0` or `assignedWorkBeads=0`, or your `bd dolt set port` edits keep reverting at the next `gc start` — you're likely looking at a rig whose Dolt view has drifted from the managed city Dolt. Do **not** edit `.beads/dolt-server.port` or `bd dolt set port` directly; both self-revert. See the [Managed-city Dolt endpoints runbook](/runbooks/managed-city-endpoints) for the mental model, the forbidden edits, the sanctioned escape hatches (`gc rig set-endpoint --inherit`/`--self --force`/`--external`), and an end-to-end recovery recipe. ## Still Stuck? If a symptom only makes sense once you know how the pieces fit together, see [The six primitives](/getting-started/how-gas-city-works) for the underlying model. Open an issue at [gastownhall/gascity/issues](https://github.com/gastownhall/gascity/issues) with the output of `gc doctor --verbose` and your OS/architecture. # Coming from Coding Agents Source: https://docs.gascity.com/guides/capabilities-for-coding-agent-users How context, state, skills, history, messaging, roles, and identity work in Gas City — mapped to what you already know from coding agents. You know these capabilities from coding agents (Claude Code, Codex, Gemini CLI, Cursor, …) as features of a single agent. In Gas City they are infrastructure shared across many agents. Here is the quick map, ordered from the most basic to the most multi-agent. | Capability | Coding agents (Claude Code, Codex, …) | Gas City | | - | - | - | | Context | The window you fill: `CLAUDE.md`, open files, chat, … | An agent role, plus injected work items and mail (all beads) | | State | The context window; persist by hand to files | **Beads** — durable, queried live | | Skills | A `.claude/skills//` directory | The same directory, shared by scope (whole city, or one role) | | History | Recorded session transcripts (resumable) + manual hand-off notes | Bead history + per-session logs + a city-wide event log | | Messaging | None between distinct agent sessions; at most a communication mechanism between subagents in the same session | **Mail** (a bead) + **nudge** (wake a live session) | | Roles | A subagent file (`.claude/agents/.md`) | An agent folder (`agents//`) | | Identity | The one session you're in | A stable name per running agent (`hello-world/pack.worker_furiosa`, …) | The rest of this page is one delta per capability — what changes when the single-agent feature becomes shared infrastructure. Agents coordinate only through the store: mayor, reviewer, and worker each read and write the shared bead store (slinging work, claiming ready work, mailing a bead) — no arrow connects two agents directly. A nudge can wake a live session, but the work itself only ever moves through beads. ## Context The window is seeded automatically, per agent, from durable sources — you never hand-assemble it. * It starts from the agent's **role**: a prompt template rendered with deployment data (city, rig, working directory, branch, custom variables). * Its current **work items** and **mail** — all [beads](/tutorials/06-beads) — flow in live as it works. ## State Durable state is first-class, not something you persist by hand. Everything is a [**bead**](/tutorials/06-beads): a stored work item with status, labels, relationships, and metadata, queried live (`bd`, `gc`) — never tracked in status or lock files that go stale on a crash. Sessions come and go; the beads remain. ## Skills You author a skill once at a scope, and Gas City materializes it to every eligible agent — no per-agent allow-lists, and the model decides when a skill applies. * Pick the scope: * `skills//` at **pack level** → shared with **every** agent in the city. * `agents//skills//` at **role level** → only agents of that role (and all its pooled instances). On a name collision, the role-local skill wins. * At startup Gas City **symlinks** the pack level and role level skill directories into each agent's provider-specific skill sink — `.claude/skills/`, `.agents/skills/` (codex), `.gemini/skills/`, `.opencode/skills/`. List with `gc skill list`. * It *places* the files into each provider's own convention; it doesn't translate them. Providers whose convention isn't confirmed (copilot, cursor, pi, omp) are skipped for now. * No framework *around* skills: no per-agent allow-lists. Within a scope every eligible agent gets every skill; the model decides when one applies. * MCP is list-only today (`gc mcp list` shows what's catalogued; you wire the servers yourself). ## History History is structured and queryable across every agent, not per-session files you manage yourself. | Layer | What it records | Read with | | - | - | - | | **Bead history** | Each work item's create → update → close trail, independent of any session — the durable memory of *what was done* | `bd`, `gc` | | **Session logs** | One agent's conversation: your prompts, the model's replies, its tool calls | `gc session logs ` (`-f` to follow) | | **Event log** | An append-only, city-wide feed of system activity (sessions waking, mail sent, work created) | `gc events` | ## Messaging Agents that share no session still communicate, over two channels. | Channel | Durable? | What it is | Send with | | - | - | - | - | | **Mail** | Yes — a [bead](/tutorials/06-beads) (type `message`) | Sender, recipient, subject, body; threads and waits in an inbox until read. Agents typically pull new mail into context each turn via a hook | `gc mail` | | **Nudge** | No | A direct poke into a live session — text typed straight into a running agent to wake or redirect it now | `gc session nudge "msg"` | ## Roles A role is a folder, not a single subagent file. `agents//` holds an `agent.toml` (provider, pool, timeouts) and a `prompt.template.md` defining what that *kind* of agent does. ## Identity A specific running instance has a stable name you can address — across restarts. * Each live agent has a deterministic session name (e.g. `hello-world/pack.worker_furiosa`), so you and other agents can message, wake, peek at, and resume exactly that one. * One role instantiates into many identities (a pool of `worker_furiosa`, `worker_nux`, …). See them with `gc session list`. * Role (the kind), identity (the running instance), and pool (the set) are all facets of the single **Agent** primitive — see [the six primitives](/getting-started/how-gas-city-works). ## See also * [The six primitives](/getting-started/how-gas-city-works) — the canonical model; start here. * [Coming from Gas Town](/getting-started/coming-from-gastown) * [Tutorial 04: Communication](/tutorials/04-communication) — mail and nudge. * [Config Reference](/reference/config) # Configuring an Agent Source: https://docs.gascity.com/guides/configuring-an-agent The five axes of an agent — harness, model, upstream, transport, runtime — and how to set each in city.toml / agent.toml. The [Agents tutorial](/tutorials/02-agents) shows the fast path: drop an `agent.toml` with a `provider` and a prompt, and sling work to it. This guide is the how-to reference for everything you can tune underneath that — the **five independent axes** of an agent — with a deep dive on the **upstream** axis (who serves the model), which is the newest knob. For the exact field list and types, see the generated [Config Reference](/reference/config). This guide explains *how the pieces fit*; the reference is the authoritative spec. ## The five axes An agent is the composition of five orthogonal choices. You set each one independently — change the model without touching the harness, switch who serves the model without changing the box, and so on. | Axis | Question | Where you set it | Example | | - | - | - | - | | **Harness** | which agent CLI? | agent `provider` | `provider = "claude"` | | **Model** | which model label? | agent `option_defaults.model` | `option_defaults = { model = "sonnet" }` | | **Upstream** | who serves the model? | agent `upstream` + `[upstreams.]` | `upstream = "bedrock"` | | **Transport** | how does gc drive it? | agent `session` | `session = "acp"` | | **Runtime** | where does it run? | city `[session] provider` / `GC_SESSION` | `provider = "k8s"` | > **A note on the word "provider."** It is overloaded by history. In an > `[[agent]]` / `agent.toml` block, `provider` selects the **harness** (the agent > CLI — `claude`, `codex`, …). In the city `[session]` block, `provider` selects > the **runtime backend** (where sessions run — `tmux`, `k8s`, …). They are > different axes; this guide always says "harness" or "runtime" to disambiguate. The first three axes (harness, model, upstream) are per-agent. Transport is per-agent. Runtime is city-/environment-wide (every session in a city runs on the same backend). ## Axis 1 — Harness (`provider`) The harness is the agent CLI gc launches and drives. Gas City ships built-in presets for the popular ones — `claude`, `codex`, `gemini`, `grok`, `kimi`, `cursor`, `copilot`, `amp`, `opencode`, and more — so the minimal agent is just: ```toml theme={"dark"} # agents/reviewer/agent.toml dir = "my-project" provider = "codex" ``` Each preset defines the command, default args, resume behavior, prompt delivery, and the model + upstream contracts (below). Customize one with a city-level `[providers.]` block: a same-named block merges over the built-in (you set only what you change), and a differently-named one can inherit from any provider via `base`: ```toml theme={"dark"} # city.toml — customize the built-in claude harness [providers.claude] args = ["--verbose"] # everything else inherits from builtin:claude # …or define a new harness that inherits from a built-in [providers.claude-fast] base = "builtin:claude" option_defaults = { model = "haiku" } ``` When `command` points to a launcher wrapper, also set `resume_command` to invoke that wrapper with the harness's resume arguments and `{{.SessionKey}}`. The inherited resume command is a separate setting; changing `command` alone can leave resumed sessions launching the original executable. If a wrapper redirects the harness to a private configuration directory, prepare required startup state in that directory too. Verify a fresh interactive launch as well as resume: a non-interactive prompt can skip first-run screens that would block an interactive agent. Keep wrapper diagnostics out of the interactive terminal so they cannot overwrite startup menu options. Match `process_names` to the executable actually running inside the wrapper, and verify that Gas City still reports the agent alive after startup and while it claims work. For copy-paste setup of each built-in harness — the env vars it reads and the direct / custom-endpoint / model permutations — see [Harness Recipes](/guides/harness-recipes). See [Understanding Packs](/guides/understanding-packs) for shipping a harness preset as a reusable pack. ## Axis 2 — Model (`option_defaults.model`) A harness exposes its configurable knobs through an **options schema**. The `model` option is the common one; selecting a value injects the right CLI flags for that harness — you pick an abstract label, the harness renders the flag. ```toml theme={"dark"} # agents/reviewer/agent.toml provider = "codex" option_defaults = { model = "sonnet", permission_mode = "plan" } ``` `option_defaults` is a map of option key → choice value. The provider's `options_schema` declares the allowed choices and the `flag_args` each one emits (see `ProviderOption` / `OptionChoice` in the [reference](/reference/config)). This is why you write `model = "sonnet"` and not a raw `--model` flag: the schema keeps the model selection portable and the flags server-side. ## Axis 3 — Upstream (who serves the model) **This is the newest axis.** The *model* is *what* you ask for; the *upstream* is *who serves and resolves it* — direct Anthropic, Bedrock, Vertex, a self-run proxy, an OpenAI-compatible gateway. Switching upstream changes the base URL and credentials the harness talks to, **without changing the model, the harness, or the box** — and (post-un-weld) without re-provisioning: it relaunches the agent in the warm box. ### Declare an upstream, select it Upstreams are named presets at the city level; an agent selects one by name. ```toml theme={"dark"} # city.toml [upstreams.bedrock] description = "Anthropic models via AWS Bedrock" base_url = "https://bedrock.example.com/anthropic" api_key = "$AWS_BEDROCK_KEY" # a $VAR ref — never inline a secret [agent_defaults] upstream = "bedrock" # city-wide default for every agent ``` ```toml theme={"dark"} # agents/reviewer/agent.toml — this agent overrides the default upstream = "anthropic-direct" ``` Resolution order for an agent's upstream: agent `upstream` → `agent_defaults.upstream` → unset (no upstream env injected; the harness uses whatever is ambient). ### Abstract vs. raw — and why abstract is portable An upstream can be written two ways, which compose: **Abstract (portable).** `base_url`, `api_key`, and `auth_token` are **harness-agnostic**. The resolver renders them onto *that harness's* env-var names, declared by the harness as its `upstream_env` binding. So one upstream works on any harness: ```toml theme={"dark"} [upstreams.bedrock] base_url = "https://bedrock.example.com/anthropic" api_key = "$AWS_BEDROCK_KEY" ``` * on a `claude` agent → `ANTHROPIC_BASE_URL` + `ANTHROPIC_API_KEY` * on a `codex` agent → `OPENAI_BASE_URL` + `OPENAI_API_KEY` The built-in harnesses ship their bindings out of the box (claude → `ANTHROPIC_*`, codex → `OPENAI_*`, gemini → `GOOGLE_GEMINI_BASE_URL`/`GEMINI_API_KEY`, and so on). A custom harness declares its own: ```toml theme={"dark"} [providers.myharness.upstream_env] base_url = "MYHARNESS_BASE_URL" api_key = "MYHARNESS_API_KEY" ``` > An abstract field with **no** matching harness binding (and no override, below) > is a **hard error**, never a silent no-op — you find out at resolution time, > not when the agent quietly talks to the wrong endpoint. **Raw (escape hatch).** When the abstract trio doesn't cover what a harness needs, set raw env keys with `env`. They merge **after** the abstract render, so they win: ```toml theme={"dark"} [upstreams.bedrock] base_url = "https://bedrock.example.com/anthropic" api_key = "$AWS_BEDROCK_KEY" [upstreams.bedrock.env] AWS_REGION = "us-east-1" CLAUDE_CODE_USE_BEDROCK = "1" ``` ### Gateway harnesses — the per-field override Some harnesses are **gateways**: one CLI (e.g. `opencode`) fronts many upstreams whose credential env var is *upstream-dependent* (`GROQ_API_KEY` for Groq, `CEREBRAS_API_KEY` for Cerebras, …). Such a harness has no single binding to declare. The upstream names its own target with the per-field `*_env` overrides: ```toml theme={"dark"} [upstreams.groq] api_key = "$GROQ_KEY" api_key_env = "GROQ_API_KEY" # this upstream renders api_key to GROQ_API_KEY ``` Per-field precedence is: **upstream `*_env` override → harness binding → hard error.** Native single-upstream harnesses stay fully abstract via their binding; gateways supply the name themselves. ### Secrets and fingerprints * **Secrets are never inlined.** Abstract and raw values may reference controller env vars with `$VAR` / `${VAR}`, expanded at resolution — so `api_key = "$ANTHROPIC_API_KEY"` keeps the secret out of `city.toml`. * **Switching the upstream name** relaunches the agent in the warm box (it is a launch-half fingerprint change). **Rotating the key** within the same upstream moves no fingerprint — the resolved serving env is excluded from the hash, so a credential rotation never churns live sessions. ## Axis 4 — Transport (`session`) The transport is *how* gc drives the harness. The default is **tmux** (gc sends keystrokes and captures the pane). Set `session = "acp"` to drive the harness over the [Agent Client Protocol](/reference/exec-session-provider) (JSON-RPC over stdio) instead — the harness's resolved provider must declare `supports_acp = true`. ```toml theme={"dark"} # agents/reviewer/agent.toml provider = "claude" session = "acp" # drive over ACP instead of tmux ``` ## Axis 5 — Runtime (where it runs) The runtime is *where* the session's box lives. It is selected city-wide via the `[session]` block (or the `GC_SESSION` environment variable), not per-agent: ```toml theme={"dark"} # city.toml [session] provider = "k8s" # run every session in a Kubernetes pod ``` Built-in runtime backends: `tmux` (local, default), `subprocess` (local, headless), `k8s` (pods), `ssh:user@host` (a remote box over SSH), and `exec: