Skip to main content
This document specifies the Gas City pack system as a data model, file format, and loading process. The current pack schema is 2; this document uses “pack” for the authoring surface. The key words “must”, “must not”, “required”, “shall”, “shall not”, “should”, “should not”, and “may” are to be interpreted as normative requirements unless the paragraph is explicitly marked as non-normative.

0. Data And Information Model

The pack system separates the format of a pack from the loading of a pack. The format is the directory and TOML data a pack author writes. Loading is the process that resolves dependencies, stamps definitions into a city or rig context, applies patches and defaults, and produces one effective City configuration.

0.1. Pack

A pack is a directory containing a pack.toml file and zero or more definition, asset, and support files. The pack.toml file is the pack’s metadata and manifest. Other files are either definition files discovered by well-known rules or private files referenced by TOML fields. A pack may be used in three contexts:
  1. As the city pack. The city pack is the root pack: the city directory containing city.toml and the root pack.toml.
  2. As a city-level imported pack. Its city-scoped and unscoped definitions are loaded into the city-level surface.
  3. As a rig-level imported pack. Its rig-scoped and unscoped definitions are loaded into a specific rig surface and stamped with that rig’s name.

0.2. Pack Identity

A pack identity consists of:
The name identifies the pack as a product and as a provenance label. It must be present in [pack]. The schema identifies the pack file-format schema. The schema specified by this document is 2. A loader must reject a pack whose schema is omitted, zero, or greater than the loader’s supported schema. The version is pack metadata. Version selection is controlled by import constraints and lockfile resolution, not by a loader comparing [pack].version directly. The requires_gc field is optional metadata for the minimum compatible gc version. It is parsed and preserved as pack metadata. The current loader, importer, and doctor surfaces do not enforce the constraint during load, import, or validation; enforcement must be introduced by an explicit future implementation change before pack authors can rely on it as a hard gate.

0.3. Pack Contents

A pack may contain the following abstract content: The pack.toml file is the manifest. Well-known directories are the primary format for pack contents that have moved out of inline TOML.

0.4. Public And Private Content

Pack definitions are visible to consumers according to loader scope rules. Files under assets/ are private support files unless a public definition references them. The current loader gives import bindings a runtime namespace for agents. If a city-level pack imported under binding review defines an agent named reviewer, the effective city-level agent name is review.reviewer. The binding separator is a dot; the slash separator is reserved for the rig identity prefix (section 2.4).

1. File System Structure

A pack directory has this abstract shape:
Only pack.toml is required.

1.1. Directory Rules

The pack root must be a directory. The pack root must contain a file named pack.toml. The following top-level paths are reserved by the pack format: Pack authors must not invent additional top-level machine-readable directories. Private support files should go under assets/, or under the specific well-known directory that owns them. This reserves the top-level namespace for future pack-format expansion. The following file-system constructs must not be used as public pack format:

1.2. The pack.toml File

The pack.toml file is UTF-8 TOML. It must contain a [pack] table. Conceptually, the file may contain these tables: Unknown fields are not part of this specification. The current loader rejects unknown pack.toml fields and unknown [imports.<binding>] fields; authors must not rely on unknown keys being preserved or interpreted.
Compatibility: The current loader still accepts legacy inline pack.toml [[agent]], [[doctor]], and [[commands]] entries for existing packs. New packs should use agents/, doctor/, and commands/.

1.2.1. [pack]

The [pack] table defines pack metadata.
Compatibility: The current loader still accepts [pack].includes for legacy pack composition. New packs must use [imports.<binding>].

1.2.2. [pack.requires]

Each [[pack.requires]] entry declares an agent that must exist after loading.
City-scoped requirements are validated against the expanded city agent list. Rig-scoped requirements are validated while loading the pack for a rig.

1.2.3. [imports.<binding>]

