# How the Beads Messaging System with Threading Support Works

> Discover how the Beads messaging system with threading support efficiently handles conversations by storing messages as ephemeral issues and threading replies via dependency edges.

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

---

**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)](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)](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)](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)](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)](https://github.com/gastownhall/beads/blob/main/cmd/bd/messaging_test.go#L50-L57)**, demonstrates how to create a threaded conversation:

```go
// 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)](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)](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)](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)](https://github.com/gastownhall/beads/blob/main/internal/storage/dolt/dependencies.go)**, avoiding duplication of indexing, caching, and replication logic.

## Summary

- **Message storage** – Beads stores messages as ephemeral issues with `IssueType: "message"` in the standard issues table.
- **Thread modeling** – Conversations use `DepRepliesTo` dependency edges, with the `thread_id` column pointing to the root message for efficient grouping.
- **Thread retrieval** – The `bd show --thread` command traverses the dependency graph using breadth-first search in **[[`cmd/bd/show_thread.go`](https://github.com/gastownhall/beads/blob/main/cmd/bd/show_thread.go)](https://github.com/gastownhall/beads/blob/main/cmd/bd/show_thread.go)**.
- **Data integrity** – The doctor command validates thread references in **[[`cmd/bd/doctor/deep.go`](https://github.com/gastownhall/beads/blob/main/cmd/bd/doctor/deep.go)](https://github.com/gastownhall/beads/blob/main/cmd/bd/doctor/deep.go#L306-L313)** to prevent orphaned messages.
- **Unified architecture** – Threading reuses the existing dependency infrastructure, providing first-class messaging without a separate subsystem.

## 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)](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)](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)](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)](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.