Bead Issue Tracking¶
Bead is a lightweight, git-native issue tracking system built into sase. It uses Rust-backed event storage,
query/reduction, and mutation logic through the required sase_core_rs extension, with generated JSONL compatibility
projections for older tooling (inspired by Fossil). Issues are organized into plan-like
containers and executable child phases. Plan beads can represent ordinary plans, executable epics, or legend-level
roadmaps through their tier metadata.

Table of Contents¶
- Quick Start
- Data Model
- Issue Types
- Status Lifecycle
- Dependencies
- Storage
- Directory Structure
- Event Log + Compatibility Projections
- Sync Mechanism
- CLI Commands
- Rust Backend
- Current Checkout Source Of Truth
- ACE TUI Integration
Quick Start¶
sase bead init # Initialize beads in current project
sase bead create -t "New feature" --type "plan(sdd/tales/202605/feature.md)" --tier plan
sase bead create -t "Epic" --type "plan(sdd/epics/202605/epic.md)" --tier epic
sase bead create -t "Roadmap" --type "plan(sdd/legends/202605/roadmap.md)" --tier legend --epic-count 3
sase bead create -t "Linked epic" --type "plan(sdd/epics/202605/epic.md,<legend-id>)" --tier epic
sase bead create -t "Sub-task" --type "phase(beads-001)" # Create a phase under a plan
sase bead list # List open and in-progress issues
sase bead list --status=open # List open issues
sase bead list --status=closed # List closed issues
sase bead search auth # Search open, in-progress, and closed issues
sase bead ready # Show issues ready to work on
sase bead show beads-001 # View issue details
sase bead update beads-001.1 --status=in_progress # Claim an issue
sase bead open beads-001.1 # Reopen an issue
sase bead close beads-001.1 # Close an issue
sase bead dep add beads-001.2 beads-001.1 # Add dependency
sase bead blocked # Show blocked issues
sase bead sync # Export and stage JSONL in git
sase bead stats # Project statistics
sase bead doctor # Health check
sase bead work beads-001 # Launch agents for an epic or legend plan bead
Data Model¶
Issue Types¶
| Type | Description | ID Format |
|---|---|---|
| Plan | Plan-like container with a tier | {prefix}-{counter} |
| Phase | Executable task within an epic/plan bead | {parent_id}.{N} |
Plans are groupings that can optionally link to an SDD file via the design field. Phases always belong to a parent
plan and use hierarchical IDs (e.g., beads-001.1, beads-001.2).
Plan beads carry a tier. The SDD paths below are the conventional version-controlled locations; in local SDD mode the
same artifact classes live under .sase/sdd/.
| Tier | SDD Path | Behavior |
|---|---|---|
plan |
sdd/tales/{YYYYMM}/*.md |
Normal non-epic implementation plan |
epic |
sdd/epics/{YYYYMM}/*.md |
Executable multi-phase plan accepted by sase bead work |
legend |
sdd/legends/{YYYYMM}/*.md |
Higher-level coordination plan; launches epic-planning agents by epic_count |
Linked epics use the existing parented plan syntax:
sase bead create --title "Epic" --type "plan(sdd/epics/202605/epic.md,<legend_bead_id>)" --tier epic
Status Lifecycle¶
| Status | Icon | Description |
|---|---|---|
open |
○ |
Not started |
in_progress |
◐ |
Currently being worked on |
closed |
✓ |
Completed or abandoned |
Status can transition freely between any values via sase bead update --status=<status>. sase bead open <id> is a
shortcut for sase bead update <id> --status=open.
Dependencies¶
Dependencies are one-way relationships: issue A depends on issue B. An issue is:
- Ready if it is
openand all its dependencies areclosed. - Blocked if it has at least one dependency with status
openorin_progress.
Storage¶
Directory Structure¶
When version-controlled mode is effective (sdd.version_controlled: true, or any project resolved as the built-in
bare_git VCS provider):
sdd/beads/
config.json # Configuration (issue prefix, counter, owner)
events/
manifest.json # Event-store schema and migration metadata
streams/
<root-id>.jsonl # Canonical append-only event stream
issues.jsonl # Generated compatibility projection
beads.db # SQLite compatibility cache (gitignored)
In non-version-controlled mode for other providers, the directory is .sase/sdd/beads/ with the same structure.
Normal bead commands read and write one store for the active checkout. In version-controlled mode, canonical bead state
lives in the current checkout's sdd/beads/events/** event store plus sdd/beads/config.json. If the event store is
absent, reads fall back to legacy issues.jsonl. Numbered sibling workspaces and legacy stores are not merged into
normal sase bead reads.
Event Log + Compatibility Projections¶
Rust owns the bead storage/query/mutation path. The append-only event streams are the canonical git-portable state.
issues.jsonl remains a generated compatibility projection, and beads.db remains a local compatibility cache. They
are kept in sync:
- Writes append canonical Rust events first, then regenerate
issues.jsonland refreshbeads.db. - Reads prefer
events/manifest.jsonplusevents/streams/*.jsonl, falling back to legacyissues.jsonlonly when no event store is present. - Fresh clones read directly from the tracked event streams and can rebuild the compatibility mirrors on demand.
The .gitignore excludes beads.db* files. The event store, issues.jsonl, and config.json are tracked in git.
Sync Mechanism¶
sase bead sync regenerates the compatibility projection from the canonical event store and stages the bead state in
git, including sdd/beads/events/**, issues.jsonl, and config.json. The projection contains one JSON object per
line, sorted by issue ID for clean diffs.
When both stores exist, the event store wins. Manual edits to issues.jsonl do not change command output unless the
event store is absent.
CLI Commands¶
With no subcommand, sase bead defaults to sase bead list with default options. Use the explicit sase bead list
form when passing list filters.
sase bead init¶
Initialize the bead store for the current project. In effective version-controlled SDD mode this is sdd/beads/; in
local SDD mode this is .sase/sdd/beads/.
sase bead create¶
Create a new issue.
| Flag | Required | Description |
|---|---|---|
-t, --title |
yes | Issue title |
-T, --type |
yes | Bead type: plan(<file>), plan(<file>,<parent>), or phase(<parent_id>) |
-d, --description |
no | Issue description |
-a, --assignee |
no | Assignee name |
--tier |
no | Plan-bead tier: plan, epic, or legend |
-c, --changespec |
no | Attach a ChangeSpec name to a plan bead |
-b, --bug-id |
no | Bug ID for the attached ChangeSpec; requires --changespec |
-E, --epic-count |
no | Positive number of epics proposed by a legend plan bead |
-m, --model |
no | Model used when this bead is launched. Provider-qualified (e.g. codex/gpt-5.5) or a configured local alias (e.g. #pro). On epic and legend plan beads this becomes the land-agent model; on phase beads it is the per-phase work model. |
ChangeSpec metadata is valid only on plan beads. It is used by the epic-approval and sase bead work flows to keep plan
beads linked to the ChangeSpec they are intended to produce.
sase bead list¶
List issues with optional filtering. Without --status, the command lists open and in_progress issues; pass
--status=closed when you need closed history. --status, --type, and --tier are repeatable.
| Flag | Values | Description |
|---|---|---|
-s, --status |
open, in_progress, closed |
Filter by status (repeatable) |
-t, --type |
plan, phase |
Filter by type (repeatable) |
--tier |
plan, epic, legend |
Filter by plan-bead tier |
sase bead search <query>¶
Find beads whose indexed text fields contain a case-insensitive literal substring. This is substring search, not regex
or glob matching. Current indexed fields include ID, title, description, notes, design/plan path, owner, assignee,
model, ChangeSpec name/bug ID, status, type, and tier; timestamps are not searched. Unlike sase bead list, search
includes open, in_progress, and closed beads by default, so it is the quickest way to recover older context.
Compact output prints each matching bead with a short snippet. For multi-line fields such as descriptions or notes, the
snippet uses the line that matched the query when possible instead of always showing the first line. JSON output exposes
the exact matched_fields list for each result.
sase bead search auth
sase bead search auth --format json
sase bead search auth --format full --limit 3
sase bead search auth --status open --type phase
sase bead search auth --type plan --tier epic
| Flag | Values | Description |
|---|---|---|
-c, --color |
auto, always, never |
Color mode for compact output |
-f, --format |
compact, json, full |
Output format; defaults to compact |
-n, --limit |
non-negative integer | Maximum results; omitted or 0 means unlimited |
-s, --status |
open, in_progress, closed |
Filter by status (repeatable) |
--tier |
plan, epic, legend |
Filter by plan-bead tier (repeatable) |
-t, --type |
plan, phase |
Filter by type (repeatable) |
sase bead show <id>¶
Display complete details for an issue including status, type, tier, epic count, parent/children, dependencies, blockers, description, notes, ChangeSpec metadata, model, and linked plan path.
sase bead ready¶
Show issues that are ready to work on: open status with all dependencies closed.
sase bead open <id>¶
Reopen an issue by setting its status to open. This is equivalent to sase bead update <id> --status=open.
sase bead update <id>¶
Update one or more fields on an issue.
| Flag | Description |
|---|---|
-s, --status |
Change status |
-t, --title |
Change title |
-d, --description |
Change description |
-n, --notes |
Change notes |
-D, --design |
Change plan path |
-a, --assignee |
Change assignee |
--tier |
Change plan tier |
-E, --epic-count |
Change legend epic count |
-m, --model |
Change the launch model. Pass an empty string to clear. |
sase bead close <id> [<id2> ...]¶
Close one or more issues.
Closing a plan bead also closes its phase children. Use this intentionally: phase agents should close only their assigned phase bead, not the parent epic.
| Flag | Description |
|---|---|
-r, --reason |
Optional close reason text |
sase bead rm <id>¶
Remove an issue and cascade-delete all its children. This is irreversible.
sase bead dep add <issue> <depends_on>¶
Add a dependency: <issue> depends on <depends_on>. The issue becomes blocked if the dependency is not yet closed.
sase bead blocked¶
Show all issues that have at least one active (non-closed) blocker.
sase bead sync¶
Regenerate the compatibility projection from the canonical event store and stage bead state in git. It does not create a commit; the staged event/projection files are included in the next normal project or SDD commit.
| Flag | Description |
|---|---|
-s, --status |
Check whether bead state has unstaged changes |
sase bead stats¶
Show project statistics: total, open, in-progress, and closed counts, plus plan and phase counts.
sase bead doctor¶
Run health checks on the beads database. Checks for:
- Missing
config.json, event store, legacy projection, or compatibility cache - Projection drift between canonical events and
issues.jsonl - Invalid events or unreduced orphan phase records
- Uncommitted bead-state changes
- Orphan children (phases whose parent plan is missing)
If bead commands fail before opening a store, run sase core health first. It verifies that the required sase_core_rs
extension is importable and exposes the representative bead CLI binding used by the fast path.
sase bead onboard¶
Display a quick-start guide with common command examples.
sase bead work <id>¶
Run an entire epic-tier plan end-to-end by launching one agent per phase plus a final land agent, or run a legend-tier
plan by launching one epic-planning agent per stored epic_count.
For epic-tier plans, the command:
- Validates that
<epic_id>resolves to an issue of typeplanwithtier=epic. If the plan is already markedis_ready_to_work, the command treats the run as a retry and schedules any remaining non-closed phases. - On a confirmed launch, force-reuses the deterministic bead-work names —
<epic_id>.<N>(for each open phase),<epic_id>(for the land agent), and the legacy<epic_id>.landland-agent name — by wiping any prior owner of those names, whether that owner is a completed, dismissed, or planned reservation or a still-live agent (live owners are terminated). This also covers owners that hold the name only as aworkflow_name. If the forced-reuse cleanup cannot complete (a wipe fails or a name is still reserved afterward), the command aborts before mutating any bead state.--dry-runperforms no cleanup; it only warns which live agents a real launch would force-reuse. - Flips the epic plan bead's
is_ready_to_workflag toTruewhen it was not already ready. - Builds a Kahn-wave schedule from the epic's open phase children, respecting dependencies.
- Pre-claims each phase bead — sets
status=in_progressandassignee=<phase_bead_id>(i.e.<epic_id>.<N>). - Hands a single
----separated multi-prompt to the agent launcher. Each per-phase agent is spawned with name<epic_id>.<N>and references thework_phase_beadxprompt; a final land agent named<epic_id>references theland_epicxprompt. Phase dependencies become%wwaits on blocker phase-agent names, and the land agent waits on every launched phase agent. Because%wrequires a successfuldone.jsonoutcome, a failed or killed phase keeps dependent phases and the land agent parked until the phase name is retried successfully. Phase beads with a storedmodelemit%model:<value>; phase beads without one emit%model:worker, which resolves through the worker-lane order: active worker override, matchingllm_provider.worker_modelsentry, then the primary model lane. A stored model on the epic plan bead still applies only to the land agent. Each segment uses the force-reuse%name:!<agent_name>form so re-runningsase bead workafter a killed or failed run wipes the stale name owners before the relaunch — the command is safe to retry.
For legend-tier plans, the command:
- Validates that
<id>resolves to a plan bead withtier=legend, a positiveepic_count, and a linked legend plan path. - On a confirmed launch, force-reuses the generated epic-planning and land agent names (like
<legend_id>.1.0and<legend_id>) by wiping any prior owner of those names before relaunch, aborting before mutating bead state if that cleanup cannot complete.--dry-runperforms no cleanup; it only warns which live agents a real launch would force-reuse. - Flips the legend plan bead's
is_ready_to_workflag toTruewhen launching. - Hands a single
----separated multi-prompt to the agent launcher. Each epic-planning segment is named%name:!<legend_id>.<N>.0and includes%epic; epic-planning segments do not carry%approve, because their generated plans go through the normal plan-approval flow. EpicN > 1also waits on%w:<legend_id>.<N-1>, so epic planning proceeds in order. After the epic-planning segments, a final land-legend segment named<legend_id>references theland_legendxprompt, waits on the last epic-planning agent, and carries%approveso it can self-approve. When the legend bead has a storedmodel, that segment also emits%model:<value>. As with the epic-tier rendering, every segment uses the force-reuse%name:!<agent_name>form so the command is safe to retry after a killed or failed run.
Legend work does not create phase beads directly. The spawned epic-planning agents create epic plans, and the existing
bd/new_epic automation handles the linked epic and phase beads after those plans are approved.
| Flag | Description |
|---|---|
-n, --dry-run |
Print the wave plan and rendered multi-prompt without mutating state or launching |
-P, --no-push |
Commit launched bead state locally but skip the post-commit git push |
-y, --yes |
Skip the launch confirmation prompt |
The work xprompts are resolved by XPromptTag (tag-based lookup), so a project-local or user-defined xprompt with the
matching tag overrides the built-in. For epic-tier work, every phase and land segment carries a %approve directive so
spawned agents can self-approve their own plans without a human-in-the-loop checkpoint between waves. For legend-tier
work, only the trailing land-legend segment carries %approve; each epic-planning segment uses %epic and pauses for
the normal plan-approval flow before its child phases run.
When the epic plan bead is attached to ChangeSpec metadata (--changespec / --bug-id), sase bead work preserves the
current project's VCS context in the generated prompt. The first phase segment targets the project reference and adds a
#pr reference for the ChangeSpec, while later phase and land segments target the ChangeSpec ref directly. For
non-ChangeSpec epics launched from a known SASE workspace, each segment is still prefixed with the detected VCS workflow
and project name (for example #git:sase or #gh:sase-org/sase). If the current directory is not associated with a
SASE project, the prompts are left unprefixed and run in the caller's normal launch context.
If launching the multi-prompt fails partway through, the launcher SIGTERMs any already-spawned children before rolling
back the pre-claims and the is_ready_to_work flag when this run set it (best-effort), so the epic can be retried
without leaving zombie agents behind.
After the agents launch successfully, sase bead work commits the resulting bead-state mutation when the beads
directory belongs to a git repository and canonical/projection files changed. This commit records the bead launch state
only; it does not commit any code produced later by the spawned agents. Epic launches use the subject
chore: mark bead work launched for <id>; legend launches use chore: mark legend work launched for <id>. If the git
commit fails, the command reports that agents were already launched and exits non-zero so the operator can commit or
repair the bead state explicitly. Dry runs and stores outside git do not create a commit.
When that commit succeeds and bead.push_after_commit is true (the default), sase bead work follows it with
git push so the launched-work record reaches the remote without a manual follow-up step. The push inherits the
caller's stdin/stdout/stderr, so interactive credential prompts still work. If the repository has no remote configured,
the push is skipped silently — the local commit stands on its own. If git push fails (for example because the remote
rejected the update), the failure is reported as a warning only: the bead-launch commit is preserved on the local branch
and the warning text includes the manual git push invocation to retry.
Set bead.push_after_commit: false in ~/.config/sase/sase.yml to disable the auto-push — useful for local-only
checkouts, or when you would rather batch the bead-launch commit with later commits before pushing. Set it to async to
keep auto-pushing but move the push off the critical path: sase bead work launches a detached background git push
and returns immediately, printing the log file where the background push records its result. The background push is
non-interactive (its stdin is closed), so it cannot prompt for credentials; failures are written to that log instead of
warning inline. Pass --no-push (-P) to sase bead work to skip the push for a single invocation regardless of the
configured mode.
Rust Backend¶
The bead data model, event reducer, JSONL/config codecs, compatibility-cache refresh, mutation transactions, ID
allocation, deterministic work-plan DAG, and common CLI output planning are implemented in sase-core and exposed
through sase_core_rs. Python keeps the host logic that belongs in the application layer: locating the active bead
store, relativizing plan paths, resolving VCS context and xprompts for sase bead work, prompting the user, launching
agents, rolling back failed launches, and incrementing telemetry counters.
Common sase bead commands dispatch through an early CLI fast path before the full top-level parser is built. Help text
and host-coupled commands still fall through to the normal Python parser/handlers where needed.
Use these checks when changing bead internals:
sase core health -j
pytest tests/test_bead tests/test_core_facade/test_bead_read.py tests/test_core_facade/test_bead_mutation.py
just rust-check
just bead-perf-smoke
Current Checkout Source Of Truth¶
In version-controlled mode, every sase bead read and mutation command uses the current checkout's
sdd/beads/events/** event store and sdd/beads/config.json, with issues.jsonl used only as a fallback when events
are absent. Running the command in myproject/ reads that checkout's bead state; running it in myproject_2/ reads
myproject_2/sdd/beads/. The CLI does not merge sibling workspace stores, and duplicate IDs in another checkout do not
override the active checkout's records.
ID allocation also uses only the active store's config.json and canonical event state. If a sibling checkout has not
pulled or merged the latest bead state, it may allocate IDs based on its local state; sync bead changes through the
normal VCS workflow when several agents are coordinating on the same project.
Cross-project helper surfaces, such as mobile/editor bead pickers, may inspect one canonical store per known project, but they still do not merge numbered sibling workspaces or legacy bead stores for the same project.
ACE TUI Integration¶
Plan File Linking¶
When creating a plan bead with --type plan(PATH), the file path is stored in the design field. The ACE TUI can
navigate from a bead to its linked SDD file.
For SDD-generated epics, PATH should be the shared plan reference emitted by the plan approval flow:
sdd/epics/YYYYMM/*.md in effective version-controlled mode, or .sase/sdd/epics/YYYYMM/*.md in local SDD mode. These
relative references stay portable across checkouts while each checkout reads its own bead store.
Plan Approval Flow¶
The plan approval popup in ACE includes normal approval, E (Epic), and L (Legend) actions. Normal approval saves
to sdd/tales/, Epic saves to sdd/epics/ and launches the epic follow-up that creates an epic-tier plan bead plus
phase beads, and Legend saves to sdd/legends/ and launches a legend follow-up that records a legend-tier plan bead
with epic_count before starting the legend work chain.