| title | Messaging |
|---|
Last verified against code: 2026-03-04
Messaging is a Layer 2-4 derived mechanism that provides inter-agent
communication without introducing new primitives. Mail is composed
from the Bead Store (TaskStore.Create(bead{type:"message"})), and
nudge is composed from the Agent Protocol
(runtime.Provider.Nudge()). No new infrastructure is needed — messaging
is a thin composition layer proving the primitives are sufficient.
-
Mail: A message bead — a bead with
Type="message".Fromis the sender,Assigneeis the recipient,Titleholds the subject line, andDescriptionholds the message body. Open beads without a "read" label are unread; beads with the "read" label are read but still accessible; closed beads are archived. -
Inbox: The set of open, unread message beads assigned to a recipient. Queried by filtering for
Type="message",Status="open",Assignee=recipient, and absence of the "read" label. -
Read vs Archive: Reading a message adds the "read" label but keeps the bead open — the message remains accessible via
GetorThread. Archiving closes the bead permanently. This matches upstream Gastown behavior. -
Threading: Each message gets a
thread:<id>label. Replies inherit the parent's thread ID and add areply-to:<id>label.Thread(threadID)queries all messages sharing a thread. -
Archive: Closing a message bead. Idempotent via
ErrAlreadyArchived. -
Nudge: Text sent directly to an agent's session to wake or redirect it. Delivered via
runtime.Provider.Nudge(). Configured per-agent inAgent.Nudge. Not persisted — fire-and-forget. -
Provider: The pluggable mail backend interface. Two implementations: beadmail (default, backed by
beads.Store) and exec (user-supplied script).
┌─────────────┐
│ gc mail CLI │
└──────┬──────┘
│
┌──────▼──────┐
│ mail.Provider│
└──────┬──────┘
┌──────┴──────┐
┌─────▼─────┐ ┌────▼────┐
│ beadmail │ │ exec │
│ (default) │ │ (script)│
└─────┬──────┘ └─────────┘
│
┌─────▼──────┐
│ beads.Store │
└────────────┘
Sending a message (beadmail path):
gc mail send agent-1 -s "Hello" -m "body text"invokesProvider.Send("sender", "agent-1", "Hello", "body text")- beadmail calls
store.Create(Bead{Title:"Hello", Description:"body text", Type:"message", Assignee:"agent-1", From:"sender", Labels:["thread:<id>"]}) - Store assigns ID, sets Status="open", returns the bead
- beadmail converts to
mail.Messageand returns
Checking inbox:
gc mail inboxinvokesProvider.Inbox("agent-1")- beadmail calls
store.List()and filters forType="message",Status="open",Assignee="agent-1", no "read" label - Returns matching messages as
[]Messagewith Subject, Body, ThreadID, etc.
Reading a message:
Provider.Read(id)retrieves the bead viastore.Get(id)- Adds "read" label via
store.Update(id, UpdateOpts{Labels: ["read"]}) - The bead remains open — still accessible via Get, Thread, Count
- Returns the message
Replying to a message:
Provider.Reply(id, from, subject, body)retrieves the original bead- Inherits the original's
thread:<id>label, addsreply-to:<original-id> - Creates a new message bead addressed to the original sender
mail.Provider— interface with Send, Inbox, Get, Read, MarkRead, MarkUnread, Archive, Delete, Check, Reply, Thread, Count methods. Defined ininternal/mail/mail.go.mail.Message— ID, From, To, Subject, Body, CreatedAt, Read, ThreadID, ReplyTo, Priority, CC. The transport struct returned by all Provider methods.beadmail.Provider— default implementation backed bybeads.Store. Defined ininternal/mail/beadmail/beadmail.go.mail.ErrAlreadyArchived— sentinel error for idempotent archive calls.mail.ErrNotFound— sentinel error for Get/Read of nonexistent messages.
- Messages are beads. Every message has a corresponding bead with
Type="message". No separate message storage exists.Type="message"is the authoritative discriminator — the legacygc:messagelabel is neither written nor read. - Inbox returns only open, unread messages. Read messages (with "read" label) and closed (archived) beads are excluded from inbox.
- Read does not close.
Read(id)adds the "read" label but keeps the bead open. The message remains accessible viaGet,Thread, andCount. OnlyArchive/Deletecloses the bead. - Archive is idempotent. Archiving an already-archived message
returns
ErrAlreadyArchived, not a generic error. - Check and Get do not mutate state. Unlike Read, Check and Get return messages without adding the "read" label.
- Threading is label-based. Each message has a
thread:<id>label. Replies inherit the parent's thread ID.Thread(id)queries by label. - Nudge is fire-and-forget. There is no delivery guarantee, persistence, or retry for nudges. If the session is not running, the nudge is lost.
| Depends on | How |
|---|---|
internal/beads |
beadmail stores messages as beads |
internal/runtime |
Nudge delivered via Provider.Nudge() |
| Depended on by | How |
|---|---|
cmd/gc/cmd_mail.go |
CLI commands: send, inbox, read, peek, reply, archive, delete, mark-read, mark-unread, thread, count |
cmd/gc/cmd_hook.go |
Hook checks for unread mail via Check() |
| Agent prompts | Templates reference gc mail commands |
internal/mail/mail.go— Provider interface, Message struct, ErrAlreadyArchivedinternal/mail/fake.go— test doubleinternal/mail/fake_conformance_test.go— conformance tests for fakesinternal/mail/beadmail/beadmail.go— bead-backed implementationinternal/mail/exec/— script-based mail providerinternal/mail/mailtest/— test helperscmd/gc/cmd_mail.go— CLI commands
[mail]
provider = "beadmail" # default; or "exec" for script-basedThe exec provider runs a user-supplied script for each mail operation, allowing integration with external messaging systems.
internal/mail/fake_conformance_test.go— verifies the fake satisfies the Provider contractinternal/mail/beadmail/— unit tests for bead-backed providertest/integration/mail_test.go— integration tests with real beads
Send → [unread, open]
├── Read → [read label, open] (still in Get/Thread/Count)
│ ├── MarkUnread → [unread, open] (back to inbox)
│ └── Archive → [closed] (permanent)
├── Peek/Get → [unread, open] (no state change)
└── Archive/Delete → [closed] (permanent, skips read)
- beadmail.Inbox scans all beads. Uses
store.List()with client-side filtering. No server-side query for type + status + assignee. Acceptable for current scale. - No delivery confirmation. Neither mail nor nudge provides read receipts or delivery guarantees.
- Bead Store — messages are stored as beads; understanding bead lifecycle explains mail lifecycle
- Agent Protocol — Nudge() delivery mechanism
- Glossary — authoritative definitions of mail, nudge, and related terms