beads

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package beads provides a wrapper for the bd (beads) CLI.

Package beads provides audit logging for molecule operations.

Package beads provides a wrapper for the bd (beads) CLI.

Package beads provides molecule catalog support for hierarchical template loading.

Package beads provides field parsing utilities for structured issue descriptions.

Package beads provides handoff bead operations for agent workflow management.

Package beads molecule support - composable workflow templates.

Package beads provides routing helpers for prefix-based beads resolution.

Index

Constants

View Source
const (
	NotifyVerbose = "verbose" // All notifications (mail, convoy events, etc.)
	NotifyNormal  = "normal"  // Important events only (default)
	NotifyMuted   = "muted"   // Silent/DND mode - batch for later
)

Notification level constants

View Source
const RoutesFileName = "routes.jsonl"

RoutesFileName is the name of the routes configuration file.

View Source
const StatusHooked = "hooked"

StatusHooked is the status for beads on an agent's hook (work assignment). This is distinct from pinned - hooked beads are active work, not permanent records.

View Source
const StatusPinned = "pinned"

StatusPinned is the status for pinned beads that never get closed. These are "domain table" beads like role definitions that persist permanently.

View Source
const TownBeadsPrefix = "hq"

TownBeadsPrefix is the prefix used for town-level agent beads stored in ~/gt/.beads/. This distinguishes them from rig-level beads (which use project prefixes like "gt-").

Variables

View Source
var (
	ErrNotInstalled = errors.New("bd not installed: run 'pip install beads-cli' or see https://github.com/steveyegge/beads")
	ErrNotARepo     = errors.New("not a beads repository (no .beads directory found)")
	ErrSyncConflict = errors.New("beads sync conflict")
	ErrNotFound     = errors.New("issue not found")
)

Common errors

Functions

func AgentBeadID

func AgentBeadID(rig, role, name string) string

AgentBeadID generates the canonical agent bead ID using "gt" prefix. For non-gastown rigs, use AgentBeadIDWithPrefix with the rig's configured prefix.

func AgentBeadIDWithPrefix

func AgentBeadIDWithPrefix(prefix, rig, role, name string) string

AgentBeadIDWithPrefix generates an agent bead ID using the specified prefix. The prefix should NOT include the hyphen (e.g., "gt", "bd", not "gt-", "bd-"). For town-level agents (mayor, deacon), pass empty rig and name. For rig-level singletons (witness, refinery), pass empty name. For named agents (crew, polecat), pass all three.

func AppendRoute

func AppendRoute(townRoot string, route Route) error

AppendRoute appends a route to routes.jsonl in the town's beads directory. If the prefix already exists, it updates the path.

func AppendRouteToDir

func AppendRouteToDir(beadsDir string, route Route) error

AppendRouteToDir appends a route to routes.jsonl in the given beads directory. If the prefix already exists, it updates the path.

func CrewBeadID

func CrewBeadID(rig, name string) string

CrewBeadID returns a Crew worker agent bead ID using "gt" prefix.

func CrewBeadIDWithPrefix

func CrewBeadIDWithPrefix(prefix, rig, name string) string

CrewBeadIDWithPrefix returns a Crew worker agent bead ID using the specified prefix.

func CrewRoleBeadID

func CrewRoleBeadID() string

CrewRoleBeadID returns the Crew role bead ID.

func CrewRoleBeadIDTown

func CrewRoleBeadIDTown() string

CrewRoleBeadIDTown returns the Crew role bead ID for town-level storage.

func DeaconBeadID deprecated

func DeaconBeadID() string

DeaconBeadID returns the Deacon agent bead ID.

Deprecated: Use DeaconBeadIDTown() for town-level beads (hq- prefix). This function returns "gt-deacon" which is for rig-level storage. Town-level agents like Deacon should use the hq- prefix.

func DeaconBeadIDTown

func DeaconBeadIDTown() string

DeaconBeadIDTown returns the Deacon agent bead ID for town-level beads. This uses the "hq-" prefix for town-level storage.

func DeaconRoleBeadID

func DeaconRoleBeadID() string

DeaconRoleBeadID returns the Deacon role bead ID.

func DeaconRoleBeadIDTown

func DeaconRoleBeadIDTown() string

DeaconRoleBeadIDTown returns the Deacon role bead ID for town-level storage.

func DogBeadID

func DogBeadID(name string) string

DogBeadID returns a Dog agent bead ID. Dogs are town-level agents, so they follow the pattern: gt-dog-<name> Deprecated: Use DogBeadIDTown() for town-level beads with hq- prefix. Dogs are town-level agents and should use hq-dog-<name>, not gt-dog-<name>.

func DogBeadIDTown

func DogBeadIDTown(name string) string

DogBeadIDTown returns a Dog agent bead ID for town-level beads. Dogs are town-level agents, so they follow the pattern: hq-dog-<name>

func DogRoleBeadID

func DogRoleBeadID() string

DogRoleBeadID returns the Dog role bead ID.

func DogRoleBeadIDTown

func DogRoleBeadIDTown() string

DogRoleBeadIDTown returns the Dog role bead ID for town-level storage.

func EnsureBdDaemonHealth

func EnsureBdDaemonHealth(workDir string) string

EnsureBdDaemonHealth checks if bd daemons are healthy and attempts to restart if needed. Returns a warning message if there were issues, or empty string if everything is fine. This is non-blocking - it will not fail if daemons can't be started.

func ExpandRolePattern

func ExpandRolePattern(pattern, townRoot, rig, name, role string) string

ExpandRolePattern expands placeholders in a pattern string. Supported placeholders: {town}, {rig}, {name}, {role}

func ExpandTemplateVars

func ExpandTemplateVars(text string, ctx map[string]string) string

ExpandTemplateVars replaces {{variable}} placeholders in text using the provided context map. Unknown variables are left as-is.

func ExtractPrefix

func ExtractPrefix(beadID string) string

ExtractPrefix extracts the prefix from a bead ID. For example, "ap-qtsup.16" returns "ap-", "hq-cv-abc" returns "hq-". Returns empty string if no valid prefix found (empty input, no hyphen, or hyphen at position 0 which would indicate an invalid prefix).

func FindConflictingPrefixes

