Skip to main content
Every trackable thing in Gas City is a bead — a unit of work in the store. Sessions, mail, and convoys are beads; a formula (a reusable method) isn’t a bead, but running one materializes its steps as beads. You’ve been creating beads all along: starting a session, sending mail, cooking a formula, slinging work. This tutorial shows what’s underneath. See the six primitives for how Bead relates to Agent, Formula, Rig, Pack, and Event. This page continues from a my-city with my-project rigged, the pancakes formula cooked, and mayor / reviewer / worker agents (see Tutorial 05). Everything below runs against the bead store with the bd tool. Confirm the local pack, rig registration, and reviewer binding carried forward from the earlier tutorials:

What is a bead

A bead is a unit of work with an ID, a title, a status, and a type. We use the bd tool to work with beads directly.
The seven pancakes beads come from cooking the formula in Tutorial 05: a root bead (mc-79s), one bead per step, and the Finalize workflow control step the v2 compiler appends. Each is an independent top-level bead with its own ID; ordering lives in dependency edges (below). The leading glyph is status, followed by ID, priority (P2), and title. Pass --flat for a single-level list, --all to include closed beads. Your inventory will differ — Refactor auth module, Sprint 42, and Update API docs are created later on this page. The shape is what matters. Every bead has:
  • ID — prefixed with two letters from the city or rig name (mc-194 for “my-city”, ma-12 for a rig “my-app”)
  • Title — human-readable name
  • Status — open, in_progress, blocked, deferred, or closed
  • Type — what kind of bead it is

Bead types

The type determines what a bead represents: There’s no separate storage for tasks vs. messages vs. sessions — one store, one query interface, one dependency model for everything. That’s what makes the system composable. A formula has no type of its own. Cooking or slinging one produces plain task beads: the root carries gc.kind=workflow metadata, and an ephemeral root-only run carries gc.kind=wisp. The kind lives in metadata, not the type column. (The v1 materialization wrapped a run in a dedicated molecule container bead with parent-child step children; the current one uses plain task beads plus gc.kind metadata.)

Creating beads

Most beads are created indirectly:
  • gc session new my-project/reviewer creates a session bead
  • gc mail send mayor "Subject" "Body" creates a message bead
  • gc formula cook review creates a workflow root plus a bead for every step
  • gc sling mayor review --formula does the same and routes the work to mayor
But you can use bd to create them manually:
The trailing lines (Priority:, Status:) vary by bd version — some builds print one, some both. The bead is created identically either way. The dependency and convoy examples below use all three of these beads.

Bead lifecycle

Beads flow open → in_progress → closed: A bead's lifecycle: open → in_progress → closed on the main line — an agent claims it via gc hook, then closes it — with blocked and deferred as system-managed side states that return to the line when the dependency closes or the date passes.
  • open — not started; discoverable by agents via hooks.
  • in_progress — claimed by an agent.
  • closed — done.
  • blocked — has an open blocks dependency. Set automatically.
  • deferred — snoozed until a date.
You reach for open / in_progress / closed day to day; blocked and deferred are derived states the system manages for you.
Note that the flag is --status (--state is a different command for state dimensions). As with the opening listing, the output here is trimmed to the beads this page created — yours will still include the open pancakes beads and the mayor’s session bead.

Beads as execution state

The bead store is the execution state of the whole system — every running session, message in flight, and formula step is a bead with a status. To know what the city is doing right now, query the store:
Because work lives in the store rather than in memory, agent sessions are disposable. If an agent dies its beads stay open; when it restarts, its hooks rediscover the same work. The store is ground truth across crashes and full restarts. The rest of this chapter covers how beads get organized, routed, grouped, and discovered.

Labels

Labels are how beads get organized and routed:
bd label add takes a single label per call — apply multiples one at a time. Some labels have special meaning in Gas City:
  • gc:session — marks session beads
  • gc:message — marks mail beads
  • thread:<id> — groups mail messages into conversations
  • read — marks a message as read
You can add any labels you want for your own organization.

Metadata

Beads carry arbitrary key-value metadata for structured state:
Internally, metadata drives session tracking (session_name, alias), routing (gc.routed_to), merge strategies, and formula references. Attach anything you like without touching the title or description; --unset-metadata <key> removes one.

Dependencies

Beads depend on other beads. You saw this in formulas — needs = ["design"] is a blocking dependency, so the step can’t start until the design bead closes. This is how Gas City orders work without a central scheduler: each bead knows what it waits for, and agents see only ready work.
Now mc-xp7 won’t appear in any agent’s work query until mc-a4l is closed — the same mechanism behind formula step ordering, where needs declarations become blocks edges. The dependency types: Only blocks affects work visibility — it expresses ordering (“do A before B”). The rest express grouping (“these beads belong together”): v2 workflow steps tracks their root, convoys tracks their members, and v1 molecules use parent-child. A convoy’s members don’t depend on each other; they’re just the same batch.

Convoys

Slinging an existing bead wraps it in a convoy automatically. Convoys answer batch questions — “are all five of these tasks done yet?” — and show up in bd list as type convoy and in gc convoy list with progress summaries. You can also create one by hand to group arbitrary work as a sprint or deploy:
The convoy is a convoy bead; membership is tracks edges from it to each member — the “tracking 3 issue(s)” above. Tracking is pure grouping: it changes no parent and blocks nothing. Convoy membership shown as tracks edges: a convoy bead ("Sprint 42") with
dashed tracks edges to three independent work beads (fix login bug, refactor
auth, update API docs). The members keep their own identity and status and
are not children of the convoy.

Auto-close

When a bead closes, any convoy tracking it whose members are now all closed closes itself — in the background via the on_close hook, no polling. Convoys with the owned label skip auto-close, for workflows where you want explicit control over completion:
When you’re done, land it explicitly:

Adding beads and checking convoys

Work grows after a convoy is created — a bug surfaces mid-sprint. Add beads to an existing convoy:
If a convoy should have auto-closed but didn’t (a hook misfired), reconcile manually:

Stranded work

To find open beads in convoys that have no assignee — work that’s stuck waiting for someone to pick it up:

Convoy metadata

Convoys carry metadata that controls how grouped work behaves:
  • convoy.owner — which agent manages this convoy
  • convoy.notify — who to notify when the convoy completes
  • convoy.merge — merge strategy for PRs (direct, mr, local)
  • target — target branch inherited by child beads
These are set at creation time with flags:
Or update the target later:

How agents find work

Routed agents discover work through the claim protocol in their session startup prompt, which runs gc hook --claim. (The legacy Stop-hook form gc hook --inject is silent compatibility behavior and no longer injects work.) The flow:
  1. Work is created (bd create, gc sling, formula cook, …)
  2. Work is routed to an agent (assignee or gc.routed_to metadata)
  3. Session startup runs the agent’s work query through gc hook --claim
  4. The hook atomically claims one ready bead and preassigns continuation siblings
  5. The agent runs the claimed work — find work on your hook, you run it
For work routed to a pool — a group of agents sharing a work queue, which Tutorial 07 covers — the query checks metadata instead of assignee:
mc-xp7 is blocked by mc-a4l, so this query won’t return it — blocked work is invisible to work queries. Once mc-a4l closes, rerun the same query. The readiness barrier is gone, though mc-xp7 would also need gc.routed_to=my-project/worker to land in this queue, which nothing here sets. Routing decides which queue a bead appears in; readiness decides whether it appears at all. This is the “pull” model: agents check for work instead of having it pushed.

The bead store

Beads are persisted in a store. Gas City supports several backends:
  • bd (default) — Dolt-backed database via the bd CLI. Full-featured, good for production.
  • file — JSON file on disk. Simple, good for tutorials and small setups.
  • exec — Delegates to a custom script. For integration with external systems.
Configure the backend in city.toml:
For most users, the default works fine and you don’t need to think about it.
You rarely touch beads directly — gc session, gc mail, gc sling, and gc formula handle creation and management. You reach for bd when you want to query outstanding work across the city, create ad-hoc tasks, inspect a formula’s dependency graph, or debug why an agent isn’t picking up work. (Listings trimmed to this page’s beads; open pancakes beads will show up in yours too.)
The Sprint 42 entry under BLOCKS is the convoy’s incoming tracks edge — grouping, not a blocker. Beads are the ground truth of the city’s running state. Sessions, mail, and convoys are beads; a formula materializes its work as beads when run.

What’s next

  • Orders — formulas and scripts on autopilot, triggered by time, schedule, conditions, or events
Last modified on August 28, 2026