Pack imports are named dependencies.
The binding name is local to the importing file. Current loader behavior uses binding names for deterministic ordering of imports and stamps the binding on every agent the import contributes, qualifying runtime agent identities (section 2.5). The imported pack’s declared name and any registry display name are advisory: they can suggest a binding or provide UI copy, but they are not durable pack identity. Durable identity comes from source plus the optional version constraint or pin. A source string is a pack resolver coordinate. For GitHub-hosted packs, a browser-dereferenceable tree/<ref>/<path> URL is the preferred authored form. For other Git-backed sources, the Git remote and any pack-root subdirectory selector are part of the same source string. Public import TOML must not use fields named path, ref, commit, or hash. Registry handles such as main:gascity are command-time lookup handles and must not be persisted as source. City-pack imports use the same table shape at the top level of the root pack.toml:
Compatibility: The current loader also accepts top-level [imports.<binding>] tables in city.toml. When a city has both city.toml and pack.toml, new imports should be written in pack.toml; city.toml is the deployment layer.
Default rig imports are a city deployment concern. The canonical surface is city.toml [defaults.rig.imports.<binding>]. pack.toml must not define [defaults.rig.imports]. Rig imports are written under the [[rigs]] table they apply to:
Compatibility: The removed rigs.includes field is not a pack import surface for schema-2 authored config. A loader must hard-fail if a schema-2 city or fragment uses rigs.includes.

1.2.4. Agent Directories

Pack agents are authored as immediate child directories under agents/. Each agents/<name>/ directory defines one local agent template. The directory name is the agent name.
agent.toml is optional. A minimal agent directory may contain only a prompt file and inherit every other setting from the surrounding city or pack composition. The loader ignores entries under agents/ that are not directories. Directory names beginning with . or _ are ignored and do not define agents. The agent directory name must be a valid session identifier: it starts with an ASCII letter or digit and continues with ASCII letters, digits, hyphens, or underscores. Slashes, dots, and spaces are not valid agent name characters. If agent.toml contains a name field, the loader ignores it for identity and uses the directory name. Authors must not rely on name inside agent.toml to rename an agent. Prompt file discovery inside an agent directory uses this order:
  1. prompt.template.md
  2. prompt.md.tmpl
  3. prompt.md
New agents should use prompt.template.md. The prompt.md.tmpl name remains recognized for transitional compatibility. The scope field controls where a pack-defined agent is instantiated: The following table is a curated pack authoring reference for agent.toml. It is not an exhaustive decoder inventory: the generated schema at docs/reference/schema/pack-schema.json is the generated runtime schema inventory. The normative authoring rules are specified here.
Compatibility: The current runtime still parses agent-level skills and mcp arrays as compatibility tombstones, but active materialization ignores them. New packs should use the pack-level skills/ and mcp/ directories.
Compatibility: The fallback-agent mechanism was removed. A stale fallback key in a v2 agents/<name>/agent.toml is ignored; in a v1 inline [[agent]] table it fails the unknown-field gate.
Compatibility: Legacy inline pack.toml [[agent]] tables remain loader compatibility for existing packs. New packs must use agents/<name>/agent.toml; existing inline definitions should migrate by moving each table’s fields into the matching agent directory and moving prompt content beside the agent as prompt.template.md when the prompt is templated. During an incremental same-pack migration, an inline [[agent]] with the same name takes precedence and the matching directory agent is ignored. A v1 inline agent and a v2 directory agent with the same name across composition layers is a migration error rather than a precedence rule.

1.2.5. [[named_session]]

Each [[named_session]] table declares a canonical session backed by an agent template.

1.2.6. [[service]]

Each [[service]] table declares a workspace-owned HTTP service. Services may appear in city-level packs. Rig-level pack loading must fail if a rig-imported pack declares any service. Packs must not set publish_mode = "direct".

1.2.7. [providers.<name>]

The [providers] table defines provider presets. Pack providers are merged additively into the city. Included providers load first. Parent pack providers win over included pack providers with the same name inside a pack load. When multiple city or rig packs contribute providers, the first provider already in the effective city wins. Provider fields are the same provider fields accepted in city.toml.

1.2.8. [runtimes.<name>]

