Skip to content

feat(beads): auto-link every spawn to a bead under the beads backlog backend - #37

Merged
trillium merged 6 commits into
mainfrom
fm/beads-migration-s3-auto-beadlink
Aug 2, 2026
Merged

trillium merged 6 commits into
mainfrom
fm/beads-migration-s3-auto-beadlink

Conversation

@trillium

@trillium trillium commented Aug 2, 2026 •

Copy link
Copy Markdown
Owner

Intent

Stage 3 of the beads-authority migration: make bead-linking automatic and mandatory on every spawn when config/backlog-backend=beads, so firstmate's own work is always represented in the beads store (previously beads_id= was opt-in via --beads only). fm-spawn.sh and fm-brief.sh now auto-resolve/mint a bead via fm_beads_resolve_or_create (fm-tasks-axi-lib.sh) when the beads backend is active and no explicit --beads/FM_HOOK_BEADS_ID was given, applying the existing claim-first/close-last bead lifecycle to every dispatch. Also fixes a pre-existing gap where fm-brief.sh's documented hook-sourcing system (fm-brief-hooks.d/*.sh) was never actually wired into the code, so the Bead Receipt/Closure sections could never render. Default tasks-axi/manual backend behavior is unchanged; --beads remains opt-in there.

What Changed

  • bin/fm-spawn.sh and bin/fm-brief.sh now auto-resolve/mint a bead via a new fm_beads_resolve_or_create helper in bin/fm-tasks-axi-lib.sh whenever config/backlog-backend=beads is active and no explicit --beads/FM_HOOK_BEADS_ID was supplied, applying the existing claim-first/close-last bead lifecycle to every dispatch instead of only opted-in ones; bead minting is deliberately deferred to spawn time (not brief scaffold) to avoid orphaned beads when a brief is never spawned.
  • bin/fm-brief.sh wires up the previously-inert fm-brief-hooks.d/*.sh sourcing so injected Bead Receipt/Closure sections actually render for auto-linked briefs, and bin/fm-spawn.sh/bin/fm-teardown.sh/bin/fm-bead-stamp.sh were updated to stamp and close these auto-linked beads.
  • Added tests/fm-beads-backend.test.sh, tests/fm-brief.test.sh, and tests/fm-spawn-beads.test.sh, and synced AGENTS.md, docs/configuration.md, and docs/scripts.md to document the automatic bead-linking behavior; default tasks-axi/manual backend behavior is unchanged.

Risk Assessment

✅ Low: Both substantive issues from earlier rounds (orphan-bead creation at scaffold time, and missing Bead Receipt/Closure sections for auto-linked spawns) have been correctly fixed with matching test coverage; the remaining diff is well-contained, fails open consistently with the rest of the codebase, and no new material bugs were found in this pass.

Testing

Ran the three new/updated automated suites (fm-beads-backend, fm-brief, fm-spawn-beads) plus adjacent spawn/ledger regression suites — all pass — and additionally drove the real fm-brief.sh and fm-spawn.sh scripts end-to-end in an isolated sandbox (bypassing only the no-mistakes gate-agent refusal the same way firstmate's own test harness does) to produce a genuine generated brief showing the auto-minted bead id, the spliced-in Bead Receipt/Closure sections positioned correctly before # Setup, the recorded beads_id= in spawn metadata, and the expected list→create→claim-lifecycle-stamp sequence of beads CLI calls, directly demonstrating the described automatic/mandatory bead-linking behavior as a real dispatcher would experience it.

Evidence: Generated brief with auto-injected Bead Receipt/Closure sections
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# FIRST ACTION: enroll in Parlay
Start by enrolling in Parlay so firstmate can reach you and you can report back; this only starts a background listener and touches nothing in the repo, so the Setup isolation check below still governs every repo action.
Enrollment is one atomic, idempotent call that registers you, announces you are listening, and streams firstmate's messages to you: `parlay listen --agent manual-beads-demo-1`.
Run it as a persistent background listener that stays alive for the whole task: under a harness with a Monitor tool that is `Monitor({ command: "parlay listen --agent manual-beads-demo-1", persistent: true })`, otherwise start it in the background and keep it running.
Enrollment is best-effort, never a blocker: if parlay is not installed or the Parlay server is unreachable, note the warning briefly and continue with your task normally - do not stop and do not append a blocked status. A missing coordination channel is not a failure.

# Task
{TASK}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Bead Receipt
This task is linked to bead `bd-2f91a`.
Before anything else - your first action, before the setup below - prove you received and read this brief:
`` `
task set-state bd-2f91a dispatch=claimed --reason 'brief read and accepted'
task set-state bd-2f91a lifecycle=claimed --reason 'brief read and accepted'
`` `

# Bead Closure
Before appending `done:` to the status file, close this bead: `task close bd-2f91a`.
That closure is what a registered watcher check uses to trigger your cleanup - do this as the last step before reporting done.
If you cannot reach this step, do not worry about it further: firstmate closes this bead automatically once your work is confirmed landed and this task is torn down.

# Setup
You are in a disposable git worktree of project, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/manual-beads-demo-1`
2. Run `no-mistakes doctor`; if it reports the repo is not initialized here, run `no-mistakes init`.

# Rules
1. Never push to the default branch. Never merge a PR.
2. Stay inside this worktree; modify nothing outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/tmp/fm-manual-beads.dtK7XK/home/state/manual-beads-demo-1.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): firstmate then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will apply the configured authority and reply with the decision.
   When firstmate replies or a blocker clears and you resume, append `resolved: {how it was decided or unblocked}` (add the same `[key=<slug>]` if you opened it with one) so the decision or blocker is durably closed and does not keep resurfacing.
7. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/Users/trilliumsmith/.no-mistakes/worktrees/afb8487de4ac/01KZ0H2QAKJKXB0ATREB1ZT4CC/bin/fm-ensure-agents-md.sh .` in the worktree.
Record only project knowledge useful to almost every future session.
For anything the codebase already shows, prefer a pointer to the authoritative file, command, or doc over copying the detail.
If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/Users/trilliumsmith/.no-mistakes/worktrees/afb8487de4ac/01KZ0H2QAKJKXB0ATREB1ZT4CC/bin/fm-ensure-agents-md.sh` in the same pass.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Definition of done
The task is complete only when committed on your branch.
When you believe it is complete, append `done: {summary}` to the status file and stop.
Firstmate will then instruct you to run /no-mistakes to validate and ship a PR.

You drive no-mistakes by responding to its gates, not by implementing fixes.
Follow the guidance no-mistakes itself provides for the mechanics: it loads when you invoke /no-mistakes, and `no-mistakes axi run --help` plus the `help` lines in each `axi` response are authoritative and version-matched to the installed binary.
Do not hand-edit, commit, or fix findings yourself while a run is active - the pipeline applies every fix.

Two firstmate-specific rules layer on top of that guidance:
- ask-user findings are never yours to answer: escalate to firstmate (rule 6) and stop.
  Firstmate applies the authority contract in its `AGENTS.md` and obtains any required captain decision.
  When the decision comes back, feed it to the gate with `no-mistakes axi respond` and let the pipeline apply it - do not route the question to "the user" or implement the fix yourself.
- Avoid `--yes`: it would silently bypass firstmate's authority check and any required captain escalation.

After /no-mistakes reports CI green (the CI-ready return point - do not wait for it to keep monitoring in the background until merge), append `done: PR {url} checks green` and stop. You are finished.
Evidence: Spawn task metadata showing auto-recorded beads_id=
window=firstmate:fm-manual-beads-demo-1
endpoint_task_id=manual-beads-demo-1
worktree=/tmp/fm-manual-beads.dtK7XK/wt
project=/tmp/fm-manual-beads.dtK7XK/project
harness=codex
kind=ship
mode=no-mistakes
yolo=off
tasktmp=/tmp/fm-manual-beads-demo-1
model=default
effort=default
beads_id=bd-2f91a
Evidence: Beads CLI call transcript (lookup/mint/claim-lifecycle stamp)
list --label task:manual-beads-demo-1 --all --limit 1 --json
create --title firstmate: manual-beads-demo-1 --labels fleet:firstmate,task:manual-beads-demo-1 --silent
show bd-2f91a
set-state bd-2f91a dispatch=sent --reason dispatched: agent=manual-beads-demo-1
set-state bd-2f91a lifecycle=sent --reason dispatched: agent=manual-beads-demo-1
assign bd-2f91a manual-beads-demo-1

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (2) ✅
  • ⚠️ bin/fm-brief.sh:281 - fm-brief.sh now mints a bead (fm_beads_resolve_or_create) at brief-scaffold time under config/backlog-backend=beads, before the task is spawned. If the brief is generated but the task is never spawned (captain declines after review, spawn fails, task id abandoned), the bead gets created with no beads_id= ever recorded in any state/<id>.meta, so fm-teardown.sh's close_linked_bead (bin/fm-teardown.sh:347-360) never runs for it — the bead is permanently orphaned/unclosed in the shared federated store. Previously bead creation was opt-in only at spawn time, so this class of orphan didn't exist.
  • ℹ️ bin/fm-tasks-axi-lib.sh:93 - Comment says 'As of Stage 1, only reads (fm-fleet-snapshot.sh) use this label; no code in bin/ creates a bead with it yet.' This is now false: fm_beads_resolve_or_create, added a few lines below in this same diff (Stage 3), creates beads carrying this exact label via fm_beads_fleet_label.
  • ℹ️ docs/configuration.md:62 - docs/configuration.md states 'as of Stage 1 no bin/ code creates a bead with that label yet, only the reads below query by it.' This is now stale given this diff's fm_beads_resolve_or_create wiring in fm-spawn.sh and fm-brief.sh, which mints beads carrying the fleet:firstmate label.
  • ℹ️ bin/fm-tasks-axi-lib.sh:110 - fm_beads_resolve_or_create's lookup-then-create (task list --label ... then task create) is not atomic. fm-brief.sh calls it unlocked, fm-spawn.sh calls it under a per-task spawn lock; if brief.sh and spawn.sh (or two brief.sh invocations) ever race for the same task id, two distinct beads could both be minted with the same task:<id> label since there's no compare-and-swap. Low practical risk given the documented sequential brief-then-spawn workflow, but worth noting since the function is explicitly designed to converge callers onto one bead.

🔧 Fix: {"summary": "Defer beads auto-minting from brief scaffold to spawn"}
2 issues (1 warning, 1 info) still open:

  • ⚠️ bin/fm-spawn.sh:464 - Under auto-linked beads-backend spawns (no explicit --beads), the crewmate's brief.md is fully scaffolded by fm-brief.sh before fm-spawn.sh mints/resolves the bead (bead resolution was deliberately deferred to fm-spawn.sh by the fix commit to avoid orphan beads). fm-spawn.sh only reads an already-existing brief and never appends to it, so the '# Bead Receipt'/'# Bead Closure' sections (which instruct the worker to run task set-state &lt;id&gt; dispatch=claimed/lifecycle=claimed and close the bead) never render for the common auto-linked case. The bead sits at dispatch=sent/lifecycle=sent until fm-teardown.sh force-closes it at landing, skipping the worker-confirmed 'claimed' step entirely. This contradicts fm-spawn.sh's own header comment (lines 103-107) claiming the claim/close lifecycle 'applies to every dispatch, not just opted-in ones.' Untested by tests/fm-spawn-beads.test.sh.
  • ℹ️ bin/fm-tasks-axi-lib.sh:105 - fm-spawn.sh header comment (line 107) and fm-tasks-axi-lib.sh's fm_beads_resolve_or_create docstring (line 105, 'so fm-brief.sh and fm-spawn.sh converge on the same bead regardless of call order') are now stale/inaccurate given the fix commit removed fm-brief.sh's call to this function.

🔧 Fix: {"summary": "Inject bead hook sections into auto-linked briefs at spawn"}
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-beads-backend.test.sh tests/fm-brief.test.sh tests/fm-spawn-beads.test.sh (all pass: minting/reuse via fm_beads_resolve_or_create, brief scaffold defers bead minting to spawn, auto-link injects Bead Receipt/Closure before # Setup, explicit --beads wins and avoids duplicate sections)
  • bin/fm-test-run.sh tests/fm-spawn-reused-worktree-hooks.test.sh tests/fm-spawn-dispatch-profile.test.sh tests/fm-ledger.test.sh (regression check on adjacent spawn/bead-lifecycle behavior, all pass)
  • Manual end-to-end run of the real bin/fm-brief.sh then bin/fm-spawn.sh (via FM_GATE_REFUSE_BYPASS=1, the same escape hatch the test suite uses, against an isolated temp-sandbox home/project/worktree with faked tmux/treehouse/task binaries) under config/backlog-backend=beads with no --beads flag: confirmed the brief scaffolded with no Bead sections yet, then after spawn the brief.md had # Bead Receipt / # Bead Closure sections referencing the auto-minted bead id spliced in before # Setup, state/<id>.meta recorded beads_id=bd-2f91a, and the fake beads CLI log showed list (lookup) -> create (mint) -> show/set-state dispatch=sent/lifecycle=sent/assign (claim-first lifecycle stamp)
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed (2) ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: summary: suppress SC1090 for dynamic hook sourcing in fm-spawn.sh and fm-brief.sh
1 warning still open:

  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Avoid ShellCheck crash on hook-loop source directive in fm-brief.sh
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

Summary by CodeRabbit

  • New Features

    • Automatically links eligible tasks to existing or newly created backlog items when the beads backend is enabled.
    • Adds bead details and lifecycle instructions to generated task briefs.
    • Supports hook content in generated briefs, while safely handling unavailable or failing hooks.
  • Bug Fixes

    • Prevents duplicate hook sections and duplicate backlog items.
    • Preserves explicit bead selections and excludes secondmate tasks from automatic linking.
  • Documentation

    • Updated configuration and script documentation to describe automatic bead linking and lifecycle behavior.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@trillium, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 319d41d2-42d6-4904-989c-7806635fced4

📥 Commits

Reviewing files that changed from the base of the PR and between 20fde45 and 6e363d4.

📒 Files selected for processing (11)
  • AGENTS.md
  • bin/fm-bead-stamp.sh
  • bin/fm-brief.sh
  • bin/fm-spawn.sh
  • bin/fm-tasks-axi-lib.sh
  • bin/fm-teardown.sh
  • docs/configuration.md
  • docs/scripts.md
  • tests/fm-beads-backend.test.sh
  • tests/fm-brief.test.sh
  • tests/fm-spawn-beads.test.sh
📝 Walkthrough

Walkthrough

The beads backend now auto-resolves or creates beads for ship and scout spawns. Spawn metadata records the bead ID, generated briefs receive lifecycle hook content, and teardown documentation describes confirmed closure behavior. Explicit bead links remain supported.

Changes

Beads lifecycle integration

Layer / File(s) Summary
Bead resolve-or-create helper
bin/fm-tasks-axi-lib.sh, docs/scripts.md, tests/fm-beads-backend.test.sh
The task library resolves beads by task:<task_id> or creates labeled beads. Tests cover both paths.
Brief hook scaffolding
bin/fm-brief.sh, tests/fm-brief.test.sh, AGENTS.md
Brief generation collects executable hook output when an explicit bead ID is set. Tests cover beads-backed, default-backend, ship, scout, and secondmate scaffolds.
Spawn and lifecycle linkage
bin/fm-spawn.sh, bin/fm-bead-stamp.sh, bin/fm-teardown.sh, docs/configuration.md, tests/fm-spawn-beads.test.sh, AGENTS.md
Beads-backed ship and scout spawns auto-link beads, record metadata, inject receipt and closure sections, and preserve explicit --beads precedence.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant fm_spawn
  participant fm_tasks_axi_lib
  participant task
  participant fm_brief
  fm_spawn->>fm_tasks_axi_lib: resolve or create bead
  fm_tasks_axi_lib->>task: find or create labeled bead
  task-->>fm_spawn: return bead ID
  fm_spawn->>fm_brief: inject bead hook sections
  fm_spawn->>fm_spawn: record metadata and lifecycle state
Loading

Possibly related PRs

Suggested reviewers: kunchenguid, karotkriss

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes automatic bead linking for spawns under the beads backlog backend.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fm/beads-migration-s3-auto-beadlink

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@trillium
trillium force-pushed the fm/beads-migration-s3-auto-beadlink branch from 04b8671 to 20fde45 Compare August 2, 2026 08:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/fm-spawn.sh`:
- Around line 465-466: Move the BEADS_ARG assignment invoking
fm_beads_resolve_or_create out of the early preflight block and place it after
all non-mutating checks, once project resolution succeeds and immediately before
launch. Preserve the existing conditions for resolving or creating beads, and
add a regression case that forces project resolution failure and verifies task
create is not invoked.
- Around line 461-467: Update the automatic beads-linking branch in fm-spawn.sh
to return a clear nonzero error when fm_beads_resolve_or_create fails, rather
than clearing BEADS_ARG and continuing; preserve AUTO_BEADS_LINKED only after
successful resolution or creation. In bin/fm-spawn.sh lines 103-110 and
docs/configuration.md lines 64-66, retain the beads_id guarantee and
automatic-linking statement, respectively, once failed linkage is rejected, and
add regression coverage for missing dependencies, inaccessible storage, or
bead-creation failures.
- Around line 465-467: Update the argument-precedence logic in bin/fm-spawn.sh
so a non-empty FM_HOOK_BEADS_ID sets BEADS_ARG and marks BEADS_SET before the
auto-linking condition. Ensure the existing fm_beads_resolve_or_create path only
runs when neither --beads nor the external hook bead ID provided an explicit
bead.

In `@bin/fm-tasks-axi-lib.sh`:
- Around line 100-116: The fm_beads_resolve_or_create lookup currently treats
task-list and jq failures as empty results, risking duplicate creation and
invalid null IDs. Update this function to return failure when task list fails,
JSON parsing fails, the response is not a valid array, or an existing bead lacks
a non-null ID; only invoke bead creation for a successfully parsed, valid empty
array, and add regression tests covering failed lookup, malformed JSON, and
missing bead IDs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 60c40b57-6222-4dfb-99bf-733f0bff35d0

📥 Commits

Reviewing files that changed from the base of the PR and between 139db80 and 20fde45.

📒 Files selected for processing (11)
  • AGENTS.md
  • bin/fm-bead-stamp.sh
  • bin/fm-brief.sh
  • bin/fm-spawn.sh
  • bin/fm-tasks-axi-lib.sh
  • bin/fm-teardown.sh
  • docs/configuration.md
  • docs/scripts.md
  • tests/fm-beads-backend.test.sh
  • tests/fm-brief.test.sh
  • tests/fm-spawn-beads.test.sh

Comment thread bin/fm-spawn.sh
Comment on lines +461 to +467
# entities, not backlog work items, so they stay exempt. Fails open: a resolve
# failure (task/jq missing, store unreachable) leaves BEADS_ARG empty and spawn
# proceeds exactly as it did before this backend existed.
AUTO_BEADS_LINKED=0
if [ "$BEADS_SET" -eq 0 ] && [ "$KIND" != secondmate ] && [ "$(fm_backlog_backend_value "$CONFIG")" = beads ]; then
BEADS_ARG=$(fm_beads_resolve_or_create "$ID") || BEADS_ARG=
[ -z "$BEADS_ARG" ] || AUTO_BEADS_LINKED=1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not continue when required bead resolution fails.

With config/backlog-backend=beads, a failed task or jq dependency, store read, or bead creation clears BEADS_ARG and returns a successful spawn. That task has no beads_id=, dispatch stamp, or confirmed-close lifecycle. This conflicts with the documented automatic-linking guarantee.

  • bin/fm-spawn.sh#L461-L467: return a clear nonzero error when automatic resolution or creation fails. Add regression coverage for this failure path.
  • bin/fm-spawn.sh#L103-L110: retain the beads_id= guarantee only after the spawn path rejects failed automatic linkage.
  • docs/configuration.md#L64-L66: retain the automatic-linking statement only after the implementation rejects failed automatic linkage.

As per PR objectives, beads-backed spawns must resolve or create a bead and record beads_id=. Based on learnings, do not silently continue after a missing dependency or inaccessible backend.

📍 Affects 2 files
  • bin/fm-spawn.sh#L461-L467 (this comment)
  • bin/fm-spawn.sh#L103-L110
  • docs/configuration.md#L64-L66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-spawn.sh` around lines 461 - 467, Update the automatic beads-linking
branch in fm-spawn.sh to return a clear nonzero error when
fm_beads_resolve_or_create fails, rather than clearing BEADS_ARG and continuing;
preserve AUTO_BEADS_LINKED only after successful resolution or creation. In
bin/fm-spawn.sh lines 103-110 and docs/configuration.md lines 64-66, retain the
beads_id guarantee and automatic-linking statement, respectively, once failed
linkage is rejected, and add regression coverage for missing dependencies,
inaccessible storage, or bead-creation failures.

Source: Learnings

Comment thread bin/fm-spawn.sh
Comment on lines +465 to +466
if [ "$BEADS_SET" -eq 0 ] && [ "$KIND" != secondmate ] && [ "$(fm_backlog_backend_value "$CONFIG")" = beads ]; then
BEADS_ARG=$(fm_beads_resolve_or_create "$ID") || BEADS_ARG=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Resolve the bead after all spawn preflight checks.

These lines can mint a bead before project resolution. If project resolution or a later preflight fails, the new bead has no task metadata link and no teardown closure path.

Move resolution after non-mutating preflight checks and before launch. Add a regression case that forces project resolution to fail and asserts that task create was not called.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-spawn.sh` around lines 465 - 466, Move the BEADS_ARG assignment
invoking fm_beads_resolve_or_create out of the early preflight block and place
it after all non-mutating checks, once project resolution succeeds and
immediately before launch. Preserve the existing conditions for resolving or
creating beads, and add a regression case that forces project resolution failure
and verifies task create is not invoked.

Comment thread bin/fm-spawn.sh
Comment on lines +465 to +467
if [ "$BEADS_SET" -eq 0 ] && [ "$KIND" != secondmate ] && [ "$(fm_backlog_backend_value "$CONFIG")" = beads ]; then
BEADS_ARG=$(fm_beads_resolve_or_create "$ID") || BEADS_ARG=
[ -z "$BEADS_ARG" ] || AUTO_BEADS_LINKED=1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Read the script header and inline option help before inspecting option state.
sed -n '1,220p' bin/fm-spawn.sh

# Trace initialization and use of explicit bead inputs.
rg -n -C 4 'FM_HOOK_BEADS_ID|BEADS_SET|BEADS_ARG|--beads|Usage:' \
  bin/fm-spawn.sh tests/fm-spawn-beads.test.sh

Repository: trillium/firstmate

Length of output: 34688


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== script outline around globals/default hooks =="
sed -n '220,280p' bin/fm-spawn.sh
sed -n '860,925p' bin/fm-spawn.sh
sed -n '950,1100p' bin/fm-spawn.sh

echo
echo "== FM_HOOK_BEADS_ID occurrences in repo =="
rg -n 'FM_HOOK_BEADS_ID|hook.*beads|beads.*hook|Bead Receipt|Bead Closure|--beads' . \
  -g '!tests/fm-spawn-beads.test.sh' -g '!bin/fm-spawn.sh' -g '!**/node_modules/**' -g '!**/dist/**' || true

echo
echo "== all occurrences of FM_HOOK_BEADS_ID in tracked files =="
rg -n 'FM_HOOK_BEADS_ID' bin tests lib scripts 2>/dev/null || true

echo
echo "== behavioral/static probe for external env precedence =="
python3 - <<'PY'
from pathlib import Path
p=Path('bin/fm-spawn.sh')
s=p.read_text()
checks={
    'initializes BEADS_ARG empty': 'BEADS_ARG=' in s[s.index('KIND=ship'):s.index('for a in "$@"; do')],
    'initializes BEADS_SET zero': 'BEADS_SET=0' in s[s.index('KIND=ship'):s.index('for a in "$@"; do')],
    'opts parser reads --beads value and flag': '--beads) want_value=beads' in s and '--beads=*) BEADS_ARG=${a#--beads=}; BEADS_SET=1' in s,
    'opts parser reads short -b value and flag': '-b) want_value=beads' in s and '-b=*) BEADS_ARG=${a#-b=}; BEADS_SET=1' in s,
    'opts parser rejects empty --beads': '--beads requires a non-empty value' in s and 'if [ "$BEADS_SET" -eq 0 ] || [ -z "$BEADS_ARG" ]' not in s,
    'auto-resolution gate depends on BEADS_SET': '[ "$BEADS_SET" -eq 0 ] && [ "$KIND" != secondmate ] && [ "$(fm_backlog_backend_value "$CONFIG")" = beads ];' in s,
    'external hook id referenced in README/comments': 'FM_HOOK_BEADS_ID' in s,
    'no case sets BEADS_SET from external env': 'FM_HOOK_BEADS_ID' not in s[:s.index('for a in "$@"; do')] and '\n  FM_HOOK_BEADS_ID' not in s[s.index('KIND=ship'):s.index('BEADS_SET=0'):],
}
for k,v in checks.items():
    print(f'{k}: {v}')
PY

Repository: trillium/firstmate

Length of output: 24312


Apply the external FM_HOOK_BEADS_ID bead ID before auto-linking.

bin/fm-spawn.sh only sets BEADS_ARG and BEADS_SET from --beads; an external FM_HOOK_BEADS_ID is still used by hooks, but it does not stop auto-linking under config/backlog-backend=beads. Handle FM_HOOK_BEADS_ID at argument-precedence time so the explicit external bead is used instead of auto-resolving a different bead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-spawn.sh` around lines 465 - 467, Update the argument-precedence logic
in bin/fm-spawn.sh so a non-empty FM_HOOK_BEADS_ID sets BEADS_ARG and marks
BEADS_SET before the auto-linking condition. Ensure the existing
fm_beads_resolve_or_create path only runs when neither --beads nor the external
hook bead ID provided an explicit bead.

Comment thread bin/fm-tasks-axi-lib.sh
@trillium
trillium force-pushed the fm/beads-migration-s3-auto-beadlink branch from 20fde45 to 940cf4b Compare August 2, 2026 08:12
Stage 3 of the beads-authority migration
(data/beads-authority-migration-scout/report.md). Under
config/backlog-backend=beads, bead-linking is the backend itself, not
an opt-in cross-reference: fm-spawn.sh and fm-brief.sh now resolve or
mint a bead labeled task:<task-id> (fm_beads_resolve_or_create in
fm-tasks-axi-lib.sh) whenever --beads/FM_HOOK_BEADS_ID isn't already
set, so every ship/scout dispatch gets a beads_id= in its meta and the
claim-first/close-last lifecycle applies automatically. An explicit
--beads still wins, and secondmate launches stay exempt. The default
tasks-axi/manual backends are unchanged: --beads remains a deliberate
opt-in there.

Also wires up fm-brief.sh's documented hook system
(fm-brief-hooks.d/*.sh sourced, stdout prepended to the brief), which
had never actually been implemented, so the Bead Receipt/Closure
sections it produces can render at all.
@trillium
trillium force-pushed the fm/beads-migration-s3-auto-beadlink branch from 940cf4b to 6e363d4 Compare August 2, 2026 10:33
@trillium
trillium merged commit 36eb824 into main Aug 2, 2026
11 checks passed
trillium added a commit that referenced this pull request Aug 6, 2026
…backend (#37)

* feat(beads): auto-link every spawn to a bead under the beads backend

Stage 3 of the beads-authority migration
(data/beads-authority-migration-scout/report.md). Under
config/backlog-backend=beads, bead-linking is the backend itself, not
an opt-in cross-reference: fm-spawn.sh and fm-brief.sh now resolve or
mint a bead labeled task:<task-id> (fm_beads_resolve_or_create in
fm-tasks-axi-lib.sh) whenever --beads/FM_HOOK_BEADS_ID isn't already
set, so every ship/scout dispatch gets a beads_id= in its meta and the
claim-first/close-last lifecycle applies automatically. An explicit
--beads still wins, and secondmate launches stay exempt. The default
tasks-axi/manual backends are unchanged: --beads remains a deliberate
opt-in there.

Also wires up fm-brief.sh's documented hook system
(fm-brief-hooks.d/*.sh sourced, stdout prepended to the brief), which
had never actually been implemented, so the Bead Receipt/Closure
sections it produces can render at all.

* no-mistakes(review): {"summary": "Defer beads auto-minting from brief scaffold to spawn"}

* no-mistakes(review): {"summary": "Inject bead hook sections into auto-linked briefs at spawn"}

* no-mistakes(document): Sync docs with automatic bead-linking under beads backlog backend

* no-mistakes(lint): summary: suppress SC1090 for dynamic hook sourcing in fm-spawn.sh and fm-brief.sh

* no-mistakes(lint): Avoid ShellCheck crash on hook-loop source directive in fm-brief.sh
trillium added a commit that referenced this pull request Aug 6, 2026
…backend (#37)

* feat(beads): auto-link every spawn to a bead under the beads backend

Stage 3 of the beads-authority migration
(data/beads-authority-migration-scout/report.md). Under
config/backlog-backend=beads, bead-linking is the backend itself, not
an opt-in cross-reference: fm-spawn.sh and fm-brief.sh now resolve or
mint a bead labeled task:<task-id> (fm_beads_resolve_or_create in
fm-tasks-axi-lib.sh) whenever --beads/FM_HOOK_BEADS_ID isn't already
set, so every ship/scout dispatch gets a beads_id= in its meta and the
claim-first/close-last lifecycle applies automatically. An explicit
--beads still wins, and secondmate launches stay exempt. The default
tasks-axi/manual backends are unchanged: --beads remains a deliberate
opt-in there.

Also wires up fm-brief.sh's documented hook system
(fm-brief-hooks.d/*.sh sourced, stdout prepended to the brief), which
had never actually been implemented, so the Bead Receipt/Closure
sections it produces can render at all.

* no-mistakes(review): {"summary": "Defer beads auto-minting from brief scaffold to spawn"}

* no-mistakes(review): {"summary": "Inject bead hook sections into auto-linked briefs at spawn"}

* no-mistakes(document): Sync docs with automatic bead-linking under beads backlog backend

* no-mistakes(lint): summary: suppress SC1090 for dynamic hook sourcing in fm-spawn.sh and fm-brief.sh

* no-mistakes(lint): Avoid ShellCheck crash on hook-loop source directive in fm-brief.sh
trillium added a commit that referenced this pull request Aug 6, 2026
…backend (#37)

* feat(beads): auto-link every spawn to a bead under the beads backend

Stage 3 of the beads-authority migration
(data/beads-authority-migration-scout/report.md). Under
config/backlog-backend=beads, bead-linking is the backend itself, not
an opt-in cross-reference: fm-spawn.sh and fm-brief.sh now resolve or
mint a bead labeled task:<task-id> (fm_beads_resolve_or_create in
fm-tasks-axi-lib.sh) whenever --beads/FM_HOOK_BEADS_ID isn't already
set, so every ship/scout dispatch gets a beads_id= in its meta and the
claim-first/close-last lifecycle applies automatically. An explicit
--beads still wins, and secondmate launches stay exempt. The default
tasks-axi/manual backends are unchanged: --beads remains a deliberate
opt-in there.

Also wires up fm-brief.sh's documented hook system
(fm-brief-hooks.d/*.sh sourced, stdout prepended to the brief), which
had never actually been implemented, so the Bead Receipt/Closure
sections it produces can render at all.

* no-mistakes(review): {"summary": "Defer beads auto-minting from brief scaffold to spawn"}

* no-mistakes(review): {"summary": "Inject bead hook sections into auto-linked briefs at spawn"}

* no-mistakes(document): Sync docs with automatic bead-linking under beads backlog backend

* no-mistakes(lint): summary: suppress SC1090 for dynamic hook sourcing in fm-spawn.sh and fm-brief.sh

* no-mistakes(lint): Avoid ShellCheck crash on hook-loop source directive in fm-brief.sh
trillium added a commit that referenced this pull request Aug 6, 2026
…backend (#37)

* feat(beads): auto-link every spawn to a bead under the beads backend

Stage 3 of the beads-authority migration
(data/beads-authority-migration-scout/report.md). Under
config/backlog-backend=beads, bead-linking is the backend itself, not
an opt-in cross-reference: fm-spawn.sh and fm-brief.sh now resolve or
mint a bead labeled task:<task-id> (fm_beads_resolve_or_create in
fm-tasks-axi-lib.sh) whenever --beads/FM_HOOK_BEADS_ID isn't already
set, so every ship/scout dispatch gets a beads_id= in its meta and the
claim-first/close-last lifecycle applies automatically. An explicit
--beads still wins, and secondmate launches stay exempt. The default
tasks-axi/manual backends are unchanged: --beads remains a deliberate
opt-in there.

Also wires up fm-brief.sh's documented hook system
(fm-brief-hooks.d/*.sh sourced, stdout prepended to the brief), which
had never actually been implemented, so the Bead Receipt/Closure
sections it produces can render at all.

* no-mistakes(review): {"summary": "Defer beads auto-minting from brief scaffold to spawn"}

* no-mistakes(review): {"summary": "Inject bead hook sections into auto-linked briefs at spawn"}

* no-mistakes(document): Sync docs with automatic bead-linking under beads backlog backend

* no-mistakes(lint): summary: suppress SC1090 for dynamic hook sourcing in fm-spawn.sh and fm-brief.sh

* no-mistakes(lint): Avoid ShellCheck crash on hook-loop source directive in fm-brief.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant