Beads: Durable Work Memory for Coding Agents

📖 19 min read

Beads is an issue tracker built for coding agents rather than for people. It stores work as a graph of small items with typed dependencies, keeps that graph in a version-controlled database next to the code, and answers one question an agent asks constantly: what can I work on right now? Its command-line tool is bd, and its documentation is the primary reference for everything below.


Why Agents Lose Track of Work

Every Session Starts Blank

A coding agent knows only what is in its context window. When a session ends, or when the context is compacted to make room, the agent’s understanding of what it finished, what it deferred, and what it found along the way goes with it. Crossing a session boundary is routine and cheap on its own. Agents are started fresh all the time, to keep context small or to run an expensive phase of work separately, and re-priming a new session costs little when the task can be restated in a paragraph. The cost lands on projects that have been running long enough to accumulate their own history of decisions, deferrals, and findings, where no such paragraph exists. There the agent either re-derives the state of the project from the code or trusts whatever notes survived.

Markdown Plans Decay

The usual fix is a plan file the agent reads at the start of each session. Plans work for a while and then drift. An agent that finishes step four may not mark it done, an agent that discovers a bug mid-task may note it in prose three screens away from where anyone will look, and after a few rounds the file holds several overlapping lists, none of them current. Prose also cannot answer a structural question. Nothing in a Markdown list says that step seven cannot start until steps three and five are closed, so the agent has to infer ordering from wording every time it reads the plan.

Parallel Agents Collide

With more than one agent, a shared plan file becomes a merge conflict. Two agents on separate branches both append “task 12,” both pick the same next item, or both edit the same checklist line. Sequential numbering needs a central counter, and parallel agents have no counter they can safely share.


The Work Graph

A Bead Is a Unit of Work

A bead is one tracked item with an ID, title, description, type, priority from 0 (critical) to 4 (backlog), and status. The common types are bug, feature, task, epic, and chore, and the common path through the statuses runs from open to in progress to closed, with others for blocked and deferred work. Beads can also carry labels, comments, an assignee, acceptance criteria, and a deferral date.

Epics hold child beads, and children get IDs derived from the parent’s, so an epic’s subtasks read as bd-a3f8.1, bd-a3f8.2, and so on, up to three levels deep.

Dependencies Decide What Is Ready

Beads are connected by typed edges, and the type determines whether an edge affects scheduling. Only blocking types hold work back.

Edge type Blocks ready work Meaning
blocks Yes The dependent cannot start until the blocker closes
parent-child Indirectly A child is held back while its parent is blocked
conditional-blocks Yes The dependent runs only if the blocker fails, for error-handling paths
waits-for Yes The dependent waits for all of another bead’s children, for fan-in
related, tracks, discovered-from, caused-by, validates, supersedes No Annotations that record context without affecting order

A bead is ready when it is open and every blocking dependency is closed, and when it is not deferred or waiting on a gate. The set of ready beads is the frontier of the graph. It holds everything an agent could pick up right now without stepping on unfinished prerequisites.

Dependency graph

The Ready Frontier

How each edge type decides what bd ready returns.

How edge types decide the ready frontier of a Beads work graph An epic contains five child beads. The closed schema bead blocks the API and migration beads; the API bead is ready and the migration bead is claimed. The rollout bead is blocked by the API, the migration, and a gate waiting on a pull request. A rollback bead is linked by conditional-blocks and runs only if the rollout fails. A flaky-test bead, filed during the migration with a discovered-from edge, sits outside the epic and is ready, because discovered-from never blocks. bd ready returns exactly the API bead and the flaky-test bead. EPIC BD-A3F8 · CONTAINMENT IS THE PARENT-CHILD EDGE bd-a3f8.1 schema closed bd-a3f8.2 API blockers all closed bd-a3f8.3 migrate claimed by agent-2 bd-a3f8.4 rollout waits on .2, .3, gate gate: PR #412 clears when it merges bd-a3f8.5 rollback only if rollout fails bd-7c1e flaky test filed mid-task discovered-from bd ready returns bd-a3f8.2 and bd-7c1e: the frontier .3 is claimed, .4 waits on three blockers, and .5 becomes ready only if .4 fails blocks gate check conditional-blocks: runs on failure discovered-from: provenance only ready frontier in progress closed blocked

Closing the schema bead released the API and migration beads at once, so two agents could take them in parallel, and one already has. The rollout stays off the frontier until both close and its gate clears. The flaky-test bead filed mid-task is ready too, because a provenance edge never blocks. The agent never reasons about this ordering itself. It asks for ready work and gets back exactly the API bead and the flaky test.

Beads checks for cycles when a dependency is added and rejects one that would close a loop. The check can be skipped, and the documentation includes a recovery procedure for cycles that get in anyway, so the check keeps the graph sound in normal use rather than guaranteeing it.

Claiming Takes Work Off the Frontier

When an agent takes a ready bead, it claims it. The claim sets the assignee and moves the bead to in progress in one operation, and if two agents try to claim the same bead in the same database, the first wins. This is what lets several agents draw from one ready set without a coordinator handing out assignments. The guarantee holds only within a single database, which the sync section below makes concrete.

Discovered Work Gets Recorded, Not Remembered

Agents routinely find problems outside the task they are on, like a flaky test, a missing index, or a function with an off-by-one error. In a plan-file workflow those findings live in the session transcript and vanish with it. With Beads, the agent files a new bead linked to the current one with a discovered-from edge. The finding survives the session, carries its provenance, and enters the same prioritization as everything else instead of derailing the task in hand.


Storage and Sync

Hash IDs Remove the Need to Coordinate

Bead IDs are short hashes derived from the title, the creation time, and a random salt, such as bd-a1b2. Two agents on different branches can each create a bead without either knowing the other exists, and the IDs are very unlikely to collide when the branches merge. The length adapts to the size of the database, starting at four characters and growing as the bead count rises (to five past 500 beads and six past 1,500 by default), so collisions stay improbable without anyone managing it.

Dolt Is the Source of Truth

Beads stores its data in Dolt, a SQL database with Git-style versioning: branches, commits, diffs, merges, push, and pull, applied to tables. In embedded mode, every write is committed to Dolt’s history by default. Earlier versions of Beads kept a SQLite database and synced through a JSONL file committed to Git. Current versions can still export .beads/issues.jsonl for viewers, migration, and backup, but the export is off unless configured, and it is not how data moves between machines.

Dolt runs in one of two main modes, and the choice turns on how many processes write at once.

Mode Writers Suits
Embedded (default) One process at a time, enforced by a file lock A single agent, scripts, CI, containers
Server Many concurrent writers through a dolt sql-server process Several agents writing to the same database

Only configuration lands in Git. bd init writes a .gitignore that keeps the database directory (.beads/embeddeddolt/ in embedded mode, .beads/dolt/ in server mode) and the server’s runtime files out of commits. .beads/config.yaml and .beads/metadata.json are tracked, so every clone agrees on the storage mode, the issue prefix, and the sync remote.

C4 · Container

Beads Containers in One Repository

What lives in a clone, and how bead history syncs.

