Hello and welcome to Beads 1.1! If you’re new here: beads is the database your coding agent uses to remember what it’s doing. Instead of holding a plan in its context window — where it evaporates the moment the session ends — an agent writes each unit of work to bd, a small database that lives in your project. Each unit is a bead: it has a status (open, in_progress, blocked, deferred, closed, plus a couple of specialty states), and beads can depend on each other, so a big feature becomes an ordered graph of smaller ones instead of one giant markdown file.

Figure 1: One feature shredded into beads: parent-child and depends-on edges tell your agent what's ready to run, and a few beads sit with no dependencies at all.
One feature shredded into beads: parent-child and depends-on edges tell your agent what's ready to run, and a few beads sit with no dependencies at all.

A basic beads session looks like:

bd create "Add rate limiting to the API" --priority 1
bd ready # what's unblocked and ready to work on
bd close <id> --reason "Shipped in #142"

Because the state lives in the database and not in the agent’s head, an agent can get stuck, crash, or hand off to a different agent entirely, and the next one picks up exactly where the last one left off. That’s true whether it’s one agent on your laptop, a small team sharing a database across their machines and CI, or a whole fleet of agents running in parallel — and it matters most the moment more than one clone starts writing to the same store.

Here’s why: beads stores it’s state in a Dolt repository under the hood, and Dolt repositories can fork. If two clones each migrate their own copy of the schema independently, they can no longer merge — Dolt refuses outright. cannot merge because table dependencies has different primary keys in its common ancestor is not a sentence you want to see at 2am. If you only ever run beads locally with no remote, this specific risk doesn’t apply to you. If you sync a beads database across more than one machine — a teammate, a CI runner, a fleet of agents — keep reading.