func FindConflictingPrefixes(beadsDir string) (map[string][]string, error)

FindConflictingPrefixes checks for duplicate prefixes in routes. Returns a map of prefix -> list of paths that use it.

func FormatAgentDescription

func FormatAgentDescription(title string, fields *AgentFields) string

FormatAgentDescription creates a description string from agent fields.

func FormatAttachmentFields

func FormatAttachmentFields(fields *AttachmentFields) string

FormatAttachmentFields formats AttachmentFields as a string suitable for an issue description. Only non-empty fields are included.

func FormatMRFields

func FormatMRFields(fields *MRFields) string

FormatMRFields formats MRFields as a string suitable for an issue description. Only non-empty fields are included.

func FormatRigDescription

func FormatRigDescription(name string, fields *RigFields) string

FormatRigDescription formats the description field for a rig identity bead.

func FormatRoleConfig

func FormatRoleConfig(config *RoleConfig) string

FormatRoleConfig formats RoleConfig as a string suitable for a role bead description. Only non-empty/non-default fields are included.

func FormatSynthesisFields

func FormatSynthesisFields(fields *SynthesisFields) string

FormatSynthesisFields formats SynthesisFields as a string for issue description.

func GetPrefixForRig

func GetPrefixForRig(townRoot, rigName string) string

GetPrefixForRig returns the beads prefix for a given rig name. The prefix is returned without the trailing hyphen (e.g., "bd" not "bd-"). If the rig is not found in routes, returns "gt" as the default. The townRoot should be the Gas Town root directory (e.g., ~/gt).

func GetRigPathForPrefix

func GetRigPathForPrefix(townRoot, prefix string) string

GetRigPathForPrefix returns the rig path for a given bead ID prefix. The townRoot should be the Gas Town root directory (e.g., ~/gt). Returns the full absolute path to the rig directory, or empty string if not found. For town-level beads (path="."), returns townRoot.

func GetTownBeadsPath

func GetTownBeadsPath(townRoot string) string

GetTownBeadsPath returns the path to town-level beads directory. Town beads store hq-* prefixed issues including Mayor, Deacon, and role beads. The townRoot should be the Gas Town root directory (e.g., ~/gt).

func HandoffBeadTitle

func HandoffBeadTitle(role string) string

HandoffBeadTitle returns the well-known title for a role's handoff bead.

func IsAgentSessionBead

func IsAgentSessionBead(beadID string) bool

IsAgentSessionBead returns true if the bead ID represents an agent session molecule. Agent session beads follow patterns like gt-mayor, bd-beads-witness, gt-gastown-crew-joe. Supports any valid prefix (e.g., "gt-", "bd-"), not just "gt-". These are used to track agent state and update frequently, which can create noise.

func MayorBeadID deprecated

func MayorBeadID() string

MayorBeadID returns the Mayor agent bead ID.

Deprecated: Use MayorBeadIDTown() for town-level beads (hq- prefix). This function returns "gt-mayor" which is for rig-level storage. Town-level agents like Mayor should use the hq- prefix.

func MayorBeadIDTown

func MayorBeadIDTown() string

MayorBeadIDTown returns the Mayor agent bead ID for town-level beads. This uses the "hq-" prefix for town-level storage.

func MayorRoleBeadID

func MayorRoleBeadID() string

MayorRoleBeadID returns the Mayor role bead ID.

func MayorRoleBeadIDTown

func MayorRoleBeadIDTown() string

MayorRoleBeadIDTown returns the Mayor role bead ID for town-level storage.

func ParseAgentBeadID

func ParseAgentBeadID(id string) (rig, role, name string, ok bool)

ParseAgentBeadID parses an agent bead ID into its components. Returns rig, role, name, and whether parsing succeeded. For town-level agents, rig will be empty. For singletons, name will be empty. Accepts any valid prefix (e.g., "gt-", "bd-"), not just "gt-".

func PolecatBeadID

func PolecatBeadID(rig, name string) string

PolecatBeadID returns a Polecat agent bead ID using "gt" prefix.

func PolecatBeadIDWithPrefix

func PolecatBeadIDWithPrefix(prefix, rig, name string) string

PolecatBeadIDWithPrefix returns a Polecat agent bead ID using the specified prefix.

func PolecatRoleBeadID

func PolecatRoleBeadID() string

PolecatRoleBeadID returns the Polecat role bead ID.

func PolecatRoleBeadIDTown

func PolecatRoleBeadIDTown() string

PolecatRoleBeadIDTown returns the Polecat role bead ID for town-level storage.

func RefineryBeadID

func RefineryBeadID(rig string) string

RefineryBeadID returns the Refinery agent bead ID for a rig using "gt" prefix.

func RefineryBeadIDWithPrefix

func RefineryBeadIDWithPrefix(prefix, rig string) string

RefineryBeadIDWithPrefix returns the Refinery agent bead ID for a rig using the specified prefix.

func RefineryRoleBeadID

func RefineryRoleBeadID() string

RefineryRoleBeadID returns the Refinery role bead ID.

func RefineryRoleBeadIDTown

func RefineryRoleBeadIDTown() string

RefineryRoleBeadIDTown returns the Refinery role bead ID for town-level storage.

func RemoveRoute

func RemoveRoute(townRoot string, prefix string) error

RemoveRoute removes a route by prefix from routes.jsonl.

func ResolveBeadsDir

func ResolveBeadsDir(workDir string) string

ResolveBeadsDir returns the actual beads directory, following any redirect. If workDir/.beads/redirect exists, it reads the redirect path and resolves it relative to workDir (not the .beads directory). Otherwise, returns workDir/.beads.

This is essential for crew workers and polecats that use shared beads via redirect. The redirect file contains a relative path like "../../mayor/rig/.beads".

Example: if we're at crew/max/ and .beads/redirect contains "../../mayor/rig/.beads", the redirect is resolved from crew/max/ (not crew/max/.beads/), giving us mayor/rig/.beads at the rig root level.

Circular redirect detection: If the resolved path equals the original beads directory, this indicates an errant redirect file that should be removed. The function logs a warning and returns the original beads directory.

func ResolveHookDir

func ResolveHookDir(townRoot, beadID, hookWorkDir string) string

ResolveHookDir determines the directory for running bd update on a bead. Since bd update doesn't support routing or redirects, we must resolve the actual rig directory from the bead's prefix. hookWorkDir is only used as a fallback if prefix resolution fails.

func RigBeadID

func RigBeadID(name string) string

RigBeadID generates a rig identity bead ID using "gt" prefix. For non-gastown rigs, use RigBeadIDWithPrefix with the rig's configured prefix.

func RigBeadIDWithPrefix

func RigBeadIDWithPrefix(prefix, name string) string

RigBeadIDWithPrefix generates a rig identity bead ID using the specified prefix. Format: <prefix>-rig-<name> (e.g., gt-rig-gastown)

func RoleBeadID

func RoleBeadID(roleType string) string

RoleBeadID returns the role bead ID for a given role type. Role beads define lifecycle configuration for each agent type. Deprecated: Use RoleBeadIDTown() for town-level beads with hq- prefix. Role beads are global templates and should use hq-<role>-role, not gt-<role>-role.

func RoleBeadIDTown

func RoleBeadIDTown(role string) string

RoleBeadIDTown returns the role bead ID for town-level storage. Role beads define lifecycle configuration for each agent type. Uses "hq-" prefix for town-level storage: hq-<role>-role

func SetAttachmentFields

func SetAttachmentFields(issue *Issue, fields *AttachmentFields) string

SetAttachmentFields updates an issue's description with the given attachment fields. Existing attachment field lines are replaced; other content is preserved. Returns the new description string.

func SetMRFields

func SetMRFields(issue *Issue, fields *MRFields) string

SetMRFields updates an issue's description with the given MR fields. Existing MR field lines are replaced; other content is preserved. Returns the new description string.

func SetupRedirect

func SetupRedirect(townRoot, worktreePath string) error

SetupRedirect creates a .beads/redirect file for a worktree to point to the rig's shared beads. This is used by crew, polecats, and refinery worktrees to share the rig's beads database.

Parameters:

  • townRoot: the town root directory (e.g., ~/gt)
  • worktreePath: the worktree directory (e.g., <rig>/crew/<name> or <rig>/refinery/rig)

The function:

  1. Computes the relative path from worktree to rig-level .beads
  2. Cleans up runtime files (preserving tracked files like formulas/)
  3. Creates the redirect file

Safety: This function refuses to create redirects in the canonical beads location (mayor/rig) to prevent circular redirect chains.

func StartBdDaemonIfNeeded

func StartBdDaemonIfNeeded(workDir string) error

StartBdDaemonIfNeeded starts the bd daemon for a specific workspace if not running. This is a best-effort operation - failures are logged but don't block execution.

func ValidateMolecule

func ValidateMolecule(mol *Issue) error

ValidateMolecule checks if an issue is a valid molecule definition. Returns an error describing the problem, or nil if valid.

Note: This function only validates the old format (embedded markdown steps). For new format molecules (with child issues), validation is implicit during instantiation - if the molecule has children, those are used as templates. Use InstantiateMolecule directly for new format molecules; this function will report "no steps defined" for new format molecules since it cannot access child issues without a Beads client.

func WitnessBeadID

func WitnessBeadID(rig string) string

WitnessBeadID returns the Witness agent bead ID for a rig using "gt" prefix.

func WitnessBeadIDWithPrefix

func WitnessBeadIDWithPrefix(prefix, rig string) string

WitnessBeadIDWithPrefix returns the Witness agent bead ID for a rig using the specified prefix.

func WitnessRoleBeadID

func WitnessRoleBeadID() string

WitnessRoleBeadID returns the Witness role bead ID.

func WitnessRoleBeadIDTown

func WitnessRoleBeadIDTown() string

WitnessRoleBeadIDTown returns the Witness role bead ID for town-level storage.

func WriteRoutes

func WriteRoutes(beadsDir string, routes []Route) error

WriteRoutes writes routes to routes.jsonl, overwriting existing content.

Types

type AgentFields

type AgentFields struct {
	RoleType          string // polecat, witness, refinery, deacon, mayor
	Rig               string // Rig name (empty for global agents like mayor/deacon)
	AgentState        string // spawning, working, done, stuck
	HookBead          string // Currently pinned work bead ID
	RoleBead          string // Role definition bead ID (canonical location; may not exist yet)
	CleanupStatus     string // ZFC: polecat self-reports git state (clean, has_uncommitted, has_stash, has_unpushed)
	ActiveMR          string // Currently active merge request bead ID (for traceability)
	NotificationLevel string // DND mode: verbose, normal, muted (default: normal)
}

AgentFields holds structured fields for agent beads. These are stored as "key: value" lines in the description.

func ParseAgentFields

func ParseAgentFields(description string) *AgentFields

ParseAgentFields extracts agent fields from an issue's description.

func ParseAgentFieldsFromDescription

func ParseAgentFieldsFromDescription(description string) *AgentFields

ParseAgentFieldsFromDescription is an alias for ParseAgentFields. Used by daemon for compatibility.

type AttachmentFields

type AttachmentFields struct {
	AttachedMolecule string // Root issue ID of the attached molecule
	AttachedAt       string // ISO 8601 timestamp when attached
	AttachedArgs     string // Natural language args passed via gt sling --args (no-tmux mode)
	DispatchedBy     string // Agent ID that dispatched this work (for completion notification)
}

AttachmentFields holds the attachment info for pinned beads. These fields track which molecule is attached to a handoff/pinned bead.

func ParseAttachmentFields

func ParseAttachmentFields(issue *Issue) *AttachmentFields

ParseAttachmentFields extracts attachment fields from an issue's description. Fields are expected as "key: value" lines. Returns nil if no attachment fields found.

type BackoffConfig

type BackoffConfig struct {
	Base       string // Base interval (e.g., "30s")
	Multiplier int    // Multiplier for exponential growth (default: 2)
	Max        string // Maximum interval cap (e.g., "10m")
}

BackoffConfig defines exponential backoff parameters for wait-type steps. Used by patrol agents to implement cost-saving await-signal patterns.

type BdDaemonHealth

type BdDaemonHealth struct {
	Total        int            `json:"total"`
	Healthy      int            `json:"healthy"`
	Stale        int            `json:"stale"`
	Mismatched   int            `json:"mismatched"`
	Unresponsive int            `json:"unresponsive"`
	Daemons      []BdDaemonInfo `json:"daemons"`
}

BdDaemonHealth represents the overall health of bd daemons.

func CheckBdDaemonHealth

func CheckBdDaemonHealth() (*BdDaemonHealth, error)

CheckBdDaemonHealth checks the health of all bd daemons. Returns nil if no daemons are running (which is fine, bd will use direct mode).

type BdDaemonInfo

type BdDaemonInfo struct {
	Workspace       string `json:"workspace"`
	SocketPath      string `json:"socket_path"`
	PID             int    `json:"pid"`
	Version         string `json:"version"`
	Status          string `json:"status"`
	Issue           string `json:"issue,omitempty"`
	VersionMismatch bool   `json:"version_mismatch,omitempty"`
}

BdDaemonInfo represents the status of a single bd daemon instance.

type Beads

type Beads struct {
	// contains filtered or unexported fields
}

Beads wraps bd CLI operations for a working directory.

func New

func New(workDir string) *Beads

New creates a new Beads wrapper for the given directory.

func NewWithBeadsDir

func NewWithBeadsDir(workDir, beadsDir string) *Beads

NewWithBeadsDir creates a Beads wrapper with an explicit BEADS_DIR. This is needed when running from a polecat worktree but accessing town-level beads.

func (*Beads) AddDelegation

func (b *Beads) AddDelegation(d *Delegation) error

AddDelegation creates a delegation relationship from parent to child work unit. The delegation tracks who delegated (delegatedBy) and who received (delegatedTo), along with optional terms. Delegations enable credit cascade - when child work is completed, credit flows up to the parent work unit and its delegator.

Note: This is stored as metadata on the child issue until bd CLI has native delegation support. Once bd supports `bd delegate add`, this will be updated.

func (*Beads) AddDependency

func (b *Beads) AddDependency(issue, dependsOn string) error

AddDependency adds a dependency: issue depends on dependsOn.

func (*Beads) AddGateWaiter

func (b *Beads) AddGateWaiter(gateID, waiter string) error

AddGateWaiter registers an agent as a waiter on a gate bead. When the gate closes, the waiter will receive a wake notification via gt gate wake. The waiter is typically the polecat's address (e.g., "gastown/polecats/Toast").

func (*Beads) AttachMolecule

func (b *Beads) AttachMolecule(pinnedBeadID, moleculeID string) (*Issue, error)

AttachMolecule attaches a molecule to a pinned bead by updating its description. The moleculeID is the root issue ID of the molecule to attach. Returns the updated issue.

func (*Beads) Blocked

func (b *Beads) Blocked() ([]*Issue, error)

Blocked returns issues that are blocked by dependencies.

func (*Beads) ClearHandoffContent

func (b *Beads) ClearHandoffContent(role string) error

ClearHandoffContent clears the handoff bead's description.

func (*Beads) ClearHookBead

func (b *Beads) ClearHookBead(agentBeadID string) error

ClearHookBead clears the hook_bead slot on an agent bead. Used when work is complete or unslung.

func (*Beads) ClearMail

func (b *Beads) ClearMail(reason string) (*ClearMailResult, error)

ClearMail closes or clears all open messages. Non-pinned messages are closed with the given reason. Pinned messages have their description cleared but remain open.

func (*Beads) Close

func (b *Beads) Close(ids ...string) error

Close closes one or more issues. If CURSOR_SESSION_ID is set in the environment, it is passed to bd close for work attribution tracking (see decision 009-session-events-architecture.md).

func (*Beads) CloseWithReason

func (b *Beads) CloseWithReason(reason string, ids ...string) error

CloseWithReason closes one or more issues with a reason. If CURSOR_SESSION_ID is set in the environment, it is passed to bd close for work attribution tracking (see decision 009-session-events-architecture.md).

func (*Beads) Create

func (b *Beads) Create(opts CreateOptions) (*Issue, error)

Create creates a new issue and returns it. If opts.Actor is empty, it defaults to the BD_ACTOR environment variable. This ensures created_by is populated for issue provenance tracking.

func (*Beads) CreateAgentBead

func (b *Beads) CreateAgentBead(id, title string, fields *AgentFields) (*Issue, error)

CreateAgentBead creates an agent bead for tracking agent lifecycle. The ID format is: <prefix>-<rig>-<role>-<name> (e.g., gt-gastown-polecat-Toast) Use AgentBeadID() helper to generate correct IDs. The created_by field is populated from BD_ACTOR env var for provenance tracking.

func (*Beads) CreateDogAgentBead

func (b *Beads) CreateDogAgentBead(name, location string) (*Issue, error)

CreateDogAgentBead creates an agent bead for a dog. Dogs use a different schema than other agents - they use labels for metadata. Returns the created issue or an error.

func (*Beads) CreateRigBead

func (b *Beads) CreateRigBead(id, title string, fields *RigFields) (*Issue, error)

CreateRigBead creates a rig identity bead for tracking rig metadata. The ID format is: <prefix>-rig-<name> (e.g., gt-rig-gastown) Use RigBeadID() helper to generate correct IDs. The created_by field is populated from BD_ACTOR env var for provenance tracking.

func (*Beads) CreateWithID

func (b *Beads) CreateWithID(id string, opts CreateOptions) (*Issue, error)

CreateWithID creates an issue with a specific ID. This is useful for agent beads, role beads, and other beads that need deterministic IDs rather than auto-generated ones.

func (*Beads) DeleteAgentBead

func (b *Beads) DeleteAgentBead(id string) error

DeleteAgentBead permanently deletes an agent bead. Uses --hard --force for immediate permanent deletion (no tombstone).

func (*Beads) DeleteDogAgentBead

func (b *Beads) DeleteDogAgentBead(name string) error

DeleteDogAgentBead finds and deletes the agent bead for a dog. Returns nil if the bead doesn't exist (idempotent).

func (*Beads) DetachMolecule

func (b *Beads) DetachMolecule(pinnedBeadID string) (*Issue, error)

DetachMolecule removes molecule attachment from a pinned bead. Returns the updated issue.

