Skip to main content
This guide is the practical migration companion for moving from the 0.14.0 PackV1 world into the PackV2 model that first landed in 0.14.1 and is being finished in the 0.15.0 wave. PackV2 was an initiative to address multiple problems in the way we write down how a city or a package works. There was a lot of entanglement between:
  • The definition of a pack or a city that can be versioned, shared, and used in many contexts.
  • The deployment configuration of how things project directories specific to your machine get rigged to a city.
  • The runtime information that Gas City needs to manage opaquely to users.
In 0.14.0 and earlier, a city was kind of a pack, but kind of not. PackV2 clears that up. Starting in 0.14.1, Gas City supports the PackV2 model: a city is defined just like a pack, but with an additional city.toml. The migration has two steps:
  1. Move portable definitions (e.g., agents, formulas) into pack.toml and the various pack-owned directories (e.g., agents, formulas)
  2. leave only deployment information (e.g., rigs) in city.toml
There is a third layer, .gc/, but that is site binding and runtime state. It matters to the model, but it is mostly not user migration work, so this guide keeps the focus on pack.toml, city.toml, and the pack directory tree. The target public migration flow is gc doctor, then gc doctor --fix for the safe mechanical rewrites, then gc doctor again to confirm the result. Some old cities may hard-break until migrated; that is intentional in this wave.
Current rollout note: The doctor-first remediation slice lands separately from the Skills/MCP, infix, and rig-path slices. Until that remediation work is present on your branch, gc import migrate may still exist as a transitional command surface even though the target model is gc doctor followed by gc doctor --fix.
Scope note: This guide describes the target PackV2 migration shape. Some sections below point at surfaces that are only in the first slice of the current rollout. When that is true, the guide calls it out inline and links the tracking issue. For release-gated behavior, also consult docs/packv2/skew-analysis.md and docs/packv2/doc-conformance-matrix.md. First-slice note: skills and MCP are current-city-pack only in this wave. Imported-pack catalogs and provider projection are later slices. The dedicated .gc/site.toml path split is still tracked in #588.

Before you start

The important mental shift is:
  • Gas City 0.14.0 centers city.toml and a lot of explicit path wiring
  • Gas City 0.14.1 and later centers pack.toml, named imports, and convention-based directories
The clean target shape is:
  • pack.toml
    • portable definition, imports, and pack-wide policy
  • city.toml
    • deployment decisions for this city
  • pack-owned directories
    • agents, formulas, orders, commands, doctor checks, overlays, skills, MCP, template fragments, assets

First: split city.toml and pack.toml

This is the most important migration step. Everything else hangs off it. In the new model, a city is a deployed pack. That means the root city directory has its own pack.toml, and the old “everything lives in city.toml” model gets broken apart.

What belongs in pack.toml

pack.toml is now the home for portable definition:
  • pack identity and compatibility metadata
  • imports
  • providers
  • pack-wide agent defaults
  • named sessions
  • pack-level patches
  • other pack-wide declarative policy
It should not be a registry of every file in the pack. If convention can find something, prefer convention.

What belongs in city.toml

city.toml is now the home for deployment:
  • rigs
  • rig-specific composition and patches
  • substrate choices
  • API/daemon/runtime behavior
  • capacity and scheduling policy
It should no longer be the place where the pack’s portable definition lives.

First concrete step: move includes to imports

For most existing cities, the first change you will actually make is composition. In Gas City 0.14.0, composition is include-based. In the PackV2 rollout, composition is import-based.

Old city-level include

# city.toml
[workspace]
name = "my-city"
includes = ["packs/gastown"]

New root pack import

# pack.toml
[pack]
name = "my-city"

[imports.gastown]
source = "../shared/gastown"
The key change is that the import gets a local name, here gastown. That local name is what the rest of the pack uses when it needs to refer to imported content.

Old rig-level include

# city.toml
[[rigs]]
name = "api-server"
path = "/srv/api"
includes = ["../shared/gastown"]

New rig-level import

# city.toml
[[rigs]]
name = "api-server"

[rigs.imports.gastown]
source = "../shared/gastown"
Use the city pack’s pack.toml for city-wide imports. Use rig-scoped imports in city.toml when a pack should compose only into one rig. Rigs are the main thing that remain in city.toml. As you migrate, the usual pattern is:
  • move portable definition into pack.toml and pack-owned directories
  • leave rigs and other deployment choices in city.toml

Then: migrate area by area

