# How Dependencies Are Represented and Managed in Beads: The Complete DAG Edge Implementation

> Learn how Beads represents and manages dependencies using directed acyclic graphs with first class directed edges in transactional storage for cycle detection and ready-work semantics.

- Repository: [Gas Town Hall/beads](https://github.com/gastownhall/beads)
- Tags: internals
- Published: 2026-04-27

---

**Dependencies in Beads are modeled as first-class directed edges in a DAG (directed acyclic graph), implemented via the `Dependency` struct in [`internal/types/types.go`](https://github.com/gastownhall/beads/blob/main/internal/types/types.go) and managed through transactional storage operations that enforce cycle detection and ready-work semantics.**

The gastownhall/beads issue tracker treats inter-issue relationships as core graph primitives rather than auxiliary metadata. Every dependency is a typed edge stored in SQL, enabling automatic cycle prevention, dependency-aware ready queues, and rich relationship semantics ranging from simple blocking to conditional gating.

## The Dependency Data Model in internal/types/types.go

At the heart of Beads dependency management lies the `Dependency` struct defined at lines 15-22 of [`internal/types/types.go`](https://github.com/gastownhall/beads/blob/main/internal/types/types.go).

### Core Struct Fields

The struct captures the minimal data required to define a graph edge between two issues:

```go
type Dependency struct {
    IssueID     string         `json:"issue_id"`      // source issue (the blocker)
    DependsOnID string         `json:"depends_on_id"` // target issue (the blocked)
    Type        DependencyType `json:"type"`          // edge semantics
    CreatedAt   time.Time      `json:"created_at"`
    CreatedBy   string         `json:"created_by,omitempty"`
    Metadata    string         `json:"metadata,omitempty"`
    ThreadID    string         `json:"thread_id,omitempty"`
}

```

This design supports both blocking workflows and conversational threading through the `ThreadID` field, while the `Metadata` column stores JSON for extended gate logic.

### Dependency Type Constants and Validation

Relationship semantics are defined through the `DependencyType` string type. The source code at lines 71-84 of [`internal/types/types.go`](https://github.com/gastownhall/beads/blob/main/internal/types/types.go) declares well-known constants:

```go
const (
    DepBlocks            DependencyType = "blocks"
    DepParentChild       DependencyType = "parent-child"
    DepConditionalBlocks DependencyType = "conditional-blocks"
    DepWaitsFor          DependencyType = "waits-for"
    DepRelated           DependencyType = "related"
    DepDiscoveredFrom    DependencyType = "discovered-from"
    DepRepliesTo         DependencyType = "replies-to"
    DepDuplicates        DependencyType = "duplicates"
)

```

Each type provides three validation methods defined at lines 108-122: `IsValid()` ensures non-empty strings under 50 bytes, `IsWellKnown()` identifies built-in constants, and `AffectsReadyWork()` returns true only for the four blocking types (blocks, parent-child, conditional-blocks, waits-for).

## Storage Layer API and Transactional Safety

All dependency mutations flow through the storage interface defined in [`internal/storage/storage.go`](https://github.com/gastownhall/beads/blob/main/internal/storage/storage.go).

### The Store Interface

The interface at lines 44-52 declares the primary entry points for dependency creation:

```go
type Store interface {
    AddDependency(ctx context.Context, dep *types.Dependency, actor string) error
    AddDependencyWithOptions(ctx context.Context, dep *types.Dependency,
        actor string, opts DependencyAddOptions) error
}

```

### Transactional Implementation in issueops/dependencies.go

Concrete store implementations delegate to `AddDependencyInTx` in [`internal/storage/issueops/dependencies.go`](https://github.com/gastownhall/beads/blob/main/internal/storage/issueops/dependencies.go) (lines 39-61). This helper executes four operations atomically within a SQL transaction: validating the edge (preventing self-dependencies), inserting into the `dependencies` table, updating dependency counters via queries defined at lines 30-44 of [`internal/storage/issueops/dependency_queries.go`](https://github.com/gastownhall/beads/blob/main/internal/storage/issueops/dependency_queries.go), and emitting `EventDependencyAdded` for audit trails.

The function accepts behavioral options defined at lines 13-19:

```go
func AddDependencyInTx(ctx context.Context, tx *sql.Tx,
    dep *types.Dependency, actor string, opts AddDependencyOpts) error

```

## Cycle Detection and High-Level Engine Logic

The tracker engine in [`internal/tracker/engine.go`](https://github.com/gastownhall/beads/blob/main/internal/tracker/engine.go) orchestrates writes with business logic for graph integrity.

When `addDependency` is invoked at lines 1005-1013, it persists the edge through the store, then conditionally runs `detectCycles` only for blocking types like `DepBlocks`. If a cycle is detected, the transaction rolls back, preventing DAG corruption. Finally, the engine invalidates the ready-work cache to ensure queue accuracy.

This cycle detection uses `GetAllDependencyRecordsInTx` to traverse the graph and verify acyclicity before commit.

## Ready-Work Calculation and Blocking Semantics

Not all edges block execution. The `AffectsReadyWork()` method determines whether a dependency type impedes issue readiness. During ready-work calculation, `BuildReadyExplanation` defined at lines 267-285 of [`internal/types/types.go`](https://github.com/gastownhall/beads/blob/main/internal/types/types.go) consults this flag to decide if an issue is blocked.

**Blocking edges** (`blocks`, `parent-child`, `conditional-blocks`, `waits-for`) prevent the dependent issue from entering the ready queue until resolved. **Non-blocking edges** (`related`, `discovered-from`, `replies-to`) maintain graph connectivity for knowledge management without affecting workflow state.

## Working with Dependencies: Code Examples

### Adding a Blocking Dependency Programmatically

To create a standard blocker relationship:

```go
ctx := context.Background()
dep := &types.Dependency{
    IssueID:     "bd-99",
    DependsOnID: "bd-42",
    Type:        types.DepBlocks,
    CreatedAt:   time.Now(),
    CreatedBy:   "alice",
}
if err := store.AddDependency(ctx, dep, "alice"); err != nil {
    log.Fatalf("failed to add dependency: %v", err)
}

```

### Creating Waits-For Gates with Metadata

The `waits-for` type supports sophisticated gating through JSON metadata:

```go
meta, _ := json.Marshal(types.WaitsForMeta{Gate: types.WaitsForAllChildren})
dep := &types.Dependency{
    IssueID:     "gate-1",
    DependsOnID: "task-7",
    Type:        types.DepWaitsFor,
    Metadata:    string(meta),
    CreatedAt:   time.Now(),
}
store.AddDependency(ctx, dep, "system")

```

### CLI Invocation

The `bd` command-line tool documented in [`docs/CLI_REFERENCE.md`](https://github.com/gastownhall/beads/blob/main/docs/CLI_REFERENCE.md) (lines 788-795) exposes the same storage flow:

```bash
bd dep add bd-99 blocks bd-42

```

This parses arguments into a `Dependency` struct and invokes the tracker engine.

## Summary

- **Dependencies in Beads** are modeled as directed edges in a DAG using the `Dependency` struct in [`internal/types/types.go`](https://github.com/gastownhall/beads/blob/main/internal/types/types.go), storing source/target issue IDs and relationship types.
- **Type safety** is enforced through `DependencyType` constants with validation methods `IsValid()`, `IsWellKnown()`, and `AffectsReadyWork()`.
- **Transactional writes** proceed through `Store.AddDependency` and `AddDependencyInTx` in [`internal/storage/issueops/dependencies.go`](https://github.com/gastownhall/beads/blob/main/internal/storage/issueops/dependencies.go), which validate constraints and update counters atomically.
- **Cycle detection** runs automatically for blocking edge types via `detectCycles` in [`internal/tracker/engine.go`](https://github.com/gastownhall/beads/blob/main/internal/tracker/engine.go).
- **Ready-work semantics** depend on `AffectsReadyWork()`, allowing non-blocking edges like `related` to exist without impacting issue readiness.
- **Metadata support** enables advanced features like `waits-for` gates with JSON configuration.

## Frequently Asked Questions

### What data structure does Beads use to store dependencies?

Beads stores dependencies as SQL table rows represented by the `Dependency` struct in [`internal/types/types.go`](https://github.com/gastownhall/beads/blob/main/internal/types/types.go) (lines 15-22). Each row contains `IssueID` (source), `DependsOnID` (target), `Type` (relationship semantics), timestamps, and optional metadata fields.

### How does Beads prevent circular dependencies?

The tracker engine in [`internal/tracker/engine.go`](https://github.com/gastownhall/beads/blob/main/internal/tracker/engine.go) invokes `detectCycles` immediately after inserting blocking edges. It traverses the dependency graph using `GetAllDependencyRecordsInTx` and returns an error if the new edge would create a cycle, causing the SQL transaction to rollback and preserving DAG integrity.

### What is the difference between blocks and waits-for dependency types?

The `blocks` type creates a standard blocking relationship where the source issue must be resolved before the target is considered ready. The `waits-for` type implements gating logic and can store JSON metadata (parsed via `ParseWaitsForGateMetadata`) specifying conditions like `all-children` or `any-children` completion modes for complex workflow control.

### Can I create custom dependency types in Beads?

Yes. The `DependencyType` accepts any non-empty string under 50 bytes. While only built-in constants return true for `IsWellKnown()`, custom types are valid and treated as non-blocking edges that do not affect ready-work calculations unless your application logic specifically recognizes them.