func (*Beads) DetachMoleculeWithAudit

func (b *Beads) DetachMoleculeWithAudit(pinnedBeadID string, opts DetachOptions) (*Issue, error)

DetachMoleculeWithAudit removes molecule attachment from a pinned bead and logs the operation. Returns the updated issue.

func (*Beads) FindDogAgentBead

func (b *Beads) FindDogAgentBead(name string) (*Issue, error)

FindDogAgentBead finds the agent bead for a dog by name. Searches for agent beads with role_type:dog and matching title. Returns nil if not found.

func (*Beads) FindHandoffBead

func (b *Beads) FindHandoffBead(role string) (*Issue, error)

FindHandoffBead finds the pinned handoff bead for a role by title. Returns nil if not found (not an error).

func (*Beads) FindMRForBranch

func (b *Beads) FindMRForBranch(branch string) (*Issue, error)

FindMRForBranch searches for an existing merge-request bead for the given branch. Returns the MR bead if found, nil if not found. This enables idempotent `gt done` - if an MR already exists, we skip creation.

func (*Beads) GetAgentBead

func (b *Beads) GetAgentBead(id string) (*Issue, *AgentFields, error)

GetAgentBead retrieves an agent bead by ID. Returns nil if not found.

func (*Beads) GetAgentNotificationLevel

func (b *Beads) GetAgentNotificationLevel(id string) (string, error)

GetAgentNotificationLevel returns the notification level for an agent. Returns "normal" if not set (the default).

func (*Beads) GetAssignedIssue

func (b *Beads) GetAssignedIssue(assignee string) (*Issue, error)

GetAssignedIssue returns the first open issue assigned to the given assignee. Returns nil if no open issue is assigned.

func (*Beads) GetAttachment

func (b *Beads) GetAttachment(pinnedBeadID string) (*AttachmentFields, error)

GetAttachment returns the attachment fields from a pinned bead. Returns nil if no molecule is attached.

func (*Beads) GetDelegation

func (b *Beads) GetDelegation(child string) (*Delegation, error)

GetDelegation retrieves the delegation information for a child work unit. Returns nil if the issue has no delegation.

func (*Beads) GetOrCreateHandoffBead

func (b *Beads) GetOrCreateHandoffBead(role string) (*Issue, error)

GetOrCreateHandoffBead returns the handoff bead for a role, creating it if needed.

func (*Beads) GetRoleConfig

func (b *Beads) GetRoleConfig(roleBeadID string) (*RoleConfig, error)

GetRoleConfig looks up a role bead and returns its parsed RoleConfig. Returns nil, nil if the role bead doesn't exist or has no config.

func (*Beads) InstantiateMolecule

func (b *Beads) InstantiateMolecule(mol *Issue, parent *Issue, opts InstantiateOptions) ([]*Issue, error)

InstantiateMolecule creates child issues from a molecule template.

This function supports two molecule formats (format bridge pattern):

  1. New format (child issues): If the molecule proto has child issues, those children are used as templates. Dependencies are copied from the template children's DependsOn relationships.
  1. Old format (embedded markdown): If the molecule has no children, steps are parsed from the Description field using ParseMoleculeSteps(). Dependencies are extracted from "Needs:" declarations in the markdown.

For each step, this creates:

  • A child issue with ID "{parent.ID}.{step.Ref}"
  • Title from step title
  • Description from step instructions (with template vars expanded)
  • Type: task
  • Priority: inherited from parent
  • Dependencies wired according to template

The function is atomic via bd CLI - either all issues are created or none. Returns the created step issues.

func (*Beads) IsBeadsRepo

func (b *Beads) IsBeadsRepo() bool

IsBeadsRepo checks if the working directory is a beads repository.

func (*Beads) List

func (b *Beads) List(opts ListOptions) ([]*Issue, error)

List returns issues matching the given options.

func (*Beads) ListAgentBeads

func (b *Beads) ListAgentBeads() (map[string]*Issue, error)

ListAgentBeads returns all agent beads in a single query. Returns a map of agent bead ID to Issue.

func (*Beads) ListByAssignee

func (b *Beads) ListByAssignee(assignee string) ([]*Issue, error)

ListByAssignee returns all issues assigned to a specific assignee. The assignee is typically in the format "rig/polecatName" (e.g., "gastown/Toast").

func (*Beads) ListDelegationsFrom

func (b *Beads) ListDelegationsFrom(parent string) ([]*Delegation, error)

ListDelegationsFrom returns all delegations from a parent work unit. This searches for issues that have delegated_from pointing to the parent.

func (*Beads) LogDetachAudit

func (b *Beads) LogDetachAudit(entry DetachAuditEntry) error

LogDetachAudit appends an audit entry to the audit log file. The audit log is stored in .beads/audit.log as JSONL format.

func (*Beads) MergeSlotAcquire

func (b *Beads) MergeSlotAcquire(holder string, addWaiter bool) (*MergeSlotStatus, error)

MergeSlotAcquire attempts to acquire the merge slot for exclusive access. If holder is empty, defaults to BD_ACTOR environment variable. If addWaiter is true and the slot is held, the requester is added to the waiters queue. Returns the acquisition result.

func (*Beads) MergeSlotCheck

func (b *Beads) MergeSlotCheck() (*MergeSlotStatus, error)

MergeSlotCheck checks the availability of the merge slot. Returns the current status including holder and waiters if held.

func (*Beads) MergeSlotCreate

func (b *Beads) MergeSlotCreate() (string, error)

MergeSlotCreate creates the merge slot bead for the current rig. The slot is used for serialized conflict resolution in the merge queue. Returns the slot ID if successful.

func (*Beads) MergeSlotEnsureExists

func (b *Beads) MergeSlotEnsureExists() (string, error)

MergeSlotEnsureExists creates the merge slot if it doesn't exist. This is idempotent - safe to call multiple times.

func (*Beads) MergeSlotRelease

func (b *Beads) MergeSlotRelease(holder string) error

MergeSlotRelease releases the merge slot after conflict resolution completes. If holder is provided, it verifies the slot is held by that holder before releasing.

func (*Beads) Ready

func (b *Beads) Ready() ([]*Issue, error)

Ready returns issues that are ready to work (not blocked).

func (*Beads) ReadyWithType

func (b *Beads) ReadyWithType(issueType string) ([]*Issue, error)

ReadyWithType returns ready issues filtered by type. Uses bd ready --type flag for server-side filtering.

func (*Beads) Release

func (b *Beads) Release(id string) error

Release moves an in_progress issue back to open status. This is used to recover stuck steps when a worker dies mid-task. It clears the assignee so the step can be claimed by another worker.

func (*Beads) ReleaseWithReason

func (b *Beads) ReleaseWithReason(id, reason string) error

ReleaseWithReason moves an in_progress issue back to open status with a reason. The reason is added as a note to the issue for tracking purposes.

func (*Beads) RemoveDelegation

func (b *Beads) RemoveDelegation(parent, child string) error

RemoveDelegation removes a delegation relationship.

func (*Beads) RemoveDependency

func (b *Beads) RemoveDependency(issue, dependsOn string) error

RemoveDependency removes a dependency.

func (*Beads) Run

func (b *Beads) Run(args ...string) ([]byte, error)

Run executes a bd command and returns stdout. This is a public wrapper around the internal run method for cases where callers need to run arbitrary bd commands.

func (*Beads) SetHookBead

func (b *Beads) SetHookBead(agentBeadID, hookBeadID string) error

SetHookBead sets the hook_bead slot on an agent bead. This is a convenience wrapper that only sets the hook without changing agent_state. Per gt-zecmc: agent_state ("running", "dead", "idle") is observable from tmux and should not be recorded in beads ("discover, don't track" principle).

func (*Beads) Show

func (b *Beads) Show(id string) (*Issue, error)

Show returns detailed information about an issue.

func (*Beads) ShowMultiple

func (b *Beads) ShowMultiple(ids []string) (map[string]*Issue, error)

ShowMultiple fetches multiple issues by ID in a single bd call. Returns a map of ID to Issue. Missing IDs are not included in the map.

func (*Beads) Stats

func (b *Beads) Stats() (string, error)

Stats returns repository statistics.

func (*Beads) Sync

func (b *Beads) Sync() error

Sync syncs beads with remote.

func (*Beads) SyncFromMain

func (b *Beads) SyncFromMain() error

SyncFromMain syncs beads updates from main branch.

func (*Beads) SyncStatus

func (b *Beads) SyncStatus() (*SyncStatus, error)

SyncStatus returns the sync status without performing a sync.

func (*Beads) Update

func (b *Beads) Update(id string, opts UpdateOptions) error

Update updates an existing issue.

func (*Beads) UpdateAgentActiveMR

func (b *Beads) UpdateAgentActiveMR(id string, activeMR string) error

UpdateAgentActiveMR updates the active_mr field in an agent bead. This links the agent to their current merge request for traceability. Pass empty string to clear the field (e.g., after merge completes).

func (*Beads) UpdateAgentCleanupStatus

func (b *Beads) UpdateAgentCleanupStatus(id string, cleanupStatus string) error

UpdateAgentCleanupStatus updates the cleanup_status field in an agent bead. This is called by the polecat to self-report its git state (ZFC compliance). Valid statuses: clean, has_uncommitted, has_stash, has_unpushed

func (*Beads) UpdateAgentNotificationLevel

func (b *Beads) UpdateAgentNotificationLevel(id string, level string) error

UpdateAgentNotificationLevel updates the notification_level field in an agent bead. Valid levels: verbose, normal, muted (DND mode). Pass empty string to reset to default (normal).

func (*Beads) UpdateAgentState

func (b *Beads) UpdateAgentState(id string, state string, hookBead *string) error

UpdateAgentState updates the agent_state field in an agent bead. Optionally updates hook_bead if provided.

IMPORTANT: This function uses the proper bd commands to update agent fields: - `bd agent state` for agent_state (uses SQLite column directly) - `bd slot set/clear` for hook_bead (uses SQLite column directly)

