From 69e8648152fe15711b3031ca7a1cf624fe1b3fa9 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Fri, 31 Jul 2026 01:19:02 -0700 Subject: [PATCH 1/4] feat: add beads as third backlog backend option When config/backlog-backend=beads is set, firstmate uses the beads federated 'task' store as the queue source instead of data/backlog.md. Session-start's digest lists items with status:ready label from the beads store. - Add fm_beads_backend_available() check in fm-tasks-axi-lib.sh - Add print_backlog_beads_compact() rendering function in fm-session-start.sh - Update print_backlog_compact() to prioritize beads backend when configured - Add beads backend validation to bootstrap (checks task CLI and store reachability) - Update docs/configuration.md to document beads backend option - Update AGENTS.md section 10 to reference beads backend in backlog contract - Beads backend reuses existing task linkage machinery (task set-state, task close) The beads backend is fail-open: if task CLI is missing or store is unreachable, bootstrap reports a MISSING: diagnostic line and the home can still operate. --- AGENTS.md | 8 ++++---- bin/fm-bootstrap.sh | 24 ++++++++++++++++++++---- bin/fm-session-start.sh | 17 ++++++++++++++++- bin/fm-tasks-axi-lib.sh | 8 ++++++++ docs/configuration.md | 6 +++++- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e9a36aa908..fd89fab325 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -428,16 +428,16 @@ Mention cost as a courtesy when unusually much work is running, but never block ## 10. Backlog contract -`data/backlog.md` is the durable queue. +`data/backlog.md` is the durable queue (for tasks-axi and manual backends); when `config/backlog-backend=beads` is set, the beads federated task store is the queue source instead. It tracks work items only, never agents; persistent secondmates never appear as backlog items. Work routed to a secondmate is recorded in that secondmate home's own backlog, not the main backlog. -When a main-side thread such as a pending captain decision or relay reminder is worth durable tracking, file it as its own work item; use `tasks-axi hold --reason "" --kind captain` for a captain-gated thread. +When a main-side thread such as a pending captain decision or relay reminder is worth durable tracking, file it as its own work item; use `tasks-axi hold --reason "" --kind captain` for a captain-gated thread (or the equivalent beads API when using the beads backend). Unresolved decisions discovered by investigations or visual reviews follow `decision-hold-lifecycle`, which owns their mandatory backlog lifecycle. Update the backlog on every dispatch, completion, and decision for a work item. Re-evaluate queued work after every teardown and heartbeat, dispatching items only when dependencies and time gates have cleared. -`.tasks.toml`, `docs/configuration.md`, and current `tasks-axi --help` own the backlog schema, compatibility, retention, and routine command syntax. -Use compatible `tasks-axi` when the configured backend selects it and the documented manual path otherwise; keep only the configured recent Done entries. +`.tasks.toml`, `docs/configuration.md`, and current `tasks-axi --help` own the backlog schema, compatibility, retention, and routine command syntax for the default and manual backends; `docs/configuration.md` also documents the beads backend option and its mechanics. +Use compatible `tasks-axi` when the configured backend selects it, the beads store when beads is configured, and the documented manual path otherwise; keep only the configured recent Done entries. `secondmate-provisioning` and `bin/fm-backlog-handoff.sh` own cross-home handoff safety. Keep free-form notes free of temporary paths, moving versions, ephemeral identifiers, and copied state that will rot. diff --git a/bin/fm-bootstrap.sh b/bin/fm-bootstrap.sh index c86b7e839a..b7e2de796b 100755 --- a/bin/fm-bootstrap.sh +++ b/bin/fm-bootstrap.sh @@ -864,10 +864,26 @@ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] && [ -n "$crew" ] && [ "$crew" != echo "BOOTSTRAP_INFO: crew harness override active: $crew" fi crew_dispatch_validate -if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] \ - && ! fm_backlog_backend_manual "$CONFIG" && fm_tasks_axi_compatible; then - echo "BOOTSTRAP_INFO: tasks-axi available" -fi +backlog_backend=$(fm_backlog_backend_value "$CONFIG") +case "$backlog_backend" in + beads) + if ! command -v task >/dev/null 2>&1; then + echo "MISSING: task CLI (beads store; install: $(install_cmd task))" + elif ! task list --limit 1 >/dev/null 2>&1; then + echo "MISSING: task store is unreachable or broken (beads backend configured, cannot run 'task list')" + elif [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ]; then + echo "BOOTSTRAP_INFO: beads task store available" + fi + ;; + manual) + : # manual backend requires no validation + ;; + *) + if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] && fm_tasks_axi_compatible; then + echo "BOOTSTRAP_INFO: tasks-axi available" + fi + ;; +esac if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ]; then secondmate_liveness_sweep secondmate_sync diff --git a/bin/fm-session-start.sh b/bin/fm-session-start.sh index 1abbace4bf..b42f847588 100755 --- a/bin/fm-session-start.sh +++ b/bin/fm-session-start.sh @@ -192,10 +192,25 @@ print_backlog_tasks_axi_compact() { fi } +print_backlog_beads_compact() { + local out rc + printf 'compact backlog listing (beads task store; max %s item(s))\n' "$BACKLOG_LIMIT" + out=$(task list --label "status:ready" --limit "$BACKLOG_LIMIT" 2>&1) + rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$out" + else + printf 'beads task listing failed: %s\n' "$out" + fi +} + print_backlog_compact() { local path=$1 label=$2 subsection "$label" - if [ -f "$path" ]; then + if fm_beads_backend_available "$CONFIG"; then + print_backlog_beads_compact + print_backlog_pointer + elif [ -f "$path" ]; then if [ -s "$path" ]; then if fm_tasks_axi_backend_available "$CONFIG"; then print_backlog_tasks_axi_compact "$path" diff --git a/bin/fm-tasks-axi-lib.sh b/bin/fm-tasks-axi-lib.sh index 54d02fcc9e..67447e05b4 100644 --- a/bin/fm-tasks-axi-lib.sh +++ b/bin/fm-tasks-axi-lib.sh @@ -72,5 +72,13 @@ fm_backlog_backend_manual() { fm_tasks_axi_backend_available() { local config_dir=$1 fm_backlog_backend_manual "$config_dir" && return 1 + [ "$(fm_backlog_backend_value "$config_dir")" = beads ] && return 1 fm_tasks_axi_compatible } + +fm_beads_backend_available() { + local config_dir=$1 + [ "$(fm_backlog_backend_value "$config_dir")" = beads ] || return 1 + command -v task >/dev/null 2>&1 || return 1 + task list --limit 1 >/dev/null 2>&1 +} diff --git a/docs/configuration.md b/docs/configuration.md index 1b8ea3bb59..9f7ae54bab 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -42,9 +42,13 @@ Because bootstrap requires `tasks-axi` on `PATH` on every profile, that delegati Compatible means the shared bootstrap probe accepts `tasks-axi --version` as 0.1.1 or newer, `tasks-axi update --help` exposes `--archive-body`, and `tasks-axi mv --help` exposes `[...]` for the atomic multi-ID move introduced in 0.2.2 and required by handoff delegation. That sentence is the single owner of the tasks-axi compatibility definition; every other document points here instead of restating the version gates. Bootstrap requires compatible `tasks-axi` on every profile; see "Toolchain" below for missing-tool reporting and silent default-backend behavior. +Set the local, gitignored `config/backlog-backend` file to `beads` to use the beads federated `task` store as the queue source; session-start's digest will list items with `status:ready` label from the beads store instead of `data/backlog.md`. +Beads requires the `task` CLI on `PATH` and access to the active beads store. +Bootstrap validates the beads backend and reports a `MISSING:` line if the CLI is absent or the store is unreachable. Set the local, gitignored `config/backlog-backend` file to `manual` to force manual backlog editing and suppress the verbose `BOOTSTRAP_INFO: tasks-axi available` fact, not missing-tool reporting. Absent or `tasks-axi` selects the default tasks-axi backend. -The file format is unchanged in both modes; tasks-axi and manual edits produce the same `## In flight`, `## Queued`, and `## Done` sections. +The file format is unchanged in tasks-axi and manual modes; both produce the same `## In flight`, `## Queued`, and `## Done` sections in `data/backlog.md`. +The beads backend does not use `data/backlog.md`; all backlog state lives in the beads store and is queried dynamically at session start. ## Runtime backend (config/backend / FM_BACKEND) From 2a55e67f12feb9f9e244cc74b0e6934ec068b7e4 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Fri, 31 Jul 2026 01:20:09 -0700 Subject: [PATCH 2/4] test: add beads backend integration tests Tests for: - fm_backlog_backend_value() reading beads config - fm_beads_backend_available() checking task CLI and store - fm_tasks_axi_backend_available() returning false when beads is set - whitespace handling in backend config values --- tests/fm-beads-backend.test.sh | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/fm-beads-backend.test.sh diff --git a/tests/fm-beads-backend.test.sh b/tests/fm-beads-backend.test.sh new file mode 100644 index 0000000000..6d14028867 --- /dev/null +++ b/tests/fm-beads-backend.test.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# tests/fm-beads-backend.test.sh - beads as third backlog backend option. +# Tests backend selection and availability checks. +set -u + +# shellcheck source=tests/lib.sh disable=SC1091 +. "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +# shellcheck source=bin/fm-tasks-axi-lib.sh disable=SC1091 +. "$(dirname "${BASH_SOURCE[0]}")/../bin/fm-tasks-axi-lib.sh" + +TMP_ROOT=$(fm_test_tmproot fm-beads-backend) + +# Test: fm_backlog_backend_value() returns beads when configured +test_beads_backend_value() { + local config="$TMP_ROOT/config" + mkdir -p "$config" + + # Test default (tasks-axi) + value=$(fm_backlog_backend_value "$config") + [ "$value" = "tasks-axi" ] || fail "default backend should be tasks-axi, got: $value" + + # Test explicit beads + printf '%s' 'beads' > "$config/backlog-backend" + value=$(fm_backlog_backend_value "$config") + [ "$value" = "beads" ] || fail "beads backend when configured should return beads, got: $value" + + # Test manual + printf '%s' 'manual' > "$config/backlog-backend" + value=$(fm_backlog_backend_value "$config") + [ "$value" = "manual" ] || fail "manual backend when configured should return manual, got: $value" +} + +# Test: fm_beads_backend_available() checks config and task CLI +test_beads_backend_available() { + local config="$TMP_ROOT/config-beads" + mkdir -p "$config" + + # Test: beads not configured - should return false + printf '%s' 'tasks-axi' > "$config/backlog-backend" + if fm_beads_backend_available "$config"; then + fail "fm_beads_backend_available should return false when beads not configured" + fi + + # Test: beads configured but task CLI not found - should return false + printf '%s' 'beads' > "$config/backlog-backend" + if ! command -v task >/dev/null 2>&1; then + if fm_beads_backend_available "$config"; then + fail "fm_beads_backend_available should return false when task CLI not found" + fi + return 0 + fi + + # Test: beads configured and task CLI found - should return true if store is reachable + if ! task list --limit 1 >/dev/null 2>&1; then + # Store not reachable in test environment - that's OK + return 0 + fi + if ! fm_beads_backend_available "$config"; then + fail "fm_beads_backend_available should return true when beads configured and task CLI works" + fi +} + +# Test: fm_tasks_axi_backend_available() returns false when beads is configured +test_tasks_axi_backend_false_for_beads() { + local config="$TMP_ROOT/config-axi" + mkdir -p "$config" + + printf '%s' 'beads' > "$config/backlog-backend" + + if fm_tasks_axi_backend_available "$config"; then + fail "fm_tasks_axi_backend_available should return false when beads is configured" + fi +} + +# Test: backend value handles whitespace +test_backend_value_whitespace() { + local config="$TMP_ROOT/config-ws" + mkdir -p "$config" + + printf '%s' ' beads ' > "$config/backlog-backend" + value=$(fm_backlog_backend_value "$config") + [ "$value" = "beads" ] || fail "backend value should strip whitespace, got: $value" +} + +# Run all tests +test_beads_backend_value +test_beads_backend_available +test_tasks_axi_backend_false_for_beads +test_backend_value_whitespace + +echo "ok - all beads backend tests passed" From 25c66b3def6ac465b6d7a7cc5b3a0aaf430d8e05 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Fri, 31 Jul 2026 01:25:19 -0700 Subject: [PATCH 3/4] fix: add install_cmd support for task (beads CLI) The install_cmd() function now recognizes 'task' and provides an install command for the beads CLI tool. --- bin/fm-bootstrap.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/fm-bootstrap.sh b/bin/fm-bootstrap.sh index b7e2de796b..9e26d2f109 100755 --- a/bin/fm-bootstrap.sh +++ b/bin/fm-bootstrap.sh @@ -489,6 +489,7 @@ install_cmd() { no-mistakes) echo "curl -fsSL https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh | sh" ;; gh-axi|chrome-devtools-axi|lavish-axi) echo "npm install -g $1 && $1 setup hooks" ;; tasks-axi|quota-axi) echo "npm install -g $1" ;; + task) echo "go install github.com/steveyegge/beads/cmd/bd@latest # task is the beads CLI" ;; *) return 1 ;; esac } From 5023ef97aeab4ab64ae9ad75337c1c862f229474 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Fri, 31 Jul 2026 01:27:37 -0700 Subject: [PATCH 4/4] fix: make print_backlog_pointer() backend-aware Update print_backlog_pointer() to provide backend-specific guidance: - beads backend: suggest 'task show ' for beads task store - manual backend: suggest 'inspect data/backlog.md' - default/tasks-axi: original message with tasks-axi and data/backlog.md --- bin/fm-session-start.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/fm-session-start.sh b/bin/fm-session-start.sh index b42f847588..f0788070f8 100755 --- a/bin/fm-session-start.sh +++ b/bin/fm-session-start.sh @@ -136,7 +136,19 @@ print_file_or_absent() { } print_backlog_pointer() { - printf 'Full task bodies remain available on demand: tasks-axi show --full when compatible tasks-axi is available, or data/backlog.md.\n' + local backend + backend=$(fm_backlog_backend_value "$CONFIG") + case "$backend" in + beads) + printf 'Full task bodies remain available on demand: task show (beads task store).\n' + ;; + manual) + printf 'Full task bodies remain available on demand: inspect data/backlog.md.\n' + ;; + *) + printf 'Full task bodies remain available on demand: tasks-axi show --full when compatible tasks-axi is available, or data/backlog.md.\n' + ;; + esac } print_backlog_manual_compact() {