None of this is hypothetical. Every fix below started as a bug report from someone who hit the edge in the wild: two clones forking on the same dependency edge (#4259), a half-finished migration bricking a database on open (#4502), an orphaned row failing every bd create (#4534), compaction quietly eating an issue’s original text (#4464). That 2am error message is a real one.

Beads 1.1.0 was built to close that off, and it shipped on July 4th after eight weeks and two release candidates. Since v1.0.4, more than 30 contributors merged 260+ pull requests, closed 80+ issues, and landed 600+ commits. Top of the commit count: Dustin Brown and Matt Wilkie, with beads creator Steve Yegge still in the trenches, and Jim Wordelman, who sent more than 20 of those pull requests — also on the list. Thank you, all of you. None of this happens without people showing up to file the issues and send the PRs, so thank you to the community for that!

How to upgrade safely

If you take one thing from this post, take this. Running solo with no remote? Install the new binary and go — the next bd command migrates in place, same as always. If more than one clone syncs the same remote, upgrade in this order:

# 0. Back up first — cheap insurance before you touch a remote-backed database
bd export --all -o backup.jsonl

# 1. On every clone, with your CURRENT binary — get everyone in sync first
bd dolt push && bd dolt pull   # repeat until clean, then stop editing

# 2. On the designated migrator only — install the new binary, then:
BD_ALLOW_REMOTE_MIGRATE=1 bd migrate
bd dolt push

# 3. On every other clone — install the new binary, then:
bd bootstrap   # adopts the migrated database; do NOT run bd migrate here

A word about that env var, because it’s the sharpest tool in this post. BD_ALLOW_REMOTE_MIGRATE=1 declares this clone is the designated migrator and switches the gate off for that one run — all of it, including the forked-schema checks everything below celebrates. That’s the point: migrating a database other clones depend on is a judgment call bd needs you to make explicitly. So handle it accordingly: exactly one clone, only after step 1 has everyone in sync, and never wired into every clone’s upgrade job — two clones running it at once is the independent-migration fork this whole release exists to prevent. (At a keyboard, plain bd migrate is often enough — when the gate can prove you’re a safe first mover it proceeds on its own, and when it can’t it stops and lists your options.)

The full procedure — including upgrades from much older versions — is in the upgrade guide.

The rest of this post is why that recipe exists — and what 1.1 does so that getting it wrong no longer costs you your database.

Migrations that work, even with a dozen clones

The problem in one sentence: when more than one clone shares the same remote, an uncoordinated schema migration on one of them can fork the whole thing — permanently, without manual surgery. A solo bd database with no remote never hits this. Everyone else is exposed the moment any two clones migrate independently:

Figure 2: Four clones -- your laptop, a teammate's, a CI runner, and a fleet of agent workers -- sharing one Dolt remote; any two that migrate independently can fork the schema.
Four kinds of clones — your laptop, a teammate's clone, a CI runner, and a
fleet of parallel agent workers — all pushing and pulling against one shared
Dolt remote. Any two of them that migrate the schema independently can fork
it.

The state-aware remote-migrate gate is on by default now (#4516, #4551). Every time bd would silently auto-migrate a database that has a remote configured, it now checks its state against the remote and picks a lane instead:

Figure 3: The remote-migrate gate's three lanes: same version with no drift auto-migrates, a remote that's already ahead sends you to bd bootstrap, and a genuine content fork stops for a human.
A flowchart: bd compares this clone's schema state to the remote's. Same
version with no drift auto-migrates with nothing for you to do. Remote already
ahead stops and tells you to run bd bootstrap to adopt. Same version but
different content — a genuine fork — stops and tells you it needs a
human.

The skew case is detectable at all because schema_migrations now records a hash of each migration’s content alongside its version, so bd doctor can spot when two clones both claim “v53” but got there differently. And if you do hit Dolt’s hard fork refusal, bd now recognizes it and prints the actual recovery playbook instead of just the raw error.

This release also heals databases that already got bitten. A v53 migration that failed partway used to leave a database bricked — every command failing on open. Now it self-heals automatically on your next bd command: it repairs issues tables that predate the rig/agent columns (#4502) and backfills missing dependency columns before v53 runs (#4558). A separate self-heal cleans up an orphaned counter row that could brick every bd create on an otherwise healthy database (#4534). And a dirty working set no longer deadlocks recovery: bd dolt commit and bd vc commit now open past the guard that used to trap them too (#4567).

At the keyboard, the new moving parts are small:

bd doctor        # catches two clones claiming "v53" with different content
bd dolt commit   # commits a dirty working set that used to deadlock recovery

Add it up and upgrading a shared beads database is no longer a coordination problem you solve with a wiki page and good timing: the dangerous move gets refused, the safe move happens on its own, and an already-bricked database heals on the next command. Bringing a second machine into the loop — a teammate, a CI runner, a fleet — stops being the moment you hold your breath.

Safer syncing across every clone

The same “clones need to agree” problem showed up in smaller ways too, and this release closes several of them. Two clones that independently add the same dependency edge, or that delete an issue on one side while adding a comment to it on the other, used to fail to merge outright (#4259) — now they converge cleanly instead (in the delete-vs-comment case the delete wins, taking the comment with its issue the way a cascade delete would have). Pulls that used to strand half-merged — foreign-key violations left behind by issue deletes, or clones whose schema_migrations tables came from different eras — now cascade-repair or auto-resolve on their own. And when a merge genuinely can’t be fixed automatically, bd prints the recovery path instead of a stack trace and best wishes. The net: two clones doing perfectly ordinary work no longer need a human on call to unstick their merges — which was the point of syncing through a remote in the first place.

The is_blocked flag — which tells you whether an issue is waiting on something — used to go stale silently across a merge, hiding ready work or showing blocked work as ready. Pulls now recompute it automatically, and if a database’s already in a bad state, the new bd recompute-blocked command does a full repair in both embedded and server mode. Day to day:

bd dolt pull           # merges settle themselves; is_blocked recomputed on the way in
bd recompute-blocked   # full repair for a database already in a bad state
bd ready               # the queue your agents can take at face value

That flag is what bd ready reads, and bd ready is what your agents act on. With every pull recomputing it, a fleet can trust the queue without a human auditing it first, which means no more idling past work that was secretly ready, or piling onto work that wasn’t.

Restore actually restores

The recipe up top says to back up first, which raises an uncomfortable question this release finally answers: does the restore path actually work? Start with compaction. bd admin compact — the space-saver that squashes long text on old issues — used to overwrite descriptions and clear notes in place with no archive at all, and bd restore was display-only, reconstructing content from a Dolt-history heuristic that garbage collection could quietly erase. Compact an issue, and its original content could be gone for good. In 1.1, compaction archives a full snapshot before touching anything — if the archive fails, compaction aborts with your content intact — and bd restore --apply writes the original straight back (#4464). Beads also stopped advertising a compaction tier that promised 95% savings and was never implemented (#4465) — honesty in the help text counts too.

bd import — the restore half of every bd export backup — grew up alongside it. The stale guard that keeps an old snapshot from clobbering newer work is now enforced inside the import transaction, ties on timestamps keep your local row, and import reports what it actually changed instead of finishing silently. And when rolling back to an older snapshot is the whole point, --allow-stale lets you do that:

bd admin compact                      # squash old closed issues — snapshot taken first
bd restore <id>                       # inspect the archived original (display-only)
bd restore <id> --apply               # write it back
bd import --allow-stale backup.jsonl  # deliberately roll back to an older snapshot

Now compaction becomes routine maintenance on a long-lived database, and the bd export at step 0 of the upgrade recipe is insurance that actually pays out.

Breaking changes

Beads works hard to keep this list short, but three behavior changes are worth knowing before you upgrade a database you care about.

Remote-backed migrations are gated by default. Before this release, running bd against a database with a remote configured would silently apply pending migrations on whichever clone got there first — exactly how two clones fork a schema. bd now refuses that and tells you what to do instead — the recipe above is the order that keeps every clone mergeable.

JSONL auto-export flipped from on to opt-in. .beads/issues.jsonl used to refresh and get committed automatically after most writes. It’s now an opt-in export for tools that want it, not the default mutation path. If your setup depends on that file staying fresh automatically, turn it back on:

bd config set export.auto true
bd config set export.git-add true

The dependencies table got re-keyed. The old depends_on_id column is gone — the target now lives in three typed columns, with depends_on_id surviving only as a computed value on reads — and every row carries a primary key derived deterministically from the edge itself, which is exactly what makes the same-edge merges above converge. Code that writes dependency rows with raw SQL instead of bd dep add will break; if that’s not you, there’s nothing to do.

And More!

  • bd init --init-if-missing makes init idempotent — for scripts and agents that shouldn’t have to care whether they’re first (#3490).
  • bd metrics is a new command to see and control anonymous usage metrics (which commands run, bd version, OS — never your issue content). On by default with a one-time heads-up; bd metrics off turns it off for good.
  • bd remember <existing-key> now recalls the memory instead of silently overwriting it — a real footgun before.
  • Piped bd list output no longer gets truncated, which matters if you script against it.
  • Read-only now means read-only, all the way down to the embedded store — so a dashboard or an observer agent can watch a database with no chance of writing to it.

For the full technical details, check out the full release notes.

Where are we?

Beads’ whole reason for existing is that an agent can get stuck, or you can close your laptop, and the work picks up exactly where it left off — because the state lives in a database, not in a context window. This release makes the Dolt database more robust in the case that you more than one clone, sharing one remote, over time.

Beads has crossed 25,000+ stars on GitHub. Thank you again to the Gas Town Hall community — go upgrade, run the recipe above if you sync more than one clone, and let me know how it goes. If you hit something the gate didn’t catch, file it — that’s how the next release gets built.

Have thoughts about this post? Join the Gas Town Hall Discord!