How the Beads Messaging System with Threading Support Works

Beads implements messaging by storing messages as ephemeral issues with IssueType: "message" and models threaded conversations as DepRepliesTo dependency edges, using a thread_id column to group all replies under a single root message for efficient retrieval.

Beads is an open-source issue tracker that unifies work items and conversations through a graph-based dependency model. The messaging system with threading support treats every message as a first-class issue, leveraging the existing dependencies table to link replies without requiring a separate messaging subsystem. This design allows threaded discussions to inherit the same query capabilities, storage optimizations, and integrity checks as traditional blocking dependencies.

Messages as Ephemeral Issues

In Beads, a message is structurally identical to a regular issue. The Issue struct defined in [internal/types/types.go](https://github.com/gastownhall/beads/blob/main/internal/types/types.go#L77-L82) includes an IssueType field that is set to "message" for conversational items.

Key characteristics of message issues include:

  • Ephemeral flag – Messages are typically marked Ephemeral: true to exclude them from graph compaction operations, ensuring conversation history remains accessible while work items are archived.
  • Sender attribution – The Sender field records the user or system agent that created the message, distinct from the standard issue author fields.
  • Unified storage – Because messages live in the same issues table as tasks and bugs, they automatically benefit from versioning, indexing, and the existing storage layer in [internal/storage/issueops/promote.go](https://github.com/gastownhall/beads/blob/main/internal/storage/issueops/promote.go#L56-L57).

Threading via Dependency Edges

Rather than creating a separate threading subsystem, Beads reuses its dependency graph infrastructure to model reply relationships.

The DepRepliesTo Edge Type

Threading is implemented using the DepRepliesTo edge type, defined in [internal/types/types.go](https://github.com/gastownhall/beads/blob/main/internal/types/types.go#L84-L85). This directed edge indicates that one message replies to another, creating a parent-child relationship within a conversation.

Thread Grouping with thread_id

The dependencies table schema includes an optional thread_id column (see the INSERT statements in [internal/storage/issueops/promote.go](https://github.com/gastownhall/beads/blob/main/internal/storage/issueops/promote.go#L56-L57)). When creating a reply:

  1. The IssueID is set to the reply message ID.
  2. The DependsOnID is set to the parent message ID.
  3. The ThreadID is set to the root message ID (the original message in the conversation).

This denormalization allows the system to retrieve an entire conversation with a single indexed lookup on thread_id, avoiding expensive recursive joins.

Creating Message Threads Programmatically

The following pattern, exercised in [cmd/bd/messaging_test.go](https://github.com/gastownhall/beads/blob/main/cmd/bd/messaging_test.go#L50-L57), demonstrates how to create a threaded conversation:

// Create the original message
orig := &types.Issue{
    ID: "msg-thread-orig", Title: "Sprint planning discussion",
    IssueType: "message", Sender: "lead", Ephemeral: true,
    CreatedAt: now, UpdatedAt: now,
}
store.CreateIssue(ctx, orig, "tester")

// Create a reply
reply := &types.Issue{
    ID: "msg-thread-r1", Title: "Re: Sprint planning",
    IssueType: "message", Sender: "worker-1", Ephemeral: true,
    CreatedAt: now.Add(time.Minute), UpdatedAt: now.Add(time.Minute),
}
store.CreateIssue(ctx, reply, "tester")

// Link the reply to the original using DepRepliesTo.
// The ThreadID is set to the root message ID so that all replies share the same thread.
store.AddDependency(ctx, &types.Dependency{
    IssueID:    reply.ID,
    DependsOnID: orig.ID,
    Type:       types.DepRepliesTo,
    ThreadID:   orig.ID,               // <-- groups the conversation
    CreatedAt:  reply.CreatedAt,
}, "tester")

Key implementation details:

  • The ThreadID must always point to the root message, not the immediate parent, to support flat thread retrieval.
  • Both messages use Ephemeral: true to prevent them from being compacted during routine maintenance.

Retrieving and Displaying Threads

The bd show --thread <msg-id> command invokes [cmd/bd/show_thread.go](https://github.com/gastownhall/beads/blob/main/cmd/bd/show_thread.go), which implements a breadth-first traversal algorithm:

  1. Find the root – Walks upward via findRepliesTo (querying DepRepliesTo edges) until reaching a message with no parent.
  2. Collect all replies – Performs a breadth-first search starting at the root, using findReplies to fetch all direct dependents of type DepRepliesTo.
  3. Sort chronologically – Uses slices.SortFunc to order messages by CreatedAt, preserving conversation flow.
  4. Render with indentation – Prints each message with indentation proportional to its depth, showing sender, subject, timestamp, and status icons (📧 for open, ✓ for closed).

The entry point in [cmd/bd/show.go](https://github.com/gastownhall/beads/blob/main/cmd/bd/show.go#L84-L96) handles the --thread flag parsing and delegates to showMessageThread.

Key Functions in show_thread.go

  • showMessageThread – Orchestrates the entire retrieval and rendering flow.
  • findRepliesTo – Returns the parent ID of a given message by querying for DepRepliesTo edges where the message is the dependent.
  • findReplies – Returns all child messages (replies) for a given parent by querying dependents of type DepRepliesTo.

Thread Integrity Validation

Beads validates that every thread_id references an existing issue. The "Mail thread integrity" doctor check in [cmd/bd/doctor/deep.go](https://github.com/gastownhall/beads/blob/main/cmd/bd/doctor/deep.go#L306-L313) scans the dependencies table for thread_id values that point to deleted or non-existent issues, flagging orphaned conversation groups.

This validation ensures that message threads remain traversable even after individual messages are modified or soft-deleted.

Design Benefits of Graph-Based Messaging

The messaging system with threading support leverages the dependency graph for several architectural advantages:

  • Uniform data model – All relationships (blocks, duplicates, replies-to) coexist in the same dependencies table, simplifying queries and migrations.
  • Scalable retrieval – The thread_id column enables O(1) lookups of entire conversations without recursive Common Table Expressions (CTEs).
  • Non-blocking semantics – Because DepRepliesTo is non-blocking, replies never prevent a message from being marked "ready" or "closed," maintaining clean separation between conversation state and work state.
  • Storage efficiency – Messages reuse the same persistence layer as issues, defined in [internal/storage/dolt/dependencies.go](https://github.com/gastownhall/beads/blob/main/internal/storage/dolt/dependencies.go), avoiding duplication of indexing, caching, and replication logic.

Summary

Frequently Asked Questions

How does Beads store messages differently from regular issues?

Messages are stored in the same issues table as bugs and tasks, but are distinguished by IssueType: "message" and typically marked Ephemeral: true. This ephemeral flag prevents them from being compacted during routine maintenance, preserving conversation history while allowing work items to be archived, according to the Issue struct definition in [internal/types/types.go](https://github.com/gastownhall/beads/blob/main/internal/types/types.go#L77-L82).

What is the purpose of the DepRepliesTo edge type?

DepRepliesTo is a dependency edge type defined in [internal/types/types.go](https://github.com/gastownhall/beads/blob/main/internal/types/types.go#L84-L85) that represents a reply relationship between two messages. It creates a directed graph where edges point from replies to their parent messages, enabling both tree-structured threading and flat thread retrieval via the thread_id column.

How does the thread_id column improve query performance?

The thread_id column stores the root message ID for every reply in a conversation. This denormalization allows the system to retrieve all messages in a thread with a single indexed query (SELECT * FROM dependencies WHERE thread_id = ?) rather than executing expensive recursive queries to traverse reply chains, significantly improving performance for long conversation threads.

How can I view a message thread using the Beads CLI?

Use the bd show --thread <message-id> command, which is handled in [cmd/bd/show.go](https://github.com/gastownhall/beads/blob/main/cmd/bd/show.go#L84-L96) and implemented in [cmd/bd/show_thread.go](https://github.com/gastownhall/beads/blob/main/cmd/bd/show_thread.go). This command finds the root message, collects all replies using breadth-first search, sorts them chronologically, and renders the conversation with visual indentation showing sender, timestamp, and subject lines.

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 →