This ensures consistency with `bd slot show` and other beads commands. Previously, this function embedded these fields in the description text, which caused inconsistencies with bd slot commands (see GH #gt-9v52).

func (*Beads) UpdateHandoffContent

func (b *Beads) UpdateHandoffContent(role, content string) error

UpdateHandoffContent updates the handoff bead's description with new content.

type CatalogMolecule

type CatalogMolecule struct {
	ID          string `json:"id"`
	Title       string `json:"title"`
	Description string `json:"description"`
	Source      string `json:"source,omitempty"` // "town", "rig", "project"
}

CatalogMolecule represents a molecule template in the catalog. Unlike regular issues, catalog molecules are read-only templates.

func (*CatalogMolecule) ToIssue

func (mol *CatalogMolecule) ToIssue() *Issue

ToIssue converts a catalog molecule to an Issue struct for compatibility. The issue has Type="molecule" and is marked as a template.

type ClearMailResult

type ClearMailResult struct {
	Closed  int // Number of messages closed
	Cleared int // Number of pinned messages cleared (content removed)
}

ClearMailResult contains statistics from a ClearMail operation.

type CreateOptions

type CreateOptions struct {
	Title       string
	Type        string // "task", "bug", "feature", "epic"
	Priority    int    // 0-4
	Description string
	Parent      string
	Actor       string // Who is creating this issue (populates created_by)
}

CreateOptions specifies options for creating an issue.

type Delegation

type Delegation struct {
	// Parent is the work unit ID that delegated the work
	Parent string `json:"parent"`

	// Child is the work unit ID that received the delegated work
	Child string `json:"child"`

	// DelegatedBy is the entity (hop:// URI or actor string) that delegated
	DelegatedBy string `json:"delegated_by"`

	// DelegatedTo is the entity (hop:// URI or actor string) receiving delegation
	DelegatedTo string `json:"delegated_to"`

	// Terms contains optional conditions of the delegation
	Terms *DelegationTerms `json:"terms,omitempty"`

	// CreatedAt is when the delegation was created
	CreatedAt string `json:"created_at,omitempty"`
}

Delegation represents a work delegation relationship between work units. Delegation links a parent work unit to a child work unit, tracking who delegated the work and to whom, along with any terms of the delegation. This enables work distribution with credit cascade - work flows down, validation and credit flow up.

type DelegationTerms

type DelegationTerms struct {
	// Portion describes what part of the parent work is delegated
	Portion string `json:"portion,omitempty"`

	// Deadline is the expected completion date
	Deadline string `json:"deadline,omitempty"`

	// AcceptanceCriteria describes what constitutes completion
	AcceptanceCriteria string `json:"acceptance_criteria,omitempty"`

	// CreditShare is the percentage of credit that flows to the delegate (0-100)
	CreditShare int `json:"credit_share,omitempty"`
}

DelegationTerms holds optional terms/conditions for a delegation.

type DetachAuditEntry

type DetachAuditEntry struct {
	Timestamp        string `json:"timestamp"`
	Operation        string `json:"operation"` // "detach", "burn", "squash"
	PinnedBeadID     string `json:"pinned_bead_id"`
	DetachedMolecule string `json:"detached_molecule"`
	DetachedBy       string `json:"detached_by,omitempty"` // Agent that triggered detach
	Reason           string `json:"reason,omitempty"`      // Optional reason for detach
	PreviousState    string `json:"previous_state,omitempty"`
}

DetachAuditEntry represents an audit log entry for a detach operation.

type DetachOptions

type DetachOptions struct {
	Operation string // "detach", "burn", "squash" - defaults to "detach"
	Agent     string // Who is performing the detach
	Reason    string // Optional reason for the detach
}

DetachOptions specifies optional context for a detach operation.

type InstantiateOptions

type InstantiateOptions struct {
	// Context map for {{variable}} substitution
	Context map[string]string
}

InstantiateOptions configures molecule instantiation behavior.

type Issue

type Issue struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Status      string   `json:"status"`
	Priority    int      `json:"priority"`
	Type        string   `json:"issue_type"`
	CreatedAt   string   `json:"created_at"`
	CreatedBy   string   `json:"created_by,omitempty"`
	UpdatedAt   string   `json:"updated_at"`
	ClosedAt    string   `json:"closed_at,omitempty"`
	Parent      string   `json:"parent,omitempty"`
	Assignee    string   `json:"assignee,omitempty"`
	Children    []string `json:"children,omitempty"`
	DependsOn   []string `json:"depends_on,omitempty"`
	Blocks      []string `json:"blocks,omitempty"`
	BlockedBy   []string `json:"blocked_by,omitempty"`
	Labels      []string `json:"labels,omitempty"`

	// Agent bead slots (type=agent only)
	HookBead   string `json:"hook_bead,omitempty"`   // Current work attached to agent's hook
	RoleBead   string `json:"role_bead,omitempty"`   // Role definition bead (shared)
	AgentState string `json:"agent_state,omitempty"` // Agent lifecycle state (spawning, working, done, stuck)

	// Counts from list output
	DependencyCount int `json:"dependency_count,omitempty"`
	DependentCount  int `json:"dependent_count,omitempty"`
	BlockedByCount  int `json:"blocked_by_count,omitempty"`

	// Detailed dependency info from show output
	Dependencies []IssueDep `json:"dependencies,omitempty"`
	Dependents   []IssueDep `json:"dependents,omitempty"`
}

Issue represents a beads issue.

type IssueDep

type IssueDep struct {
	ID             string `json:"id"`
	Title          string `json:"title"`
	Status         string `json:"status"`
	Priority       int    `json:"priority"`
	Type           string `json:"issue_type"`
	DependencyType string `json:"dependency_type,omitempty"`
}

IssueDep represents a dependency or dependent issue with its relation.

type ListOptions

type ListOptions struct {
	Status     string // "open", "closed", "all"
	Type       string // "task", "bug", "feature", "epic"
	Priority   int    // 0-4, -1 for no filter
	Parent     string // filter by parent ID
	Assignee   string // filter by assignee (e.g., "gastown/Toast")
	NoAssignee bool   // filter for issues with no assignee
}

ListOptions specifies filters for listing issues.

type MRFields

type MRFields struct {
	Branch      string // Source branch name (e.g., "polecat/Nux/gt-xyz")
	Target      string // Target branch (e.g., "main" or "integration/gt-epic")
	SourceIssue string // The work item being merged (e.g., "gt-xyz")
	Worker      string // Who did the work
	Rig         string // Which rig
	MergeCommit string // SHA of merge commit (set on close)
	CloseReason string // Reason for closing: merged, rejected, conflict, superseded
	AgentBead   string // Agent bead ID that created this MR (for traceability)

	// Conflict resolution fields (for priority scoring)
	RetryCount      int    // Number of conflict-resolution cycles
	LastConflictSHA string // SHA of main when conflict occurred
	ConflictTaskID  string // Link to conflict-resolution task (if any)

	// Convoy tracking (for priority scoring - convoy starvation prevention)
	ConvoyID        string // Parent convoy ID if part of a convoy
	ConvoyCreatedAt string // Convoy creation time (ISO 8601) for starvation prevention
}

MRFields holds the structured fields for a merge-request issue. These fields are stored as key: value lines in the issue description.

func ParseMRFields

func ParseMRFields(issue *Issue) *MRFields

ParseMRFields extracts structured merge-request fields from an issue's description. Fields are expected as "key: value" lines, with optional prose text mixed in. Returns nil if no MR fields are found.

type MergeSlotStatus

type MergeSlotStatus struct {
	ID        string   `json:"id"`
	Available bool     `json:"available"`
	Holder    string   `json:"holder,omitempty"`
	Waiters   []string `json:"waiters,omitempty"`
	Error     string   `json:"error,omitempty"`
}

MergeSlotStatus represents the result of checking a merge slot.

type MoleculeCatalog

type MoleculeCatalog struct {
	// contains filtered or unexported fields
}

MoleculeCatalog provides hierarchical molecule template loading. It loads molecules from multiple sources in priority order: 1. Town-level: <town>/.beads/molecules.jsonl 2. Rig-level: <town>/<rig>/.beads/molecules.jsonl 3. Project-level: .beads/molecules.jsonl in current directory

Later sources can override earlier ones by ID.

func LoadCatalog

func LoadCatalog(townRoot, rigPath, projectPath string) (*MoleculeCatalog, error)

LoadCatalog creates a catalog with all molecule sources loaded. Parameters:

  • townRoot: Path to the Gas Town root (e.g., ~/gt). Empty to skip town-level.
  • rigPath: Path to the rig directory (e.g., ~/gt/gastown). Empty to skip rig-level.
  • projectPath: Path to the project directory. Empty to skip project-level.

Molecules are loaded from town, rig, and project levels (no builtin molecules). Each level follows .beads/redirect if present (for shared beads support).

func NewMoleculeCatalog

func NewMoleculeCatalog() *MoleculeCatalog

NewMoleculeCatalog creates an empty catalog.

func (*MoleculeCatalog) Add

func (c *MoleculeCatalog) Add(mol *CatalogMolecule)

Add adds or replaces a molecule in the catalog.

func (*MoleculeCatalog) Count

func (c *MoleculeCatalog) Count() int

Count returns the number of molecules in the catalog.

func (*MoleculeCatalog) Get

Get returns a molecule by ID, or nil if not found.

func (*MoleculeCatalog) List

func (c *MoleculeCatalog) List() []*CatalogMolecule

List returns all molecules in insertion order.

func (*MoleculeCatalog) LoadFromFile

func (c *MoleculeCatalog) LoadFromFile(path, source string) error

LoadFromFile loads molecules from a JSONL file. Each line should be a JSON object with id, title, and description fields. The source parameter is added to each loaded molecule.

func (*MoleculeCatalog) SaveToFile

func (c *MoleculeCatalog) SaveToFile(path string) error

SaveToFile writes all molecules to a JSONL file. This is useful for exporting the catalog or creating template files.

type MoleculeStep

type MoleculeStep struct {
	Ref          string         // Step reference (from "## Step: <ref>")
	Title        string         // Step title (first non-empty line or ref)
	Instructions string         // Prose instructions for this step
	Needs        []string       // Step refs this step depends on
	WaitsFor     []string       // Dynamic wait conditions (e.g., "all-children")
	Tier         string         // Optional tier hint: haiku, sonnet, opus
	Type         string         // Step type: "task" (default), "wait", etc.
	Backoff      *BackoffConfig // Backoff configuration for wait-type steps
}

MoleculeStep represents a parsed step from a molecule definition.

func ParseMoleculeSteps

func ParseMoleculeSteps(description string) ([]MoleculeStep, error)

ParseMoleculeSteps extracts step definitions from a molecule's description.

The expected format is:

## Step: <ref>
<prose instructions>
Needs: <step>, <step>  # optional
Tier: haiku|sonnet|opus  # optional
Type: task|wait  # optional, default is "task"
Backoff: base=30s, multiplier=2, max=10m  # optional, for wait-type steps

Returns an empty slice if no steps are found.

type RigFields

type RigFields struct {
	Repo   string // Git URL for the rig's repository
	Prefix string // Beads prefix for this rig (e.g., "gt", "bd")
	State  string // Operational state: active, archived, maintenance
}

RigFields contains the fields specific to rig identity beads.

func ParseRigFields

func ParseRigFields(description string) *RigFields

ParseRigFields extracts rig fields from an issue's description.

type RoleConfig

type RoleConfig struct {
	// SessionPattern defines how to derive tmux session name.
	// Supports placeholders: {rig}, {name}, {role}
	// Examples: "hq-mayor", "hq-deacon", "gt-{rig}-{role}", "gt-{rig}-{name}"
	SessionPattern string

	// WorkDirPattern defines the working directory relative to town root.
	// Supports placeholders: {town}, {rig}, {name}, {role}
	// Examples: "{town}", "{town}/{rig}", "{town}/{rig}/polecats/{name}"
	WorkDirPattern string

	// NeedsPreSync indicates whether workspace needs git sync before starting.
	// True for agents with persistent clones (refinery, crew, polecat).
	NeedsPreSync bool

	// StartCommand is the command to run after creating the session.
	// Default: "exec cursor-agent -f"
	StartCommand string

	// EnvVars are additional environment variables to set in the session.
	// Stored as "key=value" pairs.
	EnvVars map[string]string
}

RoleConfig holds structured lifecycle configuration for role beads. These fields are stored as "key: value" lines in the role bead description. This enables agents to self-register their lifecycle configuration, replacing hardcoded identity string parsing in the daemon.

func ParseRoleConfig

func ParseRoleConfig(description string) *RoleConfig

ParseRoleConfig extracts RoleConfig from a role bead's description. Fields are expected as "key: value" lines. Returns nil if no config found.

type Route

type Route struct {
	Prefix string `json:"prefix"` // Issue ID prefix (e.g., "gt-")
	Path   string `json:"path"`   // Relative path to .beads directory from town root
}

Route represents a prefix-to-path routing rule. This mirrors the structure in bd's internal/routing package.

func LoadRoutes

func LoadRoutes(beadsDir string) ([]Route, error)

LoadRoutes loads routes from routes.jsonl in the given beads directory. Returns an empty slice if the file doesn't exist.

type SyncStatus

type SyncStatus struct {
	Branch    string
	Ahead     int
	Behind    int
	Conflicts []string
}

SyncStatus represents the sync status of the beads repository.

type SynthesisFields

type SynthesisFields struct {
	ConvoyID   string `json:"convoy_id"`   // Parent convoy ID
	ReviewID   string `json:"review_id"`   // Review ID for output paths
	OutputPath string `json:"output_path"` // Path to synthesis output file
	Formula    string `json:"formula"`     // Formula name (if from formula)
}

SynthesisFields holds structured fields for synthesis beads. These fields track the synthesis step in a convoy workflow.

func ParseSynthesisFields

func ParseSynthesisFields(issue *Issue) *SynthesisFields

ParseSynthesisFields extracts synthesis fields from an issue's description. Fields are expected as "key: value" lines. Returns nil if no fields found.

type UpdateOptions

type UpdateOptions struct {
	Title        *string
	Status       *string
	Priority     *int
	Description  *string
	Assignee     *string
	AddLabels    []string // Labels to add
	RemoveLabels []string // Labels to remove
	SetLabels    []string // Labels to set (replaces all existing)
}

UpdateOptions specifies options for updating an issue.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL