gc. The
pack specification is the source of truth for the
exact format.
The Pack Model
A pack is a directory with apack.toml file. Only pack.toml is required,
but most useful packs also include agents, prompt templates, formulas, orders,
commands, doctor checks, skills, MCP configuration, or support files.
Here is a small pack:
pack.toml file names the pack:
agents/reviewer/agent.toml:
reviewer. Because the
directory contains prompt.md, the loader discovers that prompt by convention.
If another city imports this pack, it does not need to copy prompt.md; the
file still belongs to the pack that declared it.
The City is itself a pack — the local (root) pack, rooted at the city directory
next to city.toml. It holds the city’s reusable definitions, imports, and
local pack metadata, and imports shared packs.
If the loader cannot understand a pack’s schema, it rejects the whole pack
rather than loading part of it.
Why Import A Pack?
Import a pack when you want to reuse behavior defined somewhere else without copying its files into your city. For example, a review pack might provide:- a
revieweragent - review formulas
- prompt fragments
- setup scripts
- doctor checks
Registries, Handles, And Sources
A registry is a local catalog of reusable packs. You use a registry handle to find a pack on your machine, but you commit a durable source to import it — so another machine resolves the pack from the committed source, not your registry name:
For a GitHub-hosted pack, use a browser-dereferenceable tree URL:
gc: its skills surface as gc.<skill>
(for example gc.mayor) and its commands as gc gc <command>. Cities created
by gc v1.4.x bound it as [imports.gascity]; gc doctor --fix renames that
key to gc (see the gascity-pack-binding check in
Troubleshooting).
City Imports And Rig Imports
A city-level import belongs to the city pack and appears at the top level of the city pack’spack.toml:
planner, the loader stamps it
with the import binding, and the runtime agent is named:
[[rigs]] table that needs it:
gc rig add ../checkout-service --include gc=https://github.com/gastownhall/gascity-packs/tree/main/gascity
writes a [rigs.imports.gc] entry for this source. The gc= prefix sets the
binding; without it, the binding is the source’s last path segment
(gascity). Because this pack ships with gc, gc rig add pins it to the
bundled sha: version rather than the ^0.1 constraint shown above. Any
other remote pack gets the constraint gc import add would write, plus a
packs.lock entry.
If that same pack defines a rig-scoped agent named planner, the runtime agent
is stamped with the rig name as well as the binding:
name becomes the identity prefix. The rig path is the filesystem
location of the project. These are different pieces of information.
Agent Scope
An agent in a pack can say where it is allowed to load.scope field has three useful states.
The scope says where the definition is available. It does not name a particular
rig. A rig-scoped agent becomes part of a rig only when that rig imports the
pack.
Names
Because the import binding qualifies every imported agent name (asgc.planner above), you address imported agents by that qualified name —
not bare planner — in patches, targets, and commands, and the imported pack’s
own name never overrides the binding.
Two imports that define agents with the same local name therefore coexist:
build.worker and review.worker do not collide. Config load fails only when
two source directories produce the same qualified name on the same surface — for
example, two unbound legacy includes that both define a city-level reviewer.
Defaults And Patches
Defaults fill in blanks after packs have loaded. Root city defaults belong incity.toml, and pack-scoped defaults can be declared in pack.toml for agents
loaded from that pack. Pack-scoped defaults follow the precedence rules in the
pack spec: explicit agent fields win, bound imports preserve inherited pack
defaults, and unbound legacy includes yield to root city defaults.
default_sling_formula is still
blank. If a pack explicitly sets the field on an agent, the explicit value
wins.
A patch changes an agent that already exists. It does not create a new agent.
Imported agents are targeted by their binding-qualified name:
dir to select the rig identity prefix:
dir is the rig name, not the rig path.
Loading Order
The loader applies packs, patches, and defaults in a deterministic order, which decides the winner when two layers set the same field:Choosing Where To Put A Change
When you customize a pack, choose the narrowest place that expresses what you mean.Pack CLI
Find A Pack
Search when you know the capability you want but not the pack name, then show a record to get paste-ready import commands:Source line
and the selected version — the registry handle stays out of the file.
Install Or Check Imports
After changing remote imports, install resolves the declared imports, writes or repairspacks.lock, and materializes the packs in the local cache:
gc import check is a read-only pass: it reports missing, stale, or uncached
import state and points back to gc import install for repair. Registry
commands are discovery only; they never sync the authored import graph.
Private Packs And Credentials
A pack whose source (or a transitive import) is a private repository needs a git credential. Credentials are never written intopack.toml, packs.lock, or
city.toml; they live in a separate, never-committed credentials.toml,
secret-by-pointer — the file records where to find the token, never the
token itself. Register one with gc import credential:
match argument is a bare host or host/path-prefix (longest-prefix wins,
so same-host different-org credentials coexist). Exactly one pointer flag is
required. By default the rule is written to <city>/.gc/credentials.toml
(0600); --global writes $GC_HOME/credentials.toml instead. gc refuses to
load a credentials.toml that is world-accessible or group-writable: the modes
it accepts are 0600/0400, plus the root-owned own-group 0440 that a Kubernetes
Secret volume mounted with fsGroup produces. List and remove registered rules
with:
$GC_GIT_CREDENTIALS_FILE (replaces the file layers when set) →
<city>/.gc/credentials.toml → $GC_HOME/credentials.toml →
$GC_GIT_CREDENTIAL_COMMAND. A credentials.toml looks like:
gc import add/install/upgrade (and
gc rig add, gc doctor --fix) authenticate the clone automatically; when no
rule matches, public imports behave exactly as before. If a private import
fails to authenticate, gc prints a hint: line pointing at
gc import credential add. Never embed user:token@ in a source URL — gc
rejects it because the credential would leak into city.toml, packs.lock, the
shared cache’s .git/config, and error output.
Version constraints and the lockfile
Version constraints and the lockfile
The The authored import expresses the source and optional constraint; the lockfile
records the exact resolved state. Once the cache and lockfile are current, city
loading uses the local resolved pack instead of re-fetching the remote source.
[pack].version field is pack metadata. Import version selection comes
from the importing file and the lockfile, not from comparing [pack].version
during load. An import can express the selected revision three ways:Registry State Is Local
Registry commands manage local discovery state. Pack imports manage shared city state.
This separation keeps local discovery flexible without making shared config
depend on the names or cache layout of one machine.
Registry Freshness
Registry catalogs are cached locally.gc pack registry search and
gc pack registry show read that cache unless you pass --refresh, and they
warn when a configured registry cache is older than the freshness window.
By default, a registry cache is considered fresh for 24 hours. Set
GC_REGISTRY_FRESHNESS to a positive Go duration string when you need a
different window:
--refresh when you want a command to
fetch the latest catalog before reading it:
pack.toml still stores durable import source and version values.