Bead lifecycles¶
This page describes the status transitions that govern beads and their claims.
Status Lifecycle¶
| Status | Icon | Description |
|---|---|---|
open |
○ |
Not started; for task beads, still a draft that is not offered for triage |
claimed |
◎ |
Reserved by a live agent that has not started work |
ready |
◇ |
Task bead explicitly offered for triage; invalid for plan, phase, and flag task beads |
snoozed |
◈ |
Task bead deferred to a wake time (or +1 target); invalid for plan, phase, and flag task beads |
in_progress |
◐ |
Being worked on, or preassigned by an epic/task launch checkpoint |
closed |
✓ |
Completed, canceled, or superseded |
Status can transition between values via sase bead update --status=<status>. A task
normally moves open → ready when its draft is proposed, ready → open when retracted,
and ready → in_progress when launched. Only task beads may carry ready; set it after
the title, description, notes, dependencies, and any desired size or model are ready for
human review. Moving a bead to closed is rejected while any descendant remains open,
claimed, ready, snoozed, or in progress. Close those descendants deliberately first;
update --status=closed never cascades. sase bead open <id> reopens the bead and
every closed ancestor above it, archiving their resolution, close reason, and close
timestamp into close history instead of discarding them,
so a closed parent never sits above reopened work but the reason it was closed is not
lost either. claimed is machine-managed by the agent runner (see
Bead Claim Lifecycle); do not set it by hand. snoozed cannot
be set through update --status either — snoozing requires a wake time, so
sase bead update -s snoozed is refused with a pointer to sase bead snooze, the same
way update --status=closed points at sase bead close. See
Snoozing a Task Bead for the full workflow.
Every new close records a typed resolution: done, canceled, or superseded.
Normal closes default to done; close_reason remains optional free text for the human
explanation. Closing an already-closed bead is a verified no-op: the command succeeds
when any supplied reason or resolution agrees with the recorded close, and fails without
writing when the request conflicts. Historical closed beads are not backfilled, so their
resolution remains unset and human-readable detail views show (unrecorded).
Flag Bead Lifecycle¶
A flag bead is not a fourth issue type and not the epic or task that introduced a
feature. It is a top-level task bead of type flag: the dedicated removal dossier for
one SASE feature flag, linked from the code-owned registry. Feature flags are a
sase-project concern only.
Two kinds exist, and the registry default is derived from the kind:
beta— default off. The behavior is unproven; a user opts in.sunset— default on. The behavior is already the default; the flag keeps the old branch reachable while callers migrate.
Removing a flag deletes the Off branch and makes the On branch unconditional.
The bead carries seven required fields. sase flag new supplies key, kind,
remove_by_date (today + 90 days), and remove_by_release (current minor + 2). The
author writes the three a removal agent cannot reconstruct: --when-enabled,
--when-disabled (the branch deleted at removal), and --remove-when (the qualitative
gate). The typed body block renders those fields. -d/--description seeds the registry
entry's one-line help.
Create feature flags with sase flag new <key>, not by hand-editing the registry and
not with sase bead create or /sase_new_task. The command creates the typed task
bead, prints the registry entry to paste, and shows the both-states test checklist.
sase bead create -T 'task(flag)' is refused because the type is not agent-creatable.
Flag task beads stay open until someone is working them or they close; they do not use
ready or snoozed. A flag becomes due only when both the date and release threshold
have passed. Due-ness never flips the stored status and never changes the boolean value.
Extend both thresholds with sase bead update <id> -b YYYY-MM-DD/release.
AXE's bead-gate reconciler raises one FlagTriage gate for a live due flag task bead.
Its decisions are:
- Remove deletes the Off branch, makes the On branch unconditional, removes the registry entry, and closes the flag bead in the same change.
- Extend pushes both
remove_bythresholds out and records why the flag is still temporary. - Keep records that the behavior is permanent. It was never a feature flag; make it a config field and close the bead.
- Close abandons the removal. Use this when the flag was already removed or is intentionally orphaned; registry/bead integrity checks catch an inconsistent survivor.
Bead Claim Lifecycle¶
An agent launched with %id(<name>, bead=<id>) reserves its bead before it starts
working, so a bead is never silently owned by a process that nothing else can see:
open ──claim──▶ claimed ──promote──▶ in_progress ──close──▶ closed
▲ │
└────release─────┘ (claim owner died before launching)
- Claim. When a bead-carrying agent enters a wait phase (dependency
%wait, runner-slot, or duration waits), the runner sets the bead toclaimedand assigns it to the agent name. Claims are written to the project's canonical bead store, committed locally, and then published synchronously on a best-effort basis so other hosts can see the claim: the runner runs the managed sync worker for that store right after the commit lands. Publication never rolls a claim back — a missing git repo or missing remote is a silent local-only outcome, and a real sync failure only prints a warning with the managed-sync log path while the local commit stands. Claiming is advisory: it never blocks or fails an agent launch, and a straight-through launch with no waits skips it entirely. Because it is advisory, the claim is acquired best-effort: the runner retries a bounded number of times (refreshing the canonical store once when the bead is not there yet, which is normal right after an epic graph is published), and whatever it fails to acquire is picked up by thebead_claim_checksreconciler. A bead can therefore turnclaimeda few seconds after its agent starts waiting rather than instantly. - Promote. Immediately before model execution the runner performs the existing
just-in-time claim, which sets
status=in_progressand assigns the runner name. Promotion is what makes the claim permanent; from that point the claim is never released automatically. In managed standalone SDD stores the promotion must produce a local commit and is published the same best-effort way before model execution; in in-tree stores the agent commits the promotion along with its implementation instead. - Attach. A launch whose bead was already closed when it was submitted attaches
without reopening. Status, assignee, resolution, close reason,
closed_at, close history, and every ancestor stay as they were. The same mutation appends oneCLOSED-BEAD LAUNCH:note authored by the launching agent:
CLOSED-BEAD LAUNCH: agent <agent> launched against this closed bead; status left closed.
Launch submitted <submitted_at>; run <run_id>.
Prior close: <closed_at> · <resolution>.
The ; run <run_id> clause is omitted when the run id is absent; a missing
closed_at or resolution prints unknown. There is no dedup: every such launch adds
one note. The agent's prompt starts with this host notice:
Host notice: bead `<id>` was already closed (`<resolution>`, `<closed_at>`) when this
run started. This run is attached to it without reopening it, and a
`CLOSED-BEAD LAUNCH:` note on the bead records the relaunch. First check whether the
earlier work already landed (`git log --grep <id>` and the bead's notes via
`sase bead read <id>`). If it did, verify it and finish without redoing it; closing
the bead again is a safe no-op. If the work is genuinely unfinished, run
`sase bead open <id>` first and close it yourself with
`sase bead close <id> --note "<what you verified>"` when done (the finalizer will not
close a reopened `open` bead). If the resolution is `canceled` or `superseded`, do not
redo the work unless this prompt explicitly asks for it.
A close made after submission still fails the launch with closed_while_queued. The
error names the bead, its closed_at, its resolution (plus the close reason when
present), the submission time, and the remedy: relaunch to run it against the closed
bead deliberately. sase bead open remains the path for a premature close.
- Release. If the owning agent dies before it ever promoted its claim, the bead
returns to
openwith an empty assignee. The runner shutdown path releases the claim on ordinary kills (except when a retry handoff is pending, which keeps the claim), and thebead_claim_checksjob is the backstop for SIGKILL, crashes, and reboots. It releases a claim only when the owning agent is dead, never promoted, and resolvable to its artifact; anything else is left untouched and reported bysase doctorinstead. A committed release is published the same best-effort way as a claim, so a freed bead does not stay claimed on other hosts. - Reconcile. The
bead_claim_checksjob — registered under thewaitsroutine — runs in both directions. Next to the release pass above, an acquire pass claims a bead on behalf of a live agent that is waiting without a claim, which is what makes a lost or delayed claim self-healing within onewaitsinterval. A held claim is recorded in the agent'sbead_claim.jsonartifact file, so an agent that already holds its claim costs the job nothing: it is filtered out without opening a bead store.sase doctorreports the residue in either direction — a claim with no resolvable owner, and a live pre-launch agent whose bead is stillopen.
Claim and release are compare-and-swap operations: a claim succeeds only from open
(re-claiming your own claim is a no-op), and a release succeeds only when the bead is
still claimed by the releasing agent. Both decline silently rather than overwriting
someone else's state, so all three layers are safe to run concurrently.
The diagram above describes an ordinary bead-carrying agent. sase bead work uses a
stronger batch checkpoint: before spawning any epic worker, it sets every scheduled
phase to in_progress with its deterministic worker as assignee and does the same for
the epic and land worker. The later runner-side wait claim and launch promotion become
idempotent no-ops. Scheduling still ignores bead status and decides from agent liveness
(artifacts and PID checks), so a retry can schedule preassigned work without creating a
duplicate name.
Task-bead launches use the same strong checkpoint principle for one worker:
sase bead work <task-id> assigns the task to the deterministic agent name <task-id>,
sets it to in_progress, commits that state, synchronizes it unless --no-push was
requested, and only then spawns the worker. A task launched through this path therefore
does not pass through the advisory claimed state.
Related¶
_[project]: A sase project is a named unit of work registered with SASE. _[workspace]: A sase workspace is a numbered clone of a project's primary repo, managed by the workspace store and tracked in that project's registry.json. _[agent]: A sase agent is an agent session or a single agent that does not belong to a session. _[prompt]: A prompt is the text SASE hands to a provider to start an agent turn. _[macro]: A macro is a reusable prompt triggered with #name in agent prompts. _[Patch]: A Patch is SASE's local unit of change. _[bead]: A bead is one tracked work item in SASE's git-native issue tracker. _[plan]: A plan is a persisted, approved artifact recording the intent behind agent work. _[TUI]: The TUI is SASE's terminal interface for running agents and reviewing results. _[epic]: An epic is an executable multi-phase plan run with sase bead work. _[gate]: A sase gate is a durable, command-backed request for one user decision, created when work must pause for the user. _[monitor]: A sase monitor is a monitor turn that runs one long command under a detached supervisor, so the command outlives the agent that started it. *[stitch]: A stitch is the lightweight ordered change record inside a Patch's STITCHES: section.