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 effectiveCity
configuration.
0.1. Pack
A pack is a directory containing apack.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:
- As the city pack. The city pack is the root pack: the city directory
containing
city.tomland the rootpack.toml. - As a city-level imported pack. Its city-scoped and unscoped definitions are loaded into the city-level surface.
- 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: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 underassets/ 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:pack.toml is required.
1.1. Directory Rules
The pack root must be a directory. The pack root must contain a file namedpack.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 inlinepack.toml[[agent]],[[doctor]], and[[commands]]entries for existing packs. New packs should useagents/,doctor/, andcommands/.
1.2.1. [pack]
The [pack] table defines pack metadata.
Compatibility: The current loader still accepts[pack].includesfor 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.
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-levelDefault rig imports are a city deployment concern. The canonical surface is[imports.<binding>]tables incity.toml. When a city has bothcity.tomlandpack.toml, new imports should be written inpack.toml;city.tomlis the deployment layer.
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 removedrigs.includesfield is not a pack import surface for schema-2 authored config. A loader must hard-fail if a schema-2 city or fragment usesrigs.includes.
1.2.4. Agent Directories
Pack agents are authored as immediate child directories underagents/.
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:
prompt.template.mdprompt.md.tmplprompt.md
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-levelskillsandmcparrays as compatibility tombstones, but active materialization ignores them. New packs should use the pack-levelskills/andmcp/directories.
Compatibility: The fallback-agent mechanism was removed. A stalefallbackkey in a v2agents/<name>/agent.tomlis ignored; in a v1 inline[[agent]]table it fails the unknown-field gate.
Compatibility: Legacy inlinepack.toml[[agent]]tables remain loader compatibility for existing packs. New packs must useagents/<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 asprompt.template.mdwhen 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-knownformulas/ 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 inpack.toml; new packs should put formulas underformulas/.
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 underdoctor/. Each immediate child directory
under doctor/ defines one check when it contains a runnable check script.
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: Legacypack.toml[[doctor]]entries remain loader compatibility for existing packs. New packs should usedoctor/<name>/.
1.2.12. Command Directory
Pack commands are authored undercommands/. Each directory containing run.sh
defines one command leaf. Nested directories imply nested command words.
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: Legacypack.toml[[commands]]entries remain loader compatibility for existing packs. New packs should usecommands/<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 frompack.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:
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 beforepack.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 incity.toml; when a rootpack.tomlexists, that path warns authors to move imports topack.toml.
Compatibility: LegacyIf a pack import has an empty binding name or emptyincludeslists inpack.tomlmay also feed the pack loader. New packs should use[imports.<binding>]. Schema-2 rootcity.tomlfiles and schema-2 config fragments reject legacy pack surfaces such asworkspace.includes,workspace.default_rig_includes,[packs.*],rigs.includes, and inline agent definitions.
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: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:- The loader keeps agents and named sessions whose
scopeis omitted orcity. - The loader drops agents and named sessions whose
scopeisrig. - The effective identity prefix for kept agents is empty unless the definition
explicitly supplied
dir.
- The loader keeps agents and named sessions whose
scopeis omitted orrig. - The loader drops agents and named sessions whose
scopeiscity. - For kept agents and named sessions whose
diris empty, the loader setsdirto the consuming rig’sname.
[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:
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 namedmayor 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:
- Recursive pack imports load.
- Pack-level patches apply inside each pack load.
- City-level packs expand.
- City-level patches apply.
- Rig-level packs expand.
- Rig overrides apply.
- Pack globals apply.
- Implicit agents are injected.
- Root city
[agent_defaults]applies.
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:- Formula directories from city-level packs.
- The city-local formula directory.
- City formula layers.
- Formula directories from packs imported by that rig.
- The rig-local formula directory, when configured.
<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 agentsession_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:pack.tomlcannot be read.pack.tomlcannot be parsed as TOML.[pack].nameis empty.[pack].schemais missing, zero, or greater than the supported schema.- A pack import has an empty binding or empty source.
- A
pack.tomlkey is unknown or unsupported by the pack authoring surface. - A
[imports.<binding>]key is unknown or unsupported by the pack import shape. - A pack dependency cycle is detected.
- A pack-level or city-level patch targets a missing agent.
- A rig-level pack declares a service.
- A pack service sets
publish_mode = "direct". - Two or more agents from different source directories share a qualified name on the same surface.
- A declared pack requirement is not satisfied.
- A schema-2
city.tomlor included fragment uses a removed PackV1 surface such asrigs.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 oldergc initversions) are tolerated during migration; thebuiltin-pack-importsdoctor check converts them to pinned[imports]entries.