Gas City Master Architecture & Operational Specification¶
Status: Bluefly guide (downstream of current docs.gascity.com)
Last Consolidated: 2026-09-13
Supersedes: gascity-integration-standard.md, gascity.md, gascity-operational-reference.md, gas-city-pack-structure.md, gas-city-tool-boundaries.md
Sources: How Gas City Works, Tutorial 01, Beads topology, Managed-city endpoints, city.toml configuration, Connected Clients, Split Storage Classes
1. Core Architecture Primitives¶
Gas City has six primitives. Source: How Gas City Works. Do not expand this list in Bluefly docs.
| Primitive | Role |
|---|---|
| Agent | WHO — configured worker; roles are Pack configuration, not hardcoded |
| Bead | WHAT — durable work unit; convoys/sessions/mail are beads by type |
| Formula | HOW — reusable method; applying it materializes Beads |
| Rig | WHERE — external project, usually Git |
| Pack | CONFIGURES — agents, formulas, orders; the City is the local/root Pack plus deployment |
| Event | OBSERVE — outbound notification |
City, Session, Order, Sling, Convoy, Drain, Mayor, Witness, Refinery, Polecat, Deacon, Provider, and Supervisor are not additional primitives. A reviewer, mayor, planner, or worker is Agent configuration supplied by a Pack. Drain is a Formula v2 fan-out construct. Factory policy for that distinction lives in factory-operating-contract.md.
Rig declaration vs path binding & $GC_HOME vs <city>/.gc¶
Governing model (Tutorial 01 + installed gc 1.4.1):
$GC_HOME (default ~/.gc/) : Client-global configuration (contexts.toml, auth keys, global caches)
<city>/city.toml : Portable City & Rig declarations (names, providers, imports)
<city>/.gc/site.toml : Machine-local Rig path bindings (materialized on the host)
<city>/.gc/worktrees/ : Ephemeral agent execution worktrees (auto-pruned upon task completion)
<city>/.gc/store/ : High-churn SQLite infra store (sessions, orders, nudges, graph)
Do not put path= on [[rigs]] in city.toml. Do not confuse client $GC_HOME with the City's hot runtime directory <city>/.gc/.
Worktree Lifecycle & Auto-Pruning¶
To eliminate unbounded disk growth from accumulated agent worktrees, Gas City provides native lifecycle cleanup:
- auto_prune_worker_dir = true: Removes pool-managed worker worktrees upon session closure after verifying clean git status, zero unpushed commits, and clean stash.
- auto_reap_closed_bead_worktrees = true: Automatically prunes worktrees tied to closed Beads.
Beads / Dolt Topology & Split Storage Classes¶
DURABLE WORK GRAPH (Dolt):
One Dolt server per city + scoped .beads/ namespaces + issue_prefix filtering.
Dolt auto-GC enabled (auto_gc_enabled = true) to prevent noms journal bloat.
HIGH-CHURN INFRASTRUCTURE (SQLite):
storage.classes: graph, sessions, messaging, orders, nudges -> sqlite-beads at .gc/store
Isolates high-frequency ephemeral churn from the durable work ledger.
Connected-Client API (Drupal / ContextControl Integration)¶
Gas City exposes an HTTP/SSE control surface for headless external operators (Connected Clients Guide): - External clients (e.g. Drupal ContextControl.ai) register a client ID, subscribe to durable reply SSE streams, and submit execution turns to agent sessions. - Allows Drupal/AMCS to act as the human front-end and site-building orchestrator without embedding or duplicating the agent runtime.
Bluefly policy layered on top¶
- Formula v2 default selection. Bluefly selects Formula v2 as the default for new governed modernization workflows because it supports durable multi-agent graphs, dependency gating, retries, and drains. Formula v1 remains supported upstream and may remain appropriate for compatible single-agent workflows.
- Customer AMCS Packaging. Customer deployments package the reusable capability layer as Bluefly Packs, Formulas, Skills, and Recipes—provisioning only the customer site rig and necessary integration rigs, avoiding permanent clones of the full 136-project estate.
Work discovery (pull/claim)¶
Gas City drives graphs of Beads through dependencies. Agents do not browse a global backlog.
MAYOR/COORDINATOR --gc sling--> BEAD --work_query--> gc hook --claim--> AGENT --> GitLab MR --> release/v0.1.x --> bd close
Session start recovers context (bd prime when needed), then gc hook → bd show → bd update --claim. bd prime is not the work picker. Do not replace the hook with fleet-wide bd ready unless performing coordinator triage. Bluefly execution contract: beads-work-ownership-contract.md. Git/worktree mechanics are not Beads semantics — no .git/checkouts, no automatic git pull --rebase. Feature/fix/chore MRs target release/v0.1.x, never main.
2. Pack and City Layout (Bluefly bindings)¶
Upstream owns portable pack and city directory layout. Bluefly conforms to the official pack specification and understanding packs guides.
Bluefly-owned rules (not duplicated from upstream):
- Packs never contain hostnames, credentials, or customer-specific deployment paths.
- Portable city/rig identity lives in city.toml. Machine-local path bindings live in .gc/site.toml. Do not put workstation path= into city.toml as the normal model.
- Session transport defaults follow exec session provider when ACP or named sessions are required.
3. Upstream Ecosystem & Federated Boundaries¶
┌────────────────────────────────────────────────────────┐
│ DRUPAL (a Rig: app/business behavior) │
│ (Fleet Registry, Cedar Compliance, JSON:API) │
└───────────────┬───────────────────────────────┬────────┘
│ │
▼ (REST / kmcp / A2A) ▼ (REST / ACP)
┌────────────────────────────────┐ ┌─────────────────────────────┐
│ KAGENT │ │ OPENCLAW │
│ (Kubernetes-Native CRDs) │ │ (Autonomous Agent Gateway) │
│ • Agent, ModelConfig, MCPServer│ │ • Skills-as-Markdown │
│ • khook events & OpenShell │ │ • ACP Bridge / Lane Queue │
└────────────────────────────────┘ └─────────────────────────────┘
3a. Authority order for Gas City semantics¶
Current docs.gascity.com is the primary authority for Gas City semantics. Bluefly docs are downstream policy/configuration and must conform to it, not reinterpret it.
- Current
docs.gascity.com - Current official Gas City specs, reference, and runbooks;
beads.gascity.comfor Beads/bd(a separate upstream project) - Current upstream implementation and tests (
github.com/gascityhall/gascity,github.com/gascityhall/beads) when docs conflict or are incomplete - Live behavior of the exact installed
gcversion - Bluefly configuration and deployment facts (formulas, packs,
city.toml, Oracle/NAS runtime evidence) - This spec / BluCity-Docs policy
- Historical chats and old artifacts
If two upstream pages disagree, record both URLs. Do not silently choose the reading that fits an old Bluefly or Gas City assumption.
Recorded upstream wording gap: Beads Storage Topology describes isolation as issue_prefix query filtering on one Dolt server. Managed-city endpoints also describes each rig as a logical Dolt database inside that server. Both agree on one Dolt process per city and no inherited-rig local server. Investigate native_store_unavailable / identity_match against that topology; do not invent a third ledger model.
Doc-vs-source disagreement is DOC_DRIFT=YES — fix the doc; do not bend working source to stale doctrine.
Verified facts (2026-09-02, cite before reusing rather than re-deriving):
| Claim | Upstream source | Verified behavior |
|---|---|---|
bd --type= valid values |
beads.gascity.com/cli-reference/types.md |
Core: bug, task, feature, chore, epic, decision. Custom types (e.g. molecule) require types.custom in .beads/config.yaml. wisp is not valid anywhere, upstream or Bluefly-custom — a formula using it is a pure Bluefly authoring bug, not an upstream issue. |
bd list --include-infra |
beads.gascity.com/cli-reference/list.md |
Documented scope is agent/role/message beads only. Its effect on molecule-type ephemeral wisps is undocumented — verify empirically inside a live rig (cwd-bound bd, not gc bd) before treating it as the fix for a reconcile-query gap. |
gc doctor side effects |
docs.gascity.com/reference/cli states checks are "read-only by default... unless --fix is explicitly used" |
CONFLICT, not reconciled. RETRIEVED 2026-09-09: empirically, a plain gc doctor run (no --fix) against a city with dolt.auto-start: false was traced via OS process-parent chain (ps -o pid,ppid,command) to spawning gc __gc-managed-dolt-scope-watchdog and a dolt sql-server child, twice, on separate invocations. Doc claim and observed binary behavior disagree — treat gc doctor as capable of starting the managed Dolt server until upstream clarifies, and never run it against a city you intend to keep stopped. Addendum, RETRIEVED 2026-09-09, github.com/gascityhall/gascity/blob/main/engdocs/design/beads-dolt-contract-redesign.md + .../engdocs/contributors/dolt-regression-audit.md: upstream's own Beads/Dolt Contract Redesign design note explicitly classifies gc doctor as a non-owner flow that "must delegate lifecycle mutations through the active owner or fail closed," and a separate contributor regression-audit doc states "doctor no longer invokes its own Dolt target resolution" for a related port-resolution bug class — i.e. upstream has designed against exactly this failure mode. Neither source confirms this redesign has shipped in the gc release Bluefly runs; both are contributor-facing design/audit docs, not docs.gascity.com release notes. This does not overturn the empirical finding above — it only establishes upstream's intended target behavior, which the observed binary may not yet match. |
.beads/config.yaml gc.endpoint_origin values |
docs.gascity.com/reference/internal/beads-topology |
RETRIEVED 2026-09-09: four documented values, not two. managed_city — city runs its own local Dolt, port lives in .beads/dolt-server.port, gc start/gc stop own its lifecycle. inherited_city — rig has no endpoint of its own, resolves through the city. city_canonical and explicit — both point at an externally-managed Dolt server that gc does not launch. Update, RETRIEVED 2026-09-09, github.com/gascityhall/gascity/blob/main/engdocs/design/beads-dolt-contract-redesign.md: the "Beads Dolt Contract" design note referenced below is not unpublished — it is public in upstream's own engdocs/ tree (contributor design/rationale doc, not the shipped docs.gascity.com reference page). It documents the full model: City scope allows only managed_city or city_canonical; Rig scope allows only inherited_city or explicit; the hard invariant is "no rig may independently become a second managed-local server." The companion field gc.endpoint_status is documented there (two values: verified, unverified); managed_city is defined as always verified because its topology is GC-owned and does not depend on external verification — an unverified external endpoint can be adopted with --adopt-unverified but stays unverified until explicit repair validates it, so a stored verified value on a city_canonical/explicit origin still should not be trusted without corroboration. Topology-mutating commands are named explicitly and are documented as the only legal way to change origin/status: gc beads city use-managed, gc beads city use-external, gc rig set-endpoint, gc beads database rename (the only command allowed to rewrite dolt_database), gc beads city recover (the only command allowed to request managed-local Dolt adopt/restart) — every topology/migration op is documented to acquire a city-scoped lock before mutating canonical files. Caveat: this is still a design note ("preserves design history and rollout rationale"), not confirmed as the exact shipped behavior of the current gc release — treat the command list and the doctor non-owner classification as documented intent, and corroborate against the installed binary's actual behavior before relying on it operationally. |
bd raw port resolution |
docs.gascity.com/runbooks/managed-city-endpoints |
RETRIEVED 2026-09-09: ordered chain — (1) $BEADS_DOLT_SERVER_PORT env var, (2) .beads/dolt-server.port file, (3) .beads/config.yaml dolt.port key, (4) .beads/metadata.json historical record. No documented mechanism exists for raw bd to reach a ledger on a different host — inherited_city and gc rig set-endpoint --external are both still workspace-local config, not a distributed access API. Remote agent messaging (turns/replies over SSE) is a separate, documented surface — see guides/connected-clients (extmsg, default http://localhost:7375) — and must not be conflated with raw ledger access. |
4. Strict CLI Boundaries & Sprawl Prevention¶
To prevent the generation of custom AI sprawl ("slop"), agents must strictly operate within their designated tools:
| CLI Tool | Authorized Domain | Forbidden Actions |
|---|---|---|
ossa |
Manifest semantics, template compile, OpenAPI mapping | Spawning agents, publishing, memory operations |
duadp |
Discovery, registration, publishing | Compiling manifests, execution |
context-cli |
Memory querying, BM25 indexing (qmd), research |
Policy decisions, network routing |
contractplane-sdk |
Policy validation (Cedar engine evaluation) | Tool discovery, execution |
blu-cli / gc |
Agent spawning, RPP provider setup, execution | Manifest compiling, discovery |
dragonfly |
Testing, verification probes | Deployments, schema compilation |
AI Sprawl Anti-Patterns¶
An agent is generating sprawl when it:
1. Creates a custom bash/Python wrapper instead of using a native Order or Command.
2. Copies secrets or tokens into files instead of referencing op://.
3. Spawns a new agent with bespoke dispatch logic instead of using gc sling <agent> <bead> or a formula step (the Gas City supervisor owns routing).
4. Creates a new repository without an authoritative Pack owner established first.
5. Writes custom CI YAML instead of importing from gitlab_components.
6. Invents a local queue instead of using the authoritative Beads/Dolt server.
7. Uses a harness-native chat/message surface (Claude messages, subagent replies, terminal scrollback) as an inter-agent control plane instead of Gas City mail, gc sling, or Beads.
Communication plane (binding)¶
Harness-native chat is not a control plane. A message that exists only in a Claude session, a subagent reply, or terminal scrollback is lost when that session ends, is invisible to every other agent, and cannot be reconciled by the orchestrator. Routing that depends on it is not routing.
| Purpose | Mechanism | Durability |
|---|---|---|
| Agent/operator communication | gc mail send <session-alias> -s "<subject>" -m "<body>" |
Message bead |
| Operator-directed message | gc mail send human ... |
Message bead |
| Durable work assignment | gc sling <target> <bead-id> |
Bead, reconciled by orchestrator |
| Work state, evidence, blockers, completion | Beads | Dolt |
| Inter-agent control | Never Claude-native chat/messages | — |
Addressing. gc mail send resolves session aliases or the literal
human; --all broadcasts only to live sessions. Two consequences that are
routinely discovered the hard way:
- A sender must be a real session.
--from <name>fails withinvalid sender: session not foundwhen<name>is a role that is not materialized. Omit--from(it defaults from$GC_SESSION_ID/$GC_ALIAS/$GC_AGENT, elsehuman) and identify the authoring role in the body. - A recipient that is not live may not resolve. Do not fall back to
harness chat. Route the work durably with
gc slingand let the session receive it when it comes up, or record the finding on the Bead — then continue with executable work.
--notify is a wake attempt. It nudges the recipient. While a
materialization freeze naming MANUAL_SESSION_WAKE=STOP is in force, send
without --notify; the message bead is durable regardless and will be read when
the session next runs.
5. Workflow Conversion Rules (Legacy → Native Primitives)¶
| Legacy Pattern | Native Replacement |
|---|---|
| Custom Python runner / bash wrapper | POSIX shell Command in commands/ |
| Custom TOML execution schemas | Standard PackV2 Order in orders/ |
| Custom HTTP health check TOML | Executable shell script in doctor/<name>/run.sh (exit 0 = pass) |
| Cron + bash deploy scripts | Native Order with trigger = "cooldown" or trigger = "cron" |
gt → gc migration¶
Most gt commands migrate 1:1 to Gas City. Use the official Gas City → Gas City command map — do not maintain a local duplicate table.
Bluefly still operates Gas City structures on Oracle until migration is verified per receipt; treat upstream command documentation as authoritative for mappings and removals.
6. City configuration (city.toml vs .gc/site.toml)¶
Schema, merge order, provider blocks, and scaling fields are defined upstream in the configuration reference. Do not duplicate the schema in this spec.
Bluefly-owned bindings:
- Formula v2 is the default compiler schema for new formulas (see formula spec v2).
- [[rigs]] in this file is portable identity (name, prefix, imports). Paths belong in .gc/site.toml.
- Legacy [packs.*] entries may remain for migration/fetch compatibility until city imports are fully normalized.
- API bind defaults and auth key requirements follow upstream; production overrides belong in deployment evidence, not this document.
7. Runtime authority and open discrepancies¶
Oracle Gas City is production runtime authority. Live topology, rig counts, agent pool sizes, ports, and gc doctor output are runtime evidence — record in receipts and operational inventories, not in this architecture spec.
Settled (ADR-0026, Accepted): Gas City is the sole orchestration runtime. The official Gas City pack is imported configuration, not a parallel runtime or a seventh primitive set. Oracle still running Gas City structures (Gas City pack roles) is incomplete cutover, not the target architecture. Seeing Mayor / Witness / Refinery / Polecat in a city that imports that pack is Pack-supplied Agent configuration.
Upstream framing (RETRIEVED 2026-09-02, gascity.com/guide/gas-city-gas-town-gasworks-whos-who/): Gas City is upstream's own predecessor project, not a Bluefly artifact — "the predecessor software-factory project that inspired Gas City," whose architecture Gas City "restructured... into configurable primitives." Do not describe Gas City as a substrate Gas City runs on top of.
Gas City source organization: github.com/gascityhall is the current upstream source org. Stale pointers to steveyegge/gascity in upstream llms.txt are documentation drift — not Bluefly authority.