Once the root split is in place, the rest of the work gets much more mechanical.

Agents

Agents move out of inline TOML inventories and into agent directories.

Old shape

[[agent]]
name = "mayor"
prompt_template = "prompts/mayor.md"
overlay_dir = "overlays/default"

New shape

agents/
└── mayor/
    ├── prompt.template.md
    └── agent.toml
Use agent.toml only when the agent needs overrides beyond shared defaults.

Migration notes

  • move each [[agent]] definition into agents/<name>/
  • move templated prompt content to agents/<name>/prompt.template.md
  • move agent-local overlay content to agents/<name>/overlay/
  • keep shared defaults in [agent_defaults] (in pack.toml for pack-wide, city.toml for city-level overrides)
  • keep pack-wide providers in [providers.*]
If you are migrating a city, city-local agents are still just agents in the root city pack.

Formulas

Formulas mostly already fit the new direction.

Preferred shape

formulas/
└── build-review.toml

Migration notes

  • keep formulas in top-level formulas/
  • stop treating formula location as configurable path wiring
  • move nested orders out of formula space

Orders

Orders are being refactored to look more like formulas. The current direction, also captured in the consistency audit, is:
  • move orders out of formulas/orders/
  • standardize on top-level orders/
  • use flat files orders/<name>.toml

Old shape

formulas/
└── orders/
    └── nightly-sync/
        └── order.toml

New shape

orders/
└── nightly-sync.toml
This gives a consistent pair:
  • formulas/<name>.toml
  • orders/<name>.toml

Commands

Commands are moving toward convention-first entry directories.

Simple case

commands/
└── status/
    └── run.sh
This is enough for a default single-word command.

Richer case

commands/
└── repo-sync/
    ├── command.toml
    ├── run.sh
    └── help.md
Use command.toml only when the default mapping is not enough, for example:
  • multi-word command placement
  • extension-root placement
  • richer metadata
  • non-default entrypoint

Migration notes

Old:
[[commands]]
name = "status"
description = "Show status"
script = "commands/status.sh"
New simple case:
commands/status/run.sh
New richer case:
commands/repo-sync/
├── command.toml
├── run.sh
└── help.md
The default commands/<name>/run.sh discovery path is part of the current release surface. command.toml remains optional for metadata or explicit overrides. The remaining command manifest symmetry work is tracked in #668.

Doctor checks

Doctor checks are moving in parallel with commands.

Simple case

doctor/
└── binaries/
    └── run.sh

Richer case

doctor/
└── git-clean/
    ├── doctor.toml
    ├── run.sh
    └── help.md
The migration rule is the same as commands:
  • keep the entrypoint local to the check that uses it
  • use local TOML only when the default mapping is not enough
The default doctor/<name>/run.sh discovery path is part of the current release surface. doctor.toml remains optional for metadata or explicit overrides. The remaining command/doctor manifest symmetry work is tracked in #668.

Overlays

Overlays move away from being a global path bucket and toward a clearer split between pack-wide and agent-local content. Use:
  • overlays/ for pack-wide overlay material
  • agents/<name>/overlay/ for agent-local overlay material
If your old config depends on overlay_dir = "...", the migration step is usually to relocate those files into one of those places.

Skills, MCP, and template fragments

These mostly follow the new directory structure directly. Use:
  • skills/ for the current city pack’s shared skills
  • mcp/ for the current city pack’s shared MCP assets
  • template-fragments/ for pack-wide prompt fragments
and:
  • agents/<name>/skills/
  • agents/<name>/mcp/
  • agents/<name>/template-fragments/
when the asset belongs to one specific agent.

Skill materialization (new in v0.15.1)

As of Gas City 0.15.1, skills are no longer list-only. Every supported-provider agent (claude, codex, gemini, opencode) sees every city-pack skill and every bootstrap implicit-import pack skill (e.g. core) materialized as symlinks into its provider-specific sink before session spawn. No attachment filtering — an agent does not declare which skills it wants; it gets the whole catalog plus its own agents/<name>/skills/ directory on top. Sink paths land at the agent’s scope root (city-scoped) or rig path (rig-scoped):
  • Claude agents: <scope-root>/.claude/skills/<name>
  • Codex agents: <scope-root>/.codex/skills/<name>
  • Gemini agents: <scope-root>/.gemini/skills/<name>
  • OpenCode agents: <scope-root>/.opencode/skills/<name>
