| name | Architecture — how gc-toolkit composes Gas City to deliver its beliefs |
|---|---|
| description | The 30,000-ft guide to gc-toolkit — the boundary between operator, runtime, pack, and GitHub; how a piece of work moves from filed to landed; how humans engage; how lessons compound; and the consistency test a new capability must pass to belong. Read it to know what the system is, to place a new capability, or to check an existing one. |
gc-toolkit delivers the beliefs in foundation.md by composing Gas City — the multi-agent runtime it runs on — rather than by growing bespoke machinery beside it. The pack is its workflows: work, review, merge, visit, feedback, patrol. Every component belongs to one of them or is a declared shared primitive; nothing exists because an incident once happened.
Mandate. How gc-toolkit composes Gas City's primitives to deliver foundation: the system boundary, how work moves end to end, how humans engage, how lessons compound, and the test that keeps new work grounded.
Boundaries. It works at altitude. The anchor lifecycle's states, writers, and gate mechanics are owned by state-machine.md; the merge cadence's runtime semantics by refinery-merge-cadence.md; the primitive list and the invariant→check binding by component-model.md; the filing conventions by file-structure.md.
Four parties, one data plane. Every durable fact is a bead; the pack owns no storage of its own.
flowchart LR
OP([operator])
subgraph city["Gas City runtime — not this repo"]
LEDGER[(bead ledger<br/>Dolt)]
POOL[pools + routing]
ORD[order runner]
end
subgraph pack["gc-toolkit pack"]
FORM[formulas +<br/>agent prompts]
LIFE[lifecycle.sh<br/>the transition writer]
CAD[merge cadence]
SIGN[signoff.sh<br/>the verdict writer]
DOC[doctor/<br/>invariant checks]
HELM[helm board<br/>render-only]
end
subgraph git["GitHub"]
BR[branch]
PR[pull request]
end
OP -->|files subject / visit| LEDGER
OP -.->|reads| HELM
LEDGER --> POOL
POOL -->|claim| FORM
FORM -->|push| BR
FORM -->|via lifecycle.sh| LIFE
LIFE -->|one atomic bd update| LEDGER
SIGN -->|gate markers| LEDGER
ORD -->|every 60s| CAD
CAD -->|reads + writes via lifecycle.sh| LEDGER
CAD -->|opens / merges| PR
BR --> PR
HELM -.->|derives per render| LEDGER
DOC -.->|asserts| LEDGER
DOC -.->|asserts| FORM
Legend. Solid = writes state. Dashed = reads without writing. Three write
paths matter: lifecycle.sh is the only writer of lifecycle transitions,
signoff.sh is the only writer of gate verdicts, and the merge cadence is the
only thing that opens or merges a PR. Everything else reads.
One pipeline, from a filed bead to a landed change. Each step names its performer; the full transition table with writers is state-machine.md.
- File. Something — a formula step, an event, the operator — files a bead.
- Route.
gc sling(ordeferred-dispatch.shwhen blockers must close first) routes it to a pool. Routed, unclaimed work is demand: the runtime spawns a session to meet it, with no operator keystroke. - Claim. A polecat claims via
gc hook --claim; the runtime writes assignee and claim in one step. - Implement. The polecat works in a per-bead worktree on a
polecat/<bead>branch, driven bymol-polecat-work. - Hand off. Push, verify the push landed, then one atomic
gc bd updatecarries the handoff — target, refinery assignee, cleared route (branch was stamped at workspace-setup). All-or-nothing: a dead session mid-handoff leaves either the pre-handoff state (witness orphan recovery re-routes it) or the complete post-handoff state, never a half. The anchor is still unanchored here; its first lifecycle transition is the refinery's. - Gate. The merge cadence's gate-ensure arm makes every declared gate
raisable; a review bead is routed to the polecat-codex pool; the reviewer's
single call to
signoff.shwrites the verdict marker or files one rework child. - PR. With every declared gate green at the live head,
pr-open.shopens (or adopts) the pull request. - Merge.
merge.shvalidates the full authorization set, merges pinned to the validated commit, then closes the anchor and recordsmerged_shain onelifecycle.shcall. - Close propagates.
closedmeans landed and verified — the one signal waiting parties key on (lifecycle-composition.md).
External facts the pack does not write — GitHub closing or retargeting a PR, a
session dying, a provider quota park — are handled by five reactive paths:
pr-facts.sh (an arm of the cadence) records PR events and files a visit, the
witness patrol recovers work orphaned by dead sessions, the dog pool is
warrant executor for due-process recovery of wedged sessions (demand-scaled
0-2), quota-park-nudge.sh nudges a session parked behind a provider limit,
and boot-health.sh detects a wedged deacon (report-only by design). See
authority-map.md. There is no healer category: writers
complete their own transitions, so nothing reconstructs pack-written state
after the fact.
The human surface is subject / visit / takeaway on native primitives (gascity-human-engagement.md is the reference).
- A subject bead is the conversation. Its id is the conversation's identity; turns are small child beads routed to the converse role. Warm, the next turn vacuums onto the live session through the continuation group; cold, a fresh session reconstitutes from the record. The record is the durable thing; sessions are disposable.
- A visit is a filed turn —
mol-visitandgc-visit-open.share the canonical entry point. Two channels carry a signal, and which applies depends on who can answer it. Mail is the agent-to-agent pathway: a worker mails the witness, which unblocks what it can and promotes what needs a person. A visit is the human engagement:escalate.shfiles or refreshes exactly one open visit per situation key, routed to the converse pool. There is no mayor; a situation that needs a human is a visit, same as any other. Patrol formulas escalate rather than mail, because a patrol sits at the top of the agent tier and has no peer to mail. - A takeaway records what a sitting concluded, and its
--waiting-onedge is what makes the wait machine-answerable (lifecycle-composition.md). - The board is render-only.
services/helm(helm-svc) derives every row per render from the ledger;gc-helm.shkeeps only the write verbs — takeaway, open, react. Everything works without the board: it spends no state, so it can never be wrong for longer than one render.
The discipline throughout is agents earn every interaction: prep done before the operator arrives, the choice framed, every surface branded so the operator lands on the question rather than re-orienting.
The feedback workflow (feedback-learning.md) turns corrective feedback into standing behavior, so attention is never spent twice:
- Capture. Working agents self-report observation beads when a turn
brings corrective feedback; the
feedback-minerorder sweeps merged PRs' review comments for what self-report missed; "learn this: …" is the operator fast path. - Distill. The
feedback-distillerorder clusters observations into patterns and promotes recurring ones as ordinary PRs against the pack — prompt edits, lint rules undertools/lint-learned.d/, doc changes. - Land. Promotions ride the same work→review→merge pipeline as everything else. A lesson is real when it is merged pack content, not when it is remembered.
Doctor is the same idea applied to structure: ten of its twelve checks assert an invariant from component-model.md §3 against the live ledger, and the other two guard pack structure, so a property that stops holding fails a named check instead of waiting to be rediscovered.
A proposed capability must trace a straight line from a belief in foundation.md, through a primitive in component-model.md §1, to one of the six workflows above. component-model.md §4 indexes where every existing component sits. Concretely (component-model.md §5):
- Adding a component — it must answer §1's "cost of not having it" column. If it cannot, it is a repair pass for a writer that should be fixed instead.
- Adding a state — declare it in
lifecycle/lifecycle.tomland name its writer, or it does not exist. - Adding a metadata key — it is state; register it in
lifecycle.toml. - Adding an invariant — name its doctor check in the same PR.
If a capability fits none of the workflows, that is the signal, and it points one of two ways: the capability is miscast and should be recast onto the primitives, or the model must move deliberately — and because architecture derives from foundation, sometimes the belief upstream is what has to move first. Either way the change is a decision on the record, not a drift.