The [runtimes] table declares pack-shipped runtime provider executables. Each [runtimes.<name>] entry binds a selection name to a Runtime Provider Protocol (RPP) executable:
<name> must not contain :, /, or whitespace, since : is reserved for prefix-form selection names (exec:...) and / and whitespace keep selection names shell- and TOML-friendly. prompt_delivery controls what happens when a rendered startup prompt is too large for an OS exec() argv (see the argv-safety guard in cmd/gc/prompt_delivery.go). A runtime gc does not already classify as argv-safe or nudge-capable hard-fails on an oversized prompt by default — unset is that default. Declaring prompt_delivery = "nudge-fallback" asserts the executable has a working post-start Nudge path (the same RPP Nudge operation builtin nudge-fallback runtimes like tmux use) that an oversized prompt can reroute through instead of argv. Like command, this is a pack-composition-time assertion: gc accepts the declaration as written and does not verify it against the runtime executable, at compose time or via the pack-runtimes doctor check below. Pack authors can self-verify ahead of production: gc runtime check <name> resolves the declaration and smoke-tests it by invoking the runtime’s nudge operation against a throwaway conformance session, failing the run (required: nudge) when nudge is absent or broken. That smoke-tests the operation, not that an oversized prompt reroutes end to end — a pack that never runs it and declares nudge-fallback falsely still will not fail until an oversized prompt actually reaches that runtime at session start. City composition registers declared runtimes into a city-wide selection registry (City.Runtimes), including runtimes declared by rig-imported packs. Selection is city-wide: set city.toml [session].provider to the declared name (provider = "cloudflare" for the entry above), or override it for one invocation with GC_SESSION. An agent’s session field does not select a runtime; it names a session transport (acp, tmux, or omitted). Name collisions with builtin runtimes or other packs are composition errors. The only re-declaration that dedupes is the same pack directory reached twice through a diamond import graph with the same command and protocol; two distinct pack directories collide even when their pack name, command, and protocol all coincide, so a runtime’s provenance is never silently re-attributed. gc runtime check <name> and gc runtime conformance <name> both accept a declared name and run against that pack’s executable: check is the smoke test (handshake, lifecycle round-trip, declared capabilities, optional probes), and conformance is the full requirement-coded RPP suite. A pack-runtimes doctor check verifies each declared executable is installed and that its RPP handshake works. An executable with no protocol op is the documented version-0 floor and passes; a present-but-broken handshake fails.

1.2.9. Formula Directory

A pack’s formula directory is the well-known formulas/ directory at the pack root. The only formula directory the pack loader collects is formulas/. Formula directories are collected as loader layers. Lower-priority pack directories are collected before higher-priority pack directories.
Compatibility: Older material may mention [formulas].dir. That field is invalid in pack.toml; new packs should put formulas under formulas/.

1.2.10. [[patches.agent]]

Agent patches modify an existing agent by identity.
Patch operation fields mirror agent fields. A scalar pointer field replaces the target value. A list replacement field replaces the target list. A field whose name ends in _append appends to the corresponding target list. The specified append fields are: Pack-level patch paths in prompt_template, session_setup_script, and overlay_dir resolve relative to the patching pack directory.

1.2.11. Doctor Directory

Pack doctor checks are authored under doctor/. Each immediate child directory under doctor/ defines one check when it contains a runnable check script.
The directory name is the default check name. run.sh is the default check script. fix.sh, when present, opts the check into gc doctor --fix. help.md is optional help text. An optional doctor.toml may override the default script names or provide metadata:
Compatibility: Legacy pack.toml [[doctor]] entries remain loader compatibility for existing packs. New packs should use doctor/<name>/.

1.2.12. Command Directory

Pack commands are authored under commands/. Each directory containing run.sh defines one command leaf. Nested directories imply nested command words.
The directory path is the default command word list. run.sh is the default entrypoint. help.md is optional help text. An optional command.toml may override the default command words or script: A command runs with the invoking environment plus these variables:
Compatibility: Legacy pack.toml [[commands]] entries remain loader compatibility for existing packs. New packs should use commands/<path>/.

1.2.13. [global]

The [global] table declares pack-wide live session commands.
When [global].session_live is loaded, {{.ConfigDir}} is resolved to the concrete pack directory. Other template variables remain for per-agent expansion.

1.2.14. [[pricing]]

Each [[pricing]] table declares an estimated model-pricing entry for one provider/model pair.
Pack pricing entries are lower priority than city-level [[pricing]] entries and higher priority than the built-in default pricing table. Pricing entries are estimates for decision support, not invoice reconciliation.

1.2.15. Authoring Summary

New packs should use these authoring constructs:
Compatibility And Unsupported Constructs: Existing packs may still contain old authoring forms, and the loader rejects several city-only or invalid tables from pack.toml.

1.3. Per-Directory Breakdown

1.3.1. assets/

assets/ is the preferred home for private pack implementation files. The loader does not scan assets/ directly. Files under assets/ become relevant only when a pack definition references them, for example from agents/reviewer/agent.toml:
Authors should put new private scripts, prompt fragments, overlays, and other implementation files under assets/ unless an established loader convention requires a well-known directory. Formula steps reference prompt assets with description_file = "../assets/<relative-path>"; section 1.3.2 specifies how those references resolve across formula layers.

1.3.2. formulas/

formulas/ is the only pack formula directory. If it exists, the loader collects it as a formula layer. A formula step may reference a pack prompt asset with description_file = "../assets/<relative-path>". The loader resolves that form through the formula layer order of section 2.9: each layer’s sibling assets/<relative-path> is a candidate, and the highest-priority existing file wins. A higher layer can therefore shadow a pack’s prompt asset while the formula itself remains inherited from the lower-priority pack. Other relative description_file paths resolve against the directory of the formula file that declares them. See the Formula Specification for formula file naming and layer resolution.

1.3.3. overlay/

overlay/ is collected automatically from loaded pack directories. City-level pack overlays are available to city agents and form the base overlay layer for rig agents. Rig-level pack overlays are collected per rig. Agent overlay_dir is different: it is an explicit path on an agent definition or patch and resolves relative to the declaring pack. Use overlay/ only for pack-level overlay files that should be collected as a pack overlay layer. Use assets/ for private overlay trees referenced by an agent’s overlay_dir.

1.3.4. Command And Doctor Directories

commands/ and doctor/ are scanned by convention. Command entrypoint scripts belong inside the command directory. Doctor check scripts belong inside the doctor check directory. Private helper scripts that are not themselves command or doctor entrypoints should live under assets/.

1.3.5. Conventional Directories

orders/, skills/, mcp/, and template-fragments/ are scanned by their owning subsystems. assets/ is not scanned directly. Files under assets/ become effective only when a pack definition references them.

2. Loader

Loading a pack is the process of turning one or more pack directories into a flat effective city configuration. The loader has these major phases:

2.1. Pack Resolution

A pack reference is resolved to a pack root directory before pack.toml is read. For pack imports, the reference is the source field of a PackImport. Import bindings are sorted lexicographically before their sources are loaded. This gives deterministic load order for TOML maps. City-pack imports are read from top-level [imports.<binding>] tables in the root pack.toml. Rig imports are read from [rigs.imports.<binding>] under the corresponding [[rigs]] entry. Pack-to-pack imports are read from top-level [imports.<binding>] in pack.toml.
Compatibility: The loader also accepts top-level [imports.<binding>] tables in city.toml; when a root pack.toml exists, that path warns authors to move imports to pack.toml.
Compatibility: Legacy includes lists in pack.toml may also feed the pack loader. New packs should use [imports.<binding>]. Schema-2 root city.toml files and schema-2 config fragments reject legacy pack surfaces such as workspace.includes, workspace.default_rig_includes, [packs.*], rigs.includes, and inline agent definitions.
If a pack import has an empty binding name or empty source, loading must fail.

2.2. Versioning

PackImport.version is a compatibility constraint for versioned sources. The exact resolved version belongs to the pack lockfile, not to pack.toml. The loader specified here consumes resolved sources. Registry lookup, remote version selection, cache population, and lockfile update occur before or around loading. They must produce a concrete pack root directory whose pack.toml can be loaded by this specification. The gc pack command surface is registry discovery, local registry configuration, authentication, and publish submission: gc pack registry add, list, remove, refresh, search, show, login, whoami, and publish. Registry handles are not durable dependency coordinates. The shipped registry commands may accept a handle such as main:gascity while searching or inspecting registry records; persisted pack TOML must store the resolved durable source and optional version constraint instead. Installing, checking, or repairing the authored import graph is owned by gc import install and gc import check.