Beads container diagram for one repository A C4 container view of one clone. The coding agent runs the bd CLI and receives context from bd prime. bd reads its config and writes to a Dolt database that auto-commits every write. The Dolt database pushes and pulls bead history to its own ref, refs/dolt/data, on the same git origin that holds the source branches. The optional JSONL export is refreshed by the pre-commit hook and is not a sync path. ONE CLONE OF THE REPOSITORY Coding agent [Container: Claude Code, Codex, …] runs bd in its own shell AGENTS.md or CLAUDE.md [File: written by bd setup] the start-work-end discipline bd [Container: Go CLI] ready · claim · close · prime Dolt database [Store: version-controlled SQL] every write auto-commits .beads/config.yaml [Config: tracked in git] mode, prefix, sync.remote .beads/issues.jsonl [Export: optional, passive] refreshed by the pre-commit hook Git hooks [Shims: bd hooks run] pre-commit, post-merge, pre-push Source code [Git working tree] commits go to refs/heads/* git origin [External: GitHub, GitLab, …] refs/dolt/data bead history, its own ref bd dolt push / pull Two refs, one remote a plain git clone skips refs/dolt/data, so a new clone runs bd bootstrap refs/heads/* source branches git push / pull runs bd prime reads SQL reads ✗ not a sync path calls, reads and writes context or config read anti-pattern external system

Sync Rides Alongside the Code

To share beads across machines, bd pushes and pulls Dolt history through a remote. That remote can be the same Git origin as the code, because Dolt stores its data under a separate ref (refs/dolt/data) that never appears in a source branch. A plain git clone does not fetch that ref, so a new clone runs Beads’ bootstrap step to pull the issue history down before its first use.

C4 · Deployment

Where a Beads Claim Is Atomic

Claiming a bead in embedded clones versus one shared server.

Beads deployment topologies and the reach of an atomic claim Two panels. Across clones in embedded mode, each clone has its own local Dolt database and a claim is atomic only inside that database, so two agents can both claim the same bead before either pushes and the collision surfaces only at merge time. On one machine in server mode, every agent writes through one dolt sql-server to one database, so the first claim wins. Inside Gas City, one server serves the whole city and each rig is an issue prefix on it. ACROSS CLONES · EMBEDDED MODE git origin refs/dolt/data Clone A local Dolt database agent 1 claims here claim is atomic Clone B local Dolt database agent 2 claims here claim is atomic bd dolt push / pull ✗ both claim bd-a1b2 before either pushes Dolt merges cell by cell, so the collision surfaces only at merge: as a conflict, or as a silent merge of two identical claims. A change stays local until it is pushed. ONE MACHINE · SERVER MODE agent 1 agent 2 agent 3 dolt sql-server [Process: many writers] one database one shared ready frontier first claim wins Every claim runs against the same database, so claiming is a true lock. Parallel agents on one backlog belong here. Inside Gas City one server serves the whole city, and each rig is an issue prefix on it, not a database sync or SQL where a claim is atomic

The two layouts behave differently under contention. In server mode every agent claims against one database, so claiming is a true lock. Across clones, each agent claims against its own local copy, and two agents can both claim the same bead before either pushes. Dolt merges at the level of individual cells, so most concurrent changes to different beads or different fields merge cleanly, but a double claim is only discovered at merge time, as a conflict or as a silent merge of two agents’ identical claims. The Beads documentation advises against concurrent modification from multiple clones without a Dolt server for this reason. Parallel agents that need to draw from the same backlog belong on one shared database. Gas City takes this route. It runs one Dolt server for each city and gives every registered project an issue prefix on that server instead of a database of its own.

Sync is also explicit. A change is local until it is pushed, so an agent that closes three beads and ends its session without pushing leaves every other agent working from a stale graph. The Git hooks Beads installs do not change this. They are thin shims that call bd hooks run, the pre-commit hook refreshes the JSONL export when export is enabled, and none of them push Dolt history.

Bead State Is Separate From Code State

Because bead history lives on its own ref, closing a bead does not mean its code has merged. An agent can close the bead for a feature on a branch that is later abandoned, and the graph will report the work as done. A plan file committed with the code at least travels with that code. Beads trades that coupling for a single shared view of work across branches, and gates, covered below, are the mechanism for tying a step to an outside fact like a merged pull request.


Repeatable Workflows

Formulas, Protos, and Molecules

Some work has the same shape every time, like a release, a dependency upgrade, or a security review. Beads captures that shape as a formula, a TOML or JSON file that declares steps, the steps each one needs before it can start, and variables such as a version number. Compiling a formula with bd cook produces a proto, a template epic. Pouring the proto with bd mol pour produces a molecule, an epic whose children are ordinary beads wired with the formula’s dependencies. The CLI names these phases after states of matter. The proto is the solid phase, a reusable template carrying a template label that is not yet live work. The molecule is the liquid phase, persistent and synced like any other bead. Wisps, covered below, are the vapor phase.

Flow

Workflow Phases: Formula to Molecule

Formula to proto to molecule or wisp.

The Beads workflow phases from formula to molecule or wisp A formula file is cooked into a proto, a template epic that is the solid phase. Pouring the proto produces a molecule, persistent beads in the liquid phase that sync like any bead. Wisping it produces a wisp, ephemeral beads in the vapor phase that are not federated and are purged after closing. A wisp that found something worth keeping is squashed into a permanent digest; otherwise it is burned. Below, a molecule is shown as what it is underneath: an epic whose child steps are wired by the formula's needs, run in parallel unless a dependency or a gate holds them back. SOURCE SOLID · TEMPLATE LIQUID · PERSISTENT VAPOR · EPHEMERAL formula [TOML or JSON file] steps, needs, variables proto [Epic, template label] variables unfilled molecule [Epic + child beads] synced like any bead steps flow through bd ready like any other work wisp [Ephemeral beads] not federated, purgeable closed: bd purge or wisp GC digest permanent deleted no record bd cook bd mol pour bd mol wisp squash burn A MOLECULE IS AN EPIC WHOSE CHILDREN ARE WIRED BY NEEDS root epic children run in parallel unless a need holds them step .1 design step .2 docs step .3 build gate: human ok step .4 release command or blocks edge gate children of the root epic

Because a molecule’s steps are just beads, they flow through the same ready computation as everything else. Steps with no dependency between them are ready at the same time and can run in parallel. The formula expresses the method once, and each run of it becomes durable, queryable work. Molecules can also be bonded to build a larger workflow out of smaller ones, most often by making one depend on another.

A formula pays off only for work that recurs. For a one-off body of work, an epic with hand-written children is simpler.

Gates Wait on the Outside World

A gate blocks a bead until something outside the graph happens. It appears in the graph like any other blocker, so the waiting step stays off the ready frontier until the gate clears. How it clears depends on the type.

Gate Clears when How it is resolved
Human Someone approves Only by explicit manual resolution
Timer A duration elapses The next gate check after the time passes
GitHub run A CI run passes A gate check that queries GitHub
GitHub PR A pull request merges A gate check that queries GitHub
Bead Another bead closes A gate check for beads in the same database; manual resolution for beads in another project

Gate checks do not run by themselves. The documentation recommends running them on a schedule, from cron, CI, or an orchestrator loop, so timer and GitHub gates close without a person or an agent watching for them.

Wisps Keep Operational Runs Out of the Record

A wisp is an ephemeral molecule, instantiated from a proto with bd mol wisp instead of bd mol pour. Its beads are flagged as ephemeral, hidden from the ready query unless explicitly included, excluded by default from federation (which shares beads between separate databases), and deletable in bulk afterward. Wisps suit routine operational runs, like a diagnostic sweep or a pre-release checklist, whose individual steps have no value once the run is over. If a wisp turns up something the project should keep, squashing it leaves a permanent digest. Burning it deletes it outright.


Wiring Beads Into an Agent

Priming Instead of Tool Schemas

An agent has to know Beads exists and how to use it. Beads recommends a command-line integration over a Model Context Protocol server wherever the agent has a shell. A session-start hook runs bd prime, which injects a short summary of the workflow and the project’s stored memories, and the agent calls bd directly from then on. The Beads Claude Code integration docs estimate this at 1,000 to 2,000 tokens, against 10,000 to 50,000 for MCP tool schemas loaded into every request. The MCP server remains the option for clients with no shell. Because the hook also fires after context compaction, the agent’s workflow knowledge is restored at the moment it would otherwise be lost.

The Session Discipline

Beads only works if the agent treats the graph as the record of work rather than an optional log. The pattern the documentation recommends has three parts:

  • At the start of a session, ask for ready work and claim one bead, rather than choosing work from memory or from a plan file.
  • During the session, file anything discovered as a new bead linked to the current one, and add blocking edges when new work must precede existing work.
  • At the end, close finished beads and push, so the next session, on this machine or another, starts from the true state.

These instructions belong in the project’s agent instructions file (AGENTS.md, CLAUDE.md, or the equivalent), which Beads’ setup commands write for the major coding agents.

Memories and Cleanup

Beads also stores memories, short project facts like “run tests with the race detector” that are injected at prime time, so knowledge that is not tied to any single bead still reaches every session.

A graph that only grows eventually costs space and query time, since every write adds history. Garbage collection deletes old closed beads and squashes old Dolt commits, which also means past states do not stay queryable forever. A separate compaction mode replaces old closed beads with short summaries. The documentation calls it “permanent graceful decay” because the original content leaves the live record, though it can be restored while a pre-compaction snapshot or the Dolt history still holds it.


When Beads Is the Right Tool

Beads earns its setup cost on a long-running body of work whose full scope is not known at the start, and on any backlog that several agents draw from. It adds overhead without benefit when the work is something you could write down completely before beginning it.

The number of sessions is a weak signal on its own. Splitting one task across several sessions is ordinary practice, whether to keep context small or to run costly phases separately, and a plan file carries a task like that well enough, because the list of steps does not move while you work through it. The list moving is what a tracker is for. On a project with open-ended scope, closing one item reveals two more, a bug found in passing has to go somewhere, and the ordering between items keeps shifting as the work teaches you what it actually involves. Maintaining that graph by hand in prose is where plan files come apart.

Situation Fit
A task whose steps you could write down before starting, however many sessions it takes A plan file holds it. The steps do not move while you work
A long-running project with open-ended scope, where finished work keeps producing new work Beads. This is the core case
Several agents drawing from the same backlog Beads on one shared server-mode database
A human team planning in a web UI with dashboards and cross-repo reporting GitHub Issues, Jira, or Linear. Beads can sync with these in both directions when agents also need the work
Repeatable multi-step processes such as releases Beads formulas and molecules

The Beads FAQ says it is usable by people, since the ready query helps anyone managing dependencies, but it is designed for agents that need offline, version-controlled work memory with graph semantics. Every query runs against the local database, so it works without a network connection.

What Adoption Costs

Beads brings Dolt with it, which is a second versioned store to install, sync, back up, and occasionally repair alongside Git. Initializing a project writes configuration under .beads/, adds or updates the agent instructions file, and installs Git hooks by default, all of which land in a shared repository and affect everyone who clones it. For someone who wants Beads on a repository they do not control, the project offers a stealth mode that keeps Beads’ files out of commits and skips the hooks and agent files, and a contributor mode that routes planning into a separate repository, so personal work tracking stays out of the upstream project.


Common Pitfalls

Syncing through the JSONL export. Older tutorials describe committing issues.jsonl to Git as the sync mechanism. Importing it is upsert-only and cannot tell that a bead absent from the file was deleted, so syncing this way leaves beads deleted elsewhere alive in every clone that imports it. Sync through Dolt push and pull.

Using a non-blocking edge where ordering matters. A related or discovered-from edge does not hold work back. If B truly cannot start before A, the edge has to be blocks, or B will appear on the ready frontier early and an agent will start it.

Found this guide helpful? Share it with your team:

Share on LinkedIn