Mixed-provider cities produce sibling sink directories at the same scope root. copilot, cursor, pi, and omp agents have no sink in v0.15.1 and get no materialization. Precedence on name collision:
  1. Agent-local (agents/<name>/skills/<foo>) wins over shared.
  2. City pack (skills/<foo>) wins over bootstrap implicit imports.
  3. Two agents at the same (scope-root, vendor) cannot both provide the same agent-local name — gc start fails with a skill-collision error; fix by renaming one.
Lifecycle: adds, edits, renames, and removals all drain the affected agents via content-hash fingerprints. Every supervisor tick runs a cleanup + re-materialise pass, so in-place skill edits take effect without a full restart cycle. User-placed content at sink paths (a regular file or directory you put there yourself) is preserved — cleanup only removes symlinks whose targets live under known gc-managed catalog roots.

Removed in v0.15.1 — attachment-list tombstones

The v0.15.0 attachment-list fields — skills, mcp, skills_append, mcp_append, and the runtime-only shared_skills — are deprecated tombstones in v0.15.1. They still parse so upgrading cities don’t break, but they are ignored by the materializer (every agent gets everything). A one-time warning fires on config load when any of these fields is present. Migrate by deleting them from your city.toml / pack.toml. Run gc doctor --fix to strip them automatically. The fields become a hard parse error in v0.16. MCP activation (projecting MCP definitions into the agent’s provider config) is tracked as a follow-up and lands on main after v0.15.1.

Fragment injection migration

The old three-layer prompt injection pipeline is replaced by explicit template inclusion.
Old mechanismNew model
global_fragments in workspace configGone — move content to template-fragments/ and use explicit {{ template "name" . }} in .template.md prompts
inject_fragments on agent configGone — same approach
inject_fragments_append on patchesGone — same approach
All .md files run through Go templatesOnly .template.md files run through Go templates
For migration convenience, append_fragments in agent.toml or [agent_defaults] auto-appends named fragments to .template.md prompts without editing each prompt file:
# pack.toml or city.toml
[agent_defaults]
append_fragments = ["operational-awareness", "command-glossary"]
Plain .md prompts are inert — no fragments attach, no template engine runs.
NYI in this wave: [agent_defaults].append_fragments is the proven migration bridge in the current release. Agent-local append_fragments is still tracked as a spec/runtime parity gap in #671.

Assets and paths

This is the positive rule that replaces a lot of 0.14.0 ad hoc path habits.

assets/ is the opaque home for your files

If a file is not part of a standard surface Gas City uses for discovery, it belongs in assets/. Examples:
  • helper scripts
  • static data files
  • fixtures and test data
  • imported pack payloads carried inside another pack

Path-valued fields

Any field that accepts a path may point to any file inside the same pack. That includes:
  • files under standard directories
  • files under assets/
  • relative paths that use ..
The hard constraint is:
  • after normalization, the path must still stay inside the pack root

Examples

run = "./run.sh"
help = "./help.md"
run = "../shared/run.sh"
source = "./assets/imports/maintenance"

Common migration gotchas

”I still have a lot in city.toml

That usually means definition and deployment are still mixed together. Ask:
  • is this portable definition?
  • is this deployment?
Then move it to:
  • pack.toml and pack-owned directories
  • city.toml
respectively.

”I used to rely on scripts/

Do not recreate scripts/ as a standard top-level convention just because 0.14.0 had it. Instead:
  • put entrypoint scripts next to the command or doctor entry that uses them
  • put general opaque helpers under assets/
For example, this old pattern:
scripts/
└── setup.sh
plus:
session_setup_script = "scripts/setup.sh"
becomes either:
commands/status/run.sh
or:
assets/scripts/setup.sh
depending on whether the script is entry-local or a general helper.

”Do I need TOML everywhere?”

No. Simple cases should work by convention:
  • agents/<name>/prompt.md
  • commands/<name>/run.sh
  • doctor/<name>/run.sh
Use TOML when you actually need:
  • defaults
  • overrides
  • metadata
  • explicit placement

Reference: Gas City 0.14.0 city.toml elements to PackV2

This is the exhaustive top-level lookup table for the old city.toml schema, plus the qualified rows that matter most during migration.
Current rollout note: Some rows below describe the target PackV2 destination rather than the exact state of every in-flight branch. In the current 15.0 wave, workspace.name still lives in city.toml. Phase A rig-binding work removes machine-local rigs.path from newly written city configs, but rigs.prefix and rigs.suspended remain in city.toml in this release.
0.14.0 elementWhat it didNew home or action
includeMerged extra config fragments into city.toml before loadRemove as part of migration. Move real composition to imports and move remaining config to pack.toml, city.toml, or discovered directories.
[workspace]Held city metadata and pack composition in one placeSplit across the root pack.toml, city.toml, and .gc/.
workspace.nameWorkspace identityTransitional in this wave. Keep it in city.toml for the current 0.15.0 migration slice. Fresh gc init keeps it aligned with pack.name; gc register keeps it aligned with the registered city name, using workspace.name when present and backfilling it from pack.name when absent. Full removal from city.toml still waits for the broader site-binding cutover; track #602.
workspace.includesCity-level pack compositionMove to [imports.*] in the root city pack.toml.
workspace.default_rig_includesDefault pack composition for newly added rigsMove to [defaults.rig.imports] in the root city pack.toml. This is the target shape, but loader-backed support is still tracked in #360.
[providers.*]Named provider presetsUsually move to [providers.*] in the root city pack.toml, unless the setting is truly deployment-only.
[packs.*]Named remote pack sources used by includesCollapse into [imports.*] entries. There should no longer be a separate [packs.*] registry in city.toml.
[[agent]]Inline agent definitionsMove to agents/<name>/, with optional agent.toml.
agent.prompt_templatePath to agent promptMove to agents/<name>/prompt.template.md for templated prompts. Use prompt.md only for plain, non-templated Markdown.
agent.overlay_dirPath to overlay contentMove content to agents/<name>/overlay/ or pack-wide overlays/.
agent.session_setup_scriptPath to setup scriptKeep as a path-valued field, but point at a pack-local file, usually next to the thing that uses it or under assets/.
agent.namepoolPath to names fileMove toward agent-local content such as agents/<name>/namepool.txt if retained.
[[named_session]]Named reusable sessionsMove to [[named_session]] in the root city pack.toml.
[[rigs]]Rig deployment entriesKeep in city.toml.
rigs.pathMachine-local project bindingWith the Phase A rig-binding slice, new writes stop persisting this in authored city.toml; older cities may still carry it until migrated.
rigs.prefixDerived rig prefixKeep in city.toml in the current release wave. It is deployment state, but not yet extracted into separate site-binding storage.
rigs.suspendedOperational toggleKeep in city.toml in the current release wave. It remains deployment/runtime state rather than portable pack definition.
rigs.includesRig-scoped pack compositionMove to rig-scoped imports in city.toml.
rigs.overridesRig-specific customization of imported agentsKeep as rig-level deployment customization in city.toml.
[patches]Post-merge modificationsMove pack-definition patches to pack.toml. Keep rig-specific patches with the rig in city.toml.
[beads]Bead store backend choiceKeep in city.toml.
[session]Session substrate configKeep in city.toml, except site-local bindings.
[mail]Mail substrate configKeep in city.toml.
[events]Events substrate configKeep in city.toml.
[dolt]Dolt connection defaultsKeep in city.toml.
[formulas]Formula directory configPrefer convention. Keep only if a remaining pack-wide formula policy survives; otherwise remove.
formulas.dirFormula directory pathReplace with the fixed top-level formulas/ convention.
[daemon]Controller daemon behaviorKeep in city.toml.
[orders]Order runtime policy such as skip lists and timeoutsKeep in city.toml.
[api]API server deployment configKeep in city.toml, except machine-local bind details.
[chat_sessions]Chat session runtime policyKeep in city.toml.
[session_sleep]Sleep policy defaultsKeep in city.toml.
[convergence]Convergence limitsKeep in city.toml.
[[service]]Workspace-owned service declarationsKeep in city.toml if they are deployment-owned services.
[agent_defaults]Defaults applied to agents in this cityLives in both pack.toml (pack-wide portable defaults) and city.toml (city-level deployment overrides). City layers on top of pack.

Reference: Gas City 0.14.0 pack.toml elements to PackV2

This is the lookup table for the old shareable-pack schema and the transitional pack fields that people are likely to have.
0.14.0 elementWhat it didNew home or action
[pack]Pack metadataKeep in pack.toml.
pack.namePack identityKeep in [pack].
pack.versionPack versionKeep in [pack].
pack.schemaPack schema versionKeep in [pack], updated to the new schema as needed.
pack.requires_gcMinimum supported gc versionKeep in [pack].
pack.city_agentsCity-vs-rig stamping hint in the old pack systemRevisit during migration. The new model prefers agent-local definition and scope rules instead of this field.
pack.includesPack-to-pack compositionReplace with [imports.*] in pack.toml.
pack.requiresPack requirementsKeep in [pack] if the requirement model survives unchanged; otherwise migrate to the current requirement shape in the design docs.
[imports.*]Named imports in transitional configsKeep in pack.toml. This is the new composition surface.
[[agent]]Inline pack agent definitionsMove to agents/<name>/, with optional agent.toml.
agent.prompt_templateAgent prompt file pathMove to agents/<name>/prompt.template.md for templated prompts. Use prompt.md only for plain, non-templated Markdown.
agent.overlay_dirAgent overlay pathMove content to agents/<name>/overlay/ or overlays/.
agent.session_setup_scriptAgent setup script pathKeep as a path-valued field pointing at a pack-local file.
[[named_session]]Pack-defined named sessionsKeep in pack.toml.
[[service]]Pack-defined servicesKeep only if services remain pack-defined in the new model. Otherwise move city-owned services to city.toml.
[providers.*]Provider presets used by the packKeep in pack.toml.
[formulas]Formula directory configPrefer convention. Remove directory wiring and use top-level formulas/.
formulas.dirFormula directory pathReplace with top-level formulas/.
[patches]Pack-level patching rulesKeep in pack.toml.
[[doctor]]Pack doctor inventoryMove toward doctor/<name>/run.sh by default, with optional doctor.toml when needed.
doctor.scriptPath to doctor entrypointKeep as a pack-local path, usually doctor/<name>/run.sh.
[[commands]]Pack command inventoryMove toward commands/<name>/run.sh by default, with optional command.toml when needed.
commands.scriptPath to command entrypointKeep as a pack-local path, usually commands/<name>/run.sh.
[global]Pack-wide session-live behaviorKeep in pack.toml if the pack-global surface survives as designed.

Reference: old top-level directories

This table is the filesystem companion to the two schema tables above.
Old directory or patternWhat it meant in 0.14.0New home or action
prompts/Shared bucket of prompt templates addressed by pathMove prompt content into agents/<name>/prompt.template.md for templated prompts. Use prompt.md only for plain, non-templated Markdown.
scripts/Shared bucket of helper and entrypoint scriptsDo not preserve as a standard top-level directory. Put entrypoint scripts next to what uses them, and put general helpers under assets/.
formulas/Formula directory, sometimes path-wired via TOMLKeep as the fixed top-level formulas/ convention.
formulas/orders/Nested order definitions under formulasMove to top-level orders/ using flat *.toml files.
orders/Top-level order directory in some citiesStandardize on this location, but use flat orders/<name>.toml files.
overlays/Pack-wide overlay bucketKeep as top-level overlays/.
overlay/Singular overlay directory seen in some older packsRemove or migrate to overlays/ or agents/<name>/overlay/.
namepools/Shared bucket of agent name poolsMove toward agent-local files if retained.
commands/ with ad hoc scriptsCommand helper directory plus TOML wiringKeep commands/, but organize as entry directories such as commands/<name>/run.sh.
doctor/ with ad hoc scriptsDoctor helper directory plus TOML wiringKeep doctor/, but organize as entry directories such as doctor/<name>/run.sh.
skills/Current city pack skills directory in newer layoutsKeep as top-level skills/.
mcp/Current city pack MCP directory in newer layoutsKeep as top-level mcp/.
template-fragments/Shared prompt-fragment directory in newer layoutsKeep as top-level template-fragments/.
packs/Local vendored packs or bootstrap importsDo not treat as a standard top-level directory. If you need opaque embedded packs, place them under assets/ and import them explicitly.
loose helper files at pack rootArbitrary files mixed into controlled surface areaKeep standard repo documents like README.md, LICENSE*, CONTRIBUTING.md, and CHANGELOG* at pack root. Move other opaque helpers under assets/.

Suggested migration order

For a real city or pack, the most practical order is:
  1. add a root pack.toml
  2. move workspace.includes and rigs.includes to imports
  3. move agent definitions into agents/
  4. move orders to top-level flat files
  5. move commands and doctor checks into commands/ and doctor/
  6. move opaque helpers into assets/
  7. clean up whatever remains in city.toml and pack.toml using the reference tables above
That gets the big structural changes done before you spend time on the smaller cleanup work.
Last modified on April 17, 2026