2.3. Recursive Pack Loading

The recursive pack loader operates on a pack root directory and a loading context:
The seen set is a recursion-stack set. A pack directory already present in the active recursion stack must cause loading to fail with a cycle error. A diamond-shaped dependency graph is valid; the loader may reuse a cached result when the same pack directory is reached through more than one acyclic path. Included or imported pack definitions are lower-priority base definitions. The parent pack’s own definitions are appended after included definitions and are therefore the later layer for provider merging.

2.4. Scope Filtering And Stamping

When loading a pack for the city-level surface:
  1. The loader keeps agents and named sessions whose scope is omitted or city.
  2. The loader drops agents and named sessions whose scope is rig.
  3. The effective identity prefix for kept agents is empty unless the definition explicitly supplied dir.
When loading a pack for a rig-level surface:
  1. The loader keeps agents and named sessions whose scope is omitted or rig.
  2. The loader drops agents and named sessions whose scope is city.
  3. For kept agents and named sessions whose dir is empty, the loader sets dir to the consuming rig’s name.
Agents contributed by an [imports.<binding>] table are stamped with that binding at both the city level and the rig level. The consuming surface’s own binding wins: all agents from an import are stamped with that import’s binding, overriding any nested binding from transitive imports. Agents defined by the city pack itself and agents from unbound legacy includes carry no binding. The runtime qualified name of an agent is:
The dir field is an identity prefix, not a filesystem path.

2.5. Naming And Collisions

Agent names are local names inside the defining pack. Import bindings qualify runtime agent names: an agent named mayor imported under binding gastown has the runtime name gastown.mayor, and hello-world/gastown.mayor when stamped for rig hello-world. Imported agents must be addressed by their binding-qualified name, not their bare local name. There is no fallback-agent mechanism. If two or more agents from different source directories share a qualified name on the same surface, loading fails; packs must own their agents under unambiguous names. Agents with the same local name under different bindings do not collide: gastown.mayor and review.mayor coexist on the city-level surface. The “same surface” means the city-level surface or one rig-level surface. Two different rigs may each have an agent with the same local name because their qualified names differ by rig dir. Agents without a binding — agents defined by the city pack itself and agents from unbound legacy includes — keep their bare local names, and duplicate local names among them fail loading.

2.6. Patches

Patches are targeted modifications to existing definitions. A patch must not create a new agent. Pack-level patches run inside recursive pack loading after imported/base agents and the current pack’s own agents have been merged. Pack-level patches may only use [[patches.agent]]. In pack-level patches, dir = "" matches by local name only, because a reusable pack normally does not know which rig will consume it. City-level patches run after city-level pack expansion and before rig-level pack expansion. A city-level patch targets agents that already exist in the effective city config at that point. Rig overrides run after all packs for that rig have expanded and after their agents have been stamped with the rig name. The patch/default order is:
  1. Recursive pack imports load.
  2. Pack-level patches apply inside each pack load.
  3. City-level packs expand.
  4. City-level patches apply.
  5. Rig-level packs expand.
  6. Rig overrides apply.
  7. Pack globals apply.
  8. Implicit agents are injected.
  9. Root city [agent_defaults] applies.
If a patch target does not exist when the patch runs, loading fails.

2.7. Defaults

[agent_defaults] is valid in both root city.toml and pack.toml. In pack.toml, it supplies pack-scoped defaults for agents loaded from that pack. In root city.toml, it supplies city defaults after pack expansion and implicit agent injection. The current default application step actively applies agent_defaults.provider to agents whose provider is still unset and agent_defaults.default_sling_formula to agents whose default_sling_formula is still unset. agent_defaults.provider also counts as a configured provider for implicit provider-agent injection. The default application step skips the control-dispatcher infrastructure agent. For unbound city includes, a root city agent_defaults.provider overrides a pack-scoped provider default. For bound [imports.<binding>] and [rigs.imports.<binding>], a pack-scoped provider default is preserved for the imported agents, matching the imported-pack inheritance behavior of default_sling_formula and append_fragments. An explicit per-agent provider always wins over either default. The current runtime also applies shared attachment defaults for append_fragments. Attachment-list fields such as skills and mcp are parsed as compatibility tombstones but ignored by active materialization. Other fields in the AgentDefaults structure may be parsed and composed by the config loader, but they are not specified here as active inherited defaults. Defaults run after pack expansion, patches, rig overrides, pack globals, and implicit agent injection. Defaults fill blank fields only; they do not override fields explicitly set by a pack, patch, rig override, or implicit agent seed.

2.8. Path Resolution

The following fields on pack agents resolve relative to the declaring pack directory: Convention-discovered prompt, overlay, namepool, skill, and MCP paths inside agents/<name>/ are resolved by discovery to concrete pack-local paths. Explicit relative prompt_template, session_setup_script, and overlay_dir values in agent.toml remain pack-relative through the agent’s source directory. The same fields in pack-level patches resolve relative to the patching pack directory. [global].session_live commands replace {{.ConfigDir}} with the concrete pack directory when the pack is loaded. Other template variables remain unresolved until runtime. Pack command and doctor script paths are declared relative to the pack directory.

2.9. Formula And Overlay Layers

City formula layers are ordered from lower priority to higher priority:
  1. Formula directories from city-level packs.
  2. The city-local formula directory.
Rig formula layers are ordered from lower priority to higher priority:
  1. City formula layers.
  2. Formula directories from packs imported by that rig.
  3. The rig-local formula directory, when configured.
When more than one layer contains a formula with the same name, the highest-priority layer wins (last wins); within a single layer, canonical <name>.toml beats deprecated <name>.formula.toml. Per-name winners are staged as symlinks into the scope’s .beads/formulas/ directory. See the Formula Specification for the resolution and staging contract. Overlay directories follow the same city-base then rig-specific collection model. Agent-specific overlay_dir is applied separately by the runtime.

2.10. Pack Globals

City-level pack globals apply to all agents. Rig-level pack globals apply only to agents in the corresponding rig. Pack globals append live session commands. They do not replace agent session_live entries.

2.11. Requirements

Pack requirements are checked after pack expansion. A city requirement must be satisfied by an agent with matching local name on the city-level surface. A rig requirement must be satisfied by an agent with matching local name during rig pack loading. If a requirement is not satisfied, loading fails.

2.12. Error Handling

The loader must fail when:
  1. pack.toml cannot be read.
  2. pack.toml cannot be parsed as TOML.
  3. [pack].name is empty.
  4. [pack].schema is missing, zero, or greater than the supported schema.
  5. A pack import has an empty binding or empty source.
  6. A pack.toml key is unknown or unsupported by the pack authoring surface.
  7. A [imports.<binding>] key is unknown or unsupported by the pack import shape.
  8. A pack dependency cycle is detected.
  9. A pack-level or city-level patch targets a missing agent.
  10. A rig-level pack declares a service.
  11. A pack service sets publish_mode = "direct".
  12. Two or more agents from different source directories share a qualified name on the same surface.
  13. A declared pack requirement is not satisfied.
  14. A schema-2 city.toml or included fragment uses a removed PackV1 surface such as rigs.includes, [packs.*], workspace.includes, workspace.default_rig_includes, or inline agent definitions. Exception: legacy builtin system-pack includes (.gc/system/packs/<name> entries written by older gc init versions) are tolerated during migration; the builtin-pack-imports doctor check converts them to pinned [imports] entries.
The loader may skip missing remote pack subpaths in compatibility cases where a remote source was fetched but the referenced pack directory no longer exists. That compatibility behavior must not be used to justify new invalid pack configuration.

3. Non-Normative Notes

This specification separates file and directory structure from loading and linking semantics: section 1 specifies the file and directory formats, and section 2 specifies the loader.
Last modified on October 7, 2026