gc sling
is dropping work, supervisor.log shows rigStores=0, or your port
edits keep reverting, you are in the right place.
Rules of thumb — skip to What NOT to do if you are mid-incident. Come back for the mental model after.
Mental model
A city runs one Dolt SQL server. Every rig is a logical database inside that Dolt. Each rig’s.beads/dolt-server.port is
a compatibility mirror that lets raw bd commands reach the
city Dolt — it is not the rig’s own server.
Endpoint origins
Two TOML fields, one per scope, describe endpoint ownership:managed_city— The city owns the Dolt lifecycle.gc startlaunches it;gc stopshuts it down. This is the default for freshbd-backed cities.city_canonical— The city declares an explicit, externally managed Dolt. GC does not launch or manage the server.inherited_city— The rig reuses its parent city’s endpoint. No rig-local Dolt. This is the default for rigs added to amanaged_citycity.explicit— The rig pins its own external endpoint. Use for cross-city rigs or rigs pointing at a shared Dolt cluster.
gc.endpoint_origin describes endpoint ownership, not JSONL retention. To
keep a city-root .beads/issues.jsonl on disk, leave the city origin as
managed_city or city_canonical and change the existing
export.auto: false line in that scope’s .beads/config.yaml to
export.auto: true. Running bd config set export.auto true from the scope
root makes the same change. Gas City preserves that explicit setting and
skips stale-JSONL cleanup for the scope. Do not append a second
export.auto key instead: bd cannot parse a config.yaml with a duplicate
key and ignores the whole file, and Gas City honors only the first
occurrence, so the scope stays opted out and the file is still removed. Do
not use explicit at city scope; it is a rig-only endpoint origin and fails
canonical validation.
Every supported bd (1.3.0 or newer) imports a present issues.jsonl only
into an empty database, so the retained file is never re-applied over
existing Dolt data.
The opt-out buys retention, not freshness. Gas City still forces
BD_EXPORT_AUTO=false on its own bd calls — gc bd, controllers,
dispatch, mail — so those writes do not re-export the file, and the retained
issues.jsonl drifts from Dolt with no warning. Refresh it out of band from
the scope root with bd export -o .beads/issues.jsonl, and note that
bd export omits infrastructure beads and memories unless you pass
--include-infra, --include-memories, or --all. Plain bd run in the
scope, from a shell or an agent session, does not get the
BD_EXPORT_AUTO=false override: it follows export.auto: true, so its
writes can re-export the file too. Use gc bd instead if the file should
change only when you refresh it. Rolling back to a Gas City build from
before this opt-out existed rewrites export.auto back to false and reaps
the file once.
What NOT to do
These are the edits mayors try first, all of which self-revert at the nextgc start and waste an afternoon.
Do not run bd dolt set port <N> inside an inherited rig
If gc.endpoint_origin: inherited_city, the rig’s canonical port
is whatever the city’s managed Dolt is listening on. bd will
accept the set port command and update .beads/config.yaml, but
on the next gc start the city’s normalization pass
(syncConfiguredDoltPortFiles) restores the managed port and
rewrites .beads/dolt-server.port back. Your change disappears.
Do not hand-edit .beads/dolt-server.port
Same reason. The file is a compatibility mirror for raw bd
commands — it reflects the city-managed port, not an authoritative
choice. gc start atomically rewrites it to match the managed
port on each startup.
Do not delete the gc.endpoint_origin: inherited_city line
Deleting the line does not switch the rig to explicit. It puts
the rig into an unverified state. gc start’s verification pass
will re-derive the origin from topology and write the line back.
Do not run bd dolt start inside an inherited rig
Any Dolt server you start this way is orphaned. GC does not track
it, will not shut it down on gc stop, and will not publish its
port to dependent sessions. If the rig’s .beads/dolt-server.port
points at this orphan server, the drift check will flag it on next
gc doctor run (see Drift symptoms below).
Sanctioned escape hatches
When you genuinely need to change a rig’s endpoint, usegc rig set-endpoint — it is the single owning operation for endpoint
topology, and it refuses footgun configurations up front.
Make this rig run its own Dolt
--self marks the rig as running its own 127.0.0.1:<P> Dolt.
While the city is in managed_city mode the command refuses
unless --force is given, and emits a one-line WARN explaining
the rig now falls outside gc start’s supervision. The rig’s
origin flips to explicit; its port file will no longer track
the managed city Dolt.
Rejoin the city
inherited_city. The next gc start will
re-publish the managed city port into .beads/dolt-server.port
and the rig’s sessions will talk to the city Dolt again.
Point at an external Dolt
explicit origin to <H>:<P>. Use this for cross-city rigs
or rigs pointing at a shared/external Dolt cluster. Add
--adopt-unverified if you need to record the endpoint before the
target is reachable.
Inspect current state
gc doctor is authoritative for GC-side state: it reports the
managed city port, each rig’s resolved endpoint origin, and any
drift it detects between the two. The dolt-drift check only
registers when the workspace is in bd-backed managed_city
topology — if you do not see it, your workspace is not in that
mode.
.beads/dolt-server.port priority explainer
When raw bd commands resolve a port, they consult sources in
this order (first hit wins):
$BEADS_DOLT_SERVER_PORTenvironment variable.beads/dolt-server.portfile (the compatibility mirror).beads/config.yamldolt.portkey (or the global beads config equivalent).beads/metadata.jsonhistorical record
$BEADS_DOLT_SERVER_PORT
for sessions it launches, which is why sessions keep working even
when the on-disk mirror is transiently out of date. For raw
interactive bd invocations the mirror file is what matters, which
is why GC aggressively rewrites it during normalization.
The authoritative source for this order is DefaultConfig in
beads/internal/doltserver/doltserver.go.
bd dolt show advertises a slightly different order in its output
text; that is a known cosmetic bug tracked separately in the beads
repo and does not affect actual resolution.
Drift symptoms
Thedolt-drift check (cmd/gc/cmd_doctor_drift.go) is registered
automatically when the workspace is in bd-backed managed_city
topology and catches three shapes of drift:
All three carry
FixHint text pointing at the specific
gc rig set-endpoint invocation that resolves them.
At gc start, the Dolt-port normalization pass also emits a WARN
line on stderr for each rig whose .beads/dolt-server.port it is
rewriting back to the managed port — that is your early warning
that a prior edit or orphan server made the mirror drift.
Storage-mode drift: a scope pointed at the wrong database
.beads/metadata.json names which bead database bd opens.
dolt_mode=server reads the managed city’s .beads/dolt;
dolt_mode=embedded reads the scope’s own
.beads/embeddeddolt/<dolt_database>. Nothing copies rows between
them.
gc rig add, gc start, gc supervisor run, gc rig set-endpoint
and gc beads city use-managed|use-external all canonicalize a
managed scope to server mode, because a Dolt directory opened
in-process cannot be shared by the controller, every agent’s bd
and the dashboard at once. On a workspace you had initialized in
embedded mode, that flip re-points the ledger and leaves the old
database on disk, unread — and bd does not fail, it answers []
with exit 0. This is not split-store specific: it bites a city with
exactly one store just as hard.
Three things now tell you:
- The flip prints the scope, both modes and the exact directory it is about to stop reading, before it happens.
gc doctor’sbd-split-storecheck enumerates both databases.- An empty whole-ledger read (
gc ready’s city leg, an unfilteredList) from a scope that has not returned a row in that process prints one notice pergcprocess, naming the unread database. It is a notice, never a refusal, and it spends nobdsubprocess of its own — a workspacebd initcreated and never filed a bead into is the same shape on disk, an idle ledger reads the same way, and separating them would mean either opening the database you were told to preserve or askingbda second question on the caller’s read deadline. Expect it on any scope that still has two bead databases, including healthy ones; that is the price of a notice that cannot cost a read.
gc doctor’s: export from a copy of the unread
database, review with bd import --dry-run, import into the active
one, and keep both directories until reconciled. Editing
dolt_mode back does not hold — the next gc start re-canonicalizes
it. While you reconcile, GC_BD_ALLOW_UNREAD_STORE_READ=1 silences
the read-time notice without changing any answer.
Recovery recipe — slung beads not reaching agents
If you seegc sling accepting work but agents not processing it,
and the supervisor log reports rigStores=0 or
assignedWorkBeads=0, that is almost always a symptom of the rig’s
Dolt view diverging from the city’s. Work the following sequence:
-
Stop the city:
-
Kill any live rig-local Dolt servers, rig by rig. For each
rig, check
.dolt/sql-server.infofor a PID and verify it: -
Confirm the rig’s canonical state is
inherited_cityand verified: each rig’s.beads/config.yamlshould contain:If any rig says something else and you want it to rejoin, rungc rig set-endpoint <rig> --inherit. -
Delete the compatibility mirror in each rig, so it is
regenerated fresh on next start:
-
Start the city:
-
Verify only one Dolt is running on the managed port:
-
Test a sling end-to-end and watch the supervisor log for
assignedWorkBeads > 0:
gc doctor --verbose output plus
the supervisor.log slice covering the last sling attempt.
References
- Design: Beads-and-Dolt contract redesign — the authoritative architecture document.
- Code:
cmd/gc/cmd_doctor_drift.go— drift detector. - Code:
cmd/gc/cmd_rig_endpoint.go—gc rig set-endpointimplementation. - Code:
cmd/gc/beads_provider_lifecycle.go—syncConfiguredDoltPortFilesnormalization. - Upstream:
beads/internal/doltserver/doltserver.goDefaultConfig— source of truth for port-resolution priority.