How to Access Beads Storage and Issue Operations Using the Go API

The Beads Go library exposes a Storage interface in internal/storage/storage.go that provides CRUD operations, dependency management, and atomic transactions for issues through methods like CreateIssue, AddDependency, and RunInTransaction.

The gastownhall/beads repository ships a native Go package (github.com/steveyegge/beads) that wraps the underlying Dolt storage layer. You can access Beads storage and issue operations using the Go API to build automation, CI pipelines, or custom workflows that manage issues directly from Go code without invoking the CLI.

Opening a Beads Database Programmatically

Before executing operations, you must locate and open the database. The entry point is beads.go, which provides helper functions to discover the database path and initialize the storage layer.

Use FindDatabasePath() to walk up the directory tree searching for a .beads/*.db file, then pass the result to Open() to obtain a Storage instance:

ctx := context.Background()
dbPath := beads.FindDatabasePath()
if dbPath == "" {
    log.Fatal("Beads DB not found – run `bd init` first")
}
store, err := beads.Open(ctx, dbPath)
if err != nil {
    log.Fatalf("open storage: %v", err)
}
defer store.Close()

The Open function returns a concrete implementation of the Storage interface defined in internal/storage/storage.go. This interface is the sole contract you need to import; it maps every method call directly to SQL operations against the embedded Dolt database.

Creating and Modifying Issues

The Storage interface provides full CRUD capabilities for issues. Key methods include CreateIssue, GetIssue, UpdateIssue, CloseIssue, and DeleteIssue, all of which accept a context.Context and an actor string for auditability.

To create an issue programmatically, populate a beads.Issue struct (defined in internal/types/types.go) and call CreateIssue:

newIssue := &beads.Issue{
    Title:     "Programmatic demo issue",
    Status:    beads.StatusOpen,
    Priority:  2,
    IssueType: beads.TypeTask,
    CreatedAt: time.Now(),
    UpdatedAt: time.Now(),
}
if err := store.CreateIssue(ctx, newIssue, "go-demo"); err != nil {
    log.Fatalf("create issue: %v", err)
}

For bulk imports, use CreateIssues to insert multiple records in a single call, reducing round-trips to the storage layer.

Managing Dependencies and Labels

Issue relationships are handled through the dependency subsystem. The methods AddDependency, RemoveDependency, GetDependencies, and GetDependents let you wire issues together using types like DepBlocks or DepDiscoveredFrom.

dep := &beads.Dependency{
    IssueID:     newIssue.ID,
    DependsOnID: "bd-1",
    Type:        beads.DepDiscoveredFrom,
    CreatedAt:   time.Now(),
    CreatedBy:   "go-demo",
}
if err := store.AddDependency(ctx, dep, "go-demo"); err != nil {
    log.Printf("dependency add failed: %v\n", err)
}

For categorization, AddLabel, RemoveLabel, and GetIssuesByLabel allow you to tag issues and filter collections programmatically.

Executing Atomic Workflows with Transactions

When you need to perform multiple operations atomically—such as creating an issue and immediately adding dependencies—use RunInTransaction. This method accepts a callback that receives a Transaction interface exposing the same methods as Storage, but guarantees that all calls within the callback commit together or rollback on error.

err := store.RunInTransaction(ctx, "create-with-deps", func(tx beads.Transaction) error {
    if err := tx.CreateIssue(ctx, parent, "go-demo"); err != nil {
        return err
    }
    if err := tx.CreateIssue(ctx, child, "go-demo"); err != nil {
        return err
    }
    dep := &beads.Dependency{
        IssueID:     child.ID,
        DependsOnID: parent.ID,
        Type:        beads.DepBlocks,
        CreatedAt:   time.Now(),
        CreatedBy:   "go-demo",
    }
    return tx.AddDependency(ctx, dep, "go-demo")
})

According to the source code in internal/storage/storage.go, the Transaction interface supports CreateIssue, UpdateIssue, AddDependency, and other mutating methods, making it ideal for consistent issue graph updates.

Querying Work Status and Statistics

The API includes specialized query methods for workflow automation. GetReadyWork returns open issues ready for scheduling, while GetBlockedIssues identifies items with unresolved dependencies.

ready, err := store.GetReadyWork(ctx, beads.WorkFilter{
    Status: beads.StatusOpen,
    Limit:  10,
})
if err != nil {
    log.Fatalf("ready work: %v", err)
}
for _, i := range ready {
    fmt.Printf("- %s: %s (prio %d)\n", i.ID, i.Title, i.Priority)
}

For dashboarding or reporting, GetStatistics returns global counts including TotalIssues, OpenIssues, and BlockedIssues without loading full issue records.

Summary

  • Locate and open the database using FindDatabasePath and Open from beads.go to obtain a Storage handle.
  • Perform CRUD operations through the Storage interface methods like CreateIssue, UpdateIssue, and CloseIssue defined in internal/storage/storage.go.
  • Link issues with AddDependency and categorize with AddLabel using types from internal/types/types.go.
  • Ensure atomicity by wrapping multi-step workflows in RunInTransaction to prevent partial updates.
  • Query efficiently using GetReadyWork for scheduling and GetStatistics for metrics.

Frequently Asked Questions

How do I find the Beads database path automatically?

Call beads.FindDatabasePath(), which traverses parent directories looking for a .beads/*.db file. If it returns an empty string, initialize a new database with bd init before attempting to open it.

What is the difference between Storage and Transaction interfaces?

The Storage interface provides standalone methods that auto-commit, while the Transaction interface (accessed via RunInTransaction) groups multiple operations into a single atomic unit. If any step in the transaction callback returns an error, the entire batch rolls back.

Can I create multiple issues at once using the Go API?

Yes. Use the CreateIssues method on the Storage interface to batch insert issues in one call. This is more efficient than calling CreateIssue repeatedly when importing large datasets.

Where are the core data structures like Issue and Dependency defined?

The core types—including Issue, Dependency, Comment, and Statistics—are defined in internal/types/types.go. These structs are used throughout the public API in beads.go and the storage layer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →