Skip to main content
The Bead is the WHAT primitive — a unit of work — and the bead store is the machinery that persists beads (tasks, messages, convoy members, and the ephemeral beads a formula run materializes). See How Gas City Works for where the Bead sits among the six. Gas City’s exec beads provider delegates each beads.Store operation to a user-supplied script, the same pattern the exec session provider uses for sessions. It makes the bead store a pluggable boundary: change one config line and Gas City persists beads through your script instead of the default bd (Dolt-backed) store.

Why

The default bd provider couples the store to a specific stack — the Go bd CLI wrapping a Dolt SQL database. A script backend lets you point Gas City at:
  • A different beads engine — for example beads_rust (br), a SQLite + JSONL hybrid with no JVM/Dolt dependency.
  • Custom persistence — bead writes that also trigger S3 snapshots, git commits, or other durability strategies.
  • Alternative databases — Postgres, SQLite, flat files, or any backend reachable from a CLI.

Usage

Select the provider in city.toml:
GC_BEADS overrides the config for a single command:
Resolution order is GC_BEADS → [beads] provider → bd (the default).

Calling Convention

The script receives the operation name as its first argument:
The script is exec’d directly — no shell. Mutations pass their payload as JSON on stdin; reads return JSON on stdout. Each invocation has a 30-second timeout. The script is spawned fresh per operation; there is no long-lived process to manage.

Exit Codes

Exit code 2 is the forward-compatibility mechanism: when Gas City adds an operation, an older script returns exit 2 and the provider treats it as a no-op success, so scripts only implement the operations they care about. The one exception is ready invoked with contract arguments (such as --include-ephemeral): there, exit 2 is surfaced as an error rather than a silent success, so runnable ephemeral beads are never quietly hidden from the orchestrator. A script that cannot serve those ready queries should exit 1 with a stderr message instead.

Operations

These are the subcommands a backend implements. Bead reads return a JSON array ([] when empty, never null). The remaining Store methods are composed by the provider from the subcommands above, so a script that implements this table supports them with no extra work:
  • ListOpen, ListByAssignee, ListByMetadata route to list; the provider applies the status/assignee/metadata filter after the script returns.
  • CloseAll is a sequence of close calls; SetMetadataBatch a sequence of set-metadata calls (neither is atomic — design scripts to be idempotent).
  • Tx runs its callback sequentially against the same store; writes are not grouped into a single transaction.
  • Ping runs list to confirm the script is reachable.

ready and ephemeral tiers

ready returns open, unblocked beads. The orchestrator’s demand scans pass --include-ephemeral, and the script must then include ephemeral run beads in its output; Gas City applies the caller’s tier filter after parsing. Future- deferred beads (defer_until in the future) are filtered at the store boundary, so a script may return them and let the provider drop them.

Wire format

Beads are exchanged as JSON matching the bd --json shape:
The script assigns id, status, created_at, and updated_at on create. On create the provider defaults a missing type to task before calling the script; on every read it normalizes a missing or empty status to open. metadata values may be any JSON type but are coerced to strings. A closed bead may carry close_reason, a string saying why it was closed (bd’s close_reason field); the provider reports it as the bead’s close reason and ignores it on a bead that is not closed. The close operation carries no reason; Gas City stamps metadata.close_reason before closing, so a script that records reasons can take it from there. Preserve ephemeral, no_history, and defer_until on every read path so the provider can keep run beads out of normal work queries. no_history=true marks durable work that carries no version-control history: unlike ephemeral beads it stays visible in issues-tier reads, and also appears in wisps-tier queries. Backends without native columns for these bits may store them internally (e.g. a private label) but must round-trip them as the matching JSON fields. The create request is a subset (title, type, priority, labels, parent_id, ref, needs, description, assignee, from, metadata, ephemeral, no_history, defer_until). The update request carries only the fields being changed (title, status, type, priority, description, parent_id, assignee, plus labels/remove_labels and a metadata overlay); omitted fields are left unchanged, and labels appends rather than replaces. dep-list returns objects of { "issue_id", "depends_on_id", "type" }.

Conventions

  • JSON on stdin for mutations avoids shell-quoting issues with titles, descriptions, and labels; set-metadata passes its value as raw stdin bytes so it can hold newlines, quotes, or JSON.
  • Not found → exit 1 with not found (or no issue found) on stderr; get, close, and reopen map that to the platform’s not-found error.
  • Idempotent writes — close on an already-closed bead should still exit 0; batch and transaction operations are not atomic.
  • Return supersets — list, children, and list-by-label should return every matching bead the script can see, including ephemeral ones; the provider applies the caller’s tier, query filters, and result limit after parsing (it always passes list-by-label a limit of 0).
  • Three-state status — the store vocabulary is open, in_progress, and closed. A backend with a richer status set maps the extras onto these three.

Environment Variables

The provider sets these before exec’ing the script: The parent environment is otherwise inherited, except that BEADS_* and GC_DOLT_* keys are stripped so a script cannot accidentally inherit the default store’s backend configuration.

Writing Your Own Script

  1. Start from contrib/beads-scripts/gc-beads-br as a template.
  2. Implement the operations your backend supports; let the provider compose the rest.
  3. Return exit 2 for operations you do not implement (but exit 1 for ready --include-ephemeral if you cannot serve it).
  4. Test with GC_BEADS=exec:./your-script gc status.

Minimal script (create / get / list)

Shipped Scripts

See contrib/beads-scripts/ for maintained implementations:
  • gc-beads-br — beads_rust (br) backend: SQLite + JSONL, no Dolt dependency. Dependencies: br, jq, bash.
  • gc-beads-k8s — runs bd inside a “beads runner” pod via kubectl exec, connecting to Dolt running as a StatefulSet in the cluster. Dependencies: kubectl, jq, bash.
The bundled bd provider itself ships an exec script (gc-beads-bd.sh) under the bd pack’s assets, which is how the default Dolt-backed store is driven through the same protocol.
Last modified on October 7, 2026