Reference for wiring
gc-toolkitinto a Gas City. Assumes a working Gas City install (gc versionreturns a version) and a city created withgc init.
gc-toolkit ships a native agent roster — polecat, refinery, witness,
deacon, converse, mechanik, polecat-codex, proactive — declared in its own
pack.toml. It imports nothing: there are no gastown prerequisites, no
transitive imports, and no agent patches to wire.
Covered here:
- Importing gc-toolkit
- The mechanik named session
- Sub-pack opt-in: gascity-keeper
- The helm board service
- Verification
For Gas City background, see gascity-reference.md.
Drop gc-toolkit somewhere reachable from the city root (the convention is
rigs/gc-toolkit/), then add the import to your city.toml:
[[rigs]]
name = "my-rig"
prefix = "mr"
[rigs.imports.gc-toolkit]
source = "rigs/gc-toolkit"source resolves relative to the city root.
[rigs.imports.gc-toolkit]
source = "github.com/<owner>/gc-toolkit"
version = "v0.1.0"version is required for git-backed imports; run gc import install to
materialize the pack under .gc/cache/.
# pack.toml (city root)
[defaults.rig]
[defaults.rig.imports.gc-toolkit]
source = "rigs/gc-toolkit"Any per-rig [rigs.imports.gc-toolkit] overrides the default for that rig.
- The roster — worker pools (
polecat, andpolecat-codexon the codex provider), patrols (refinery,witness,deacon), conversation role (converse), andproactive(always-on, 2-slot). - The lifecycle —
lifecycle/lifecycle.toml(states, transitions, metadata registry) and the single transition writerassets/scripts/lifecycle.sh. - Orders — the merge cadence (
refinery-reconcile, 60s, rig-scoped),deferred-dispatch,liveness-sweep,reconcile-rig-checkouts,boot-health,quota-park-nudge,helm-build, and the feedback miner/distiller. - Skills — surfaced via
gc skill list(gc-toolkit.handoff,gc-toolkit.session-title, …). - Doctor checks — the nine structural checks verified below.
gc-toolkit provides a mechanik named-session template (the city-scoped
structural engineer). Declare it once at the city level:
[[named_session]]
template = "mechanik"Then:
gc start
gc session attach mechanikpacks/gascity-keeper/ is a separate pack for the one rig that maintains a
gascity fork. Import it in addition to gc-toolkit, on that rig only:
[rigs.imports.gascity-keeper]
source = "rigs/gc-toolkit/packs/gascity-keeper"The complete wiring snippet — including the [[rigs.patches]]
fragment-injection blocks for refinery and polecat — lives in the sub-pack
itself: packs/gascity-keeper/pack.toml.
The sub-pack ships its own [[named_session]] (scope = "rig"), so the
keeper is spawnable without an extra block; it resolves to
<rig>/gascity-keeper.keeper (confirm with gc config show).
Sub-pack imports are rig-scoped: declare them inside a [[rigs]] block, never
at the city level, or every rig picks them up.
The board is a Go sidecar (services/helm), render-only, and optional —
everything works without it. [[service]] is forbidden in rig-imported
packs, so the stanza is city-level: add it to the city's city.toml (or
city-root pack.toml), with the command path relative to the city root:
[[service]]
name = "helm"
kind = "proxy_process"
[service.process]
command = ["bash", "rigs/gc-toolkit/assets/scripts/gc-helm-svc.sh"]
health_path = "/healthz"The launcher execs a prebuilt binary; the helm-build order keeps it built.
Write verbs (takeaway / open / react) stay in assets/scripts/gc-helm.sh;
rendering is helm-svc board --json. See
services/helm/README.md.
gc doctorThe pack's checks, and what a failure means:
| Check | Asserts (invariant) | First-failure cause |
|---|---|---|
check-state-space |
every merge_result/status combo is declared in lifecycle.toml, and a detached state rests unheld and offered to no pool (I2) |
a writer minted an undeclared state, or something routed a parked anchor back into pool demand |
check-routed-work-claimable |
every route and assignee names a live target; routed work is in bd ready or in bd blocked; rig-scoped orders bound (I3) |
a pool renamed, an order missing its rig registration, or routed work stranded outside both queues |
check-one-anchor-per-pr |
one open owning anchor per PR (I4) | duplicate anchors filed for one branch |
check-closed-implies-landed |
closed anchor ⇒ merged + merged_sha, or explicit terminal (I5) |
something closed a bead out-of-band |
check-gate-integrity |
gating anchors declare check_set; markers are a bare lane-state word (I6+I7) |
a hand-written or unmigrated marker |
check-step-terminal |
no offerable step under a closed root; no stalled frontier (I8) | a workflow died mid-molecule |
check-cadence-live |
every pack order fired within its interval (I10) | order not registered for a rig, or the controller is down |
check-config-bound |
prompts/overlays/fragments resolve in the composed config | a rename that missed a reference |
check-seed-audit-current |
generated/seed-audit/ matches its inputs (warn-only if absent) |
a prompt input moved without a re-render |
check-recycle-capable |
cycle-recycle can fire: a Stop event reaches the hook with its stdin intact, the hook's own --measure reads a transcript's context size, and no refinery defer guard is latched |
the Stop wiring stopped passing the hook its stdin, the transcript shape moved under the measurement, or an uncommitted tracked file has latched the refinery's git-op guard |
check-wisp-cascade-intact |
every bead store carries the four ON DELETE CASCADE foreign keys from the wisp auxiliary tables into wisps(id) |
a store whose schema migration recorded the constraints as applied without adding them, leaving it to accumulate auxiliary rows no wisp reaches |
gc doctor --verbose explains any failure; gc doctor --fix applies the
canonical remediation where one exists.
Each check holds one deadline for its whole run and gives every probe only the time left before it, so a slow or wedged data plane costs findings rather than the whole check: a probe that no longer fits is refused, the store behind it is reported as NOT checked, and the check says the budget ended the run. Read that as partial — an arm skipped for time is not an arm that passed.
That deadline is 60s, matching --check-timeout's default. The flag sizes
the doctor's own abandon timer and is not passed to the checks, so raising
it on a loaded host means exporting the same number of whole seconds as
GC_DOCTOR_CHECK_TIMEOUT too:
GC_DOCTOR_CHECK_TIMEOUT=120 gc doctor --check-timeout 120sgc config show | grep -E '^\[\[agent\]\]|^name ='Confirm the native roster is present — polecat, polecat-codex,
refinery, witness, deacon, dog, converse, mechanik — with no
gastown entries.
generated/seed-audit/ ships empty; render it once per clone, which also
wires the pre-commit hook that keeps it current:
assets/scripts/render-seed-audit.sh --install-hookUntil then check-seed-audit-current warns rather than errors.
The hook keeps a branch current against its own base, which is not the same as
keeping the landing branch current: the artifact is rendered from the whole
source tree, so a PR whose render predates a prompt input the base has since
gained lands over that input. merge.sh refuses such a merge, using
render-seed-audit.sh --check-merge over the tree git merge-tree writes, so
merges on this rig need git 2.38 or newer.
gc start
gc session new mechanik
gc session attach mechanikIf the mechanik session comes up with the gc-toolkit prompt header, the import composed correctly.
sourcepaths are city-root-relative, not rig-root-relative.- Rig names must differ in their first two letters — the bead prefix is
auto-derived, so set explicit
prefixvalues for similar names. pack.tomlvscity.toml. Pack-level config (defaults,[global]hooks) goes in the city rootpack.toml; per-rig wiring ([[rigs]],[rigs.imports.*],[[rigs.patches]],[[rigs.overrides]]) goes incity.toml.- Merged is not live until the checkout syncs.
reconcile-rig-checkoutsfast-forwards each rig checkout every 15 minutes; a just-merged pack change is not what the runtime executes until then (see refinery-merge-cadence.md, Adjacent order).