Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ state/ volatile runtime signals; gitignored
<id>.turn-ended touched by turn-end hooks
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
<id>.kimi-turnend-token firstmate-owned Kimi hook registry token for the task; removed by teardown
<id>.meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; a ship or scout also records the task's two base references as slot_base=, contribution_target=, and base_state= (bin/fm-task-base-lib.sh); an optional traceparent= only when trace context is enabled (docs/configuration.md "Trace context propagation"); kind=secondmate also records home= and projects=, plus remote_host=/remote_root=/remote_backend=/remote_herdr_session=/remote_target= for a remote route; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and the forge's pr_head= when available (GitHub pull requests and GitLab merge requests; docs/gitlab-merge-watch.md); fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; a ship or scout also records the task's two base references as slot_base=, contribution_target=, and base_state= (bin/fm-task-base-lib.sh); an optional traceparent= only when trace context is enabled (docs/configuration.md "Trace context propagation"); kind=secondmate also records home= and projects=, plus remote_host=/remote_root=/remote_backend=/remote_herdr_session=/remote_target= for a remote route; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and the forge's pr_head= when available (GitHub pull requests and GitLab merge requests; docs/gitlab-merge-watch.md); fm-pr-merge records merge_verification= plus merge_verified_head= for the head it re-verified, or merge_verification=override for an explicitly unverified merge; fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.herdr-presentation quarantinable attempt and restart-binding journal for Herdr's optional visual projection; never task or endpoint authority; see docs/herdr-backend.md "Presentation spaces"
<id>.landing private minimal landing record (pr=, forge pr_head=, project=) written by fm-teardown when a ship task is released before its PR lands; stands in for the removed meta so fm-pr-merge can still land that PR and fm-pr-check can rearm its merge watch
<id>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified X shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
Expand Down Expand Up @@ -315,6 +315,7 @@ Before deciding any ask-user finding, load `ask-user-authority`; the implementat
Never merge a red PR.
Without a current explicit captain instruction that states the concrete merge, that default stands, and standing `yolo` cannot authorize a red merge; section 1 owns when such an instruction overrides a Firstmate-written standing rule within its exact scope.
Use `bin/fm-pr-merge.sh` for every task PR merge so merge metadata is recorded, and use `bin/fm-merge-local.sh` for approved local-only landing; never call a lower-level merge command around their guards.
`bin/fm-pr-merge.sh` re-checks the pull request's current head and refuses a merge it cannot confirm is green, mergeable, and unblocked by review, naming the concrete failing condition and the head whenever GitHub supplies one; treat that refusal as the state to fix, and use its recorded `--allow-unverified` override only on a current explicit captain instruction for that concrete merge.
After an autonomous merge, give the captain a one-line full-URL or local-main outcome.

### Validate
Expand All @@ -337,7 +338,7 @@ Require the matching `resolved` event, forbid `--yes`, and require the worker to
Resume fleet supervision immediately after the decision lands.

Judge validation by the current-code-matched run step through `bin/fm-crew-state.sh`, not by shell liveness or the last status event.
Running, fixing, or CI states remain working; parked approval or fix-review states require the worker to follow the active gate help; passed or checks-passed is done; failed or cancelled is failed.
Running, fixing, or CI states remain working; parked approval or fix-review states require the worker to follow the active gate help; passed is done, and checks-passed is done only when the run's own evidence records it and blocked otherwise, because a head no check examined is unverified rather than green; failed or cancelled is failed.
A worker hand-editing, committing, aborting, or restarting during an active validation run duplicates pipeline ownership outside the supersession sequence above; steer it back to the gate response flow.
The other exception is a rebase the pipeline hands back, which the ship brief requires the worker to resolve and commit itself before returning to the gates.
The worker reports the PR when CI first becomes green rather than waiting for merge monitoring to finish.
Expand Down Expand Up @@ -435,7 +436,7 @@ When evidence uses an internal label, rewrite it before sending:
- teardown -> cleanup.
- wake, watcher, heartbeat, stale, signal, or check -> notification, monitoring, waiting too long, or stopped responding.
- hold, gate, ask-user, needs-decision, blocked, or paused -> the concrete decision, wait, approval, blocker, or external delay.
- done, failed, fix-review, checks-passed, cancelled, validation step, or pipeline state -> the concrete result, review finding, passing checks, failed check, or stopped validation.
- done, failed, fix-review, checks-passed, cancelled, validation step, or pipeline state -> the corroborated concrete result, review finding, verified checks, unverified claim, failed check, or stopped validation.
- brief -> instructions.
- crewmate -> worker, only when naming the helper matters.
- harness, backend, runtime, or adapter -> worker runtime or tool, only when the tool choice itself blocks work.
Expand Down
75 changes: 59 additions & 16 deletions bin/fm-crew-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
# checks" from "checks green, waiting on merge" (see nm_ci_checks_state) -
# a ci-step log-tail check overrides working -> done once checks read
# green, so a green PR is never silently read as still-validating.
# A terminal checks-passed is a REPORTED claim and is corroborated against
# the run's own ci log before it is repeated: a claim the evidence does not
# record reports blocked, because a head no check run examined is not work
# ready for review. See nm_ci_checks_state for the measured defect.
# 3. Reconcile the status log: if its last line says needs-decision/blocked but
# the run-step shows the run moved on, the log is deterministically stale and
# is flagged superseded. A genuinely parked run plus a needs-decision log
Expand Down Expand Up @@ -289,6 +293,14 @@ nm_effective_ci_step_status() {
# for the MOST RECENT recognized marker (the log is append-only/chronological,
# so the last match is current): green with nothing red after it means CI is
# green right now, still only waiting on merge/close.
#
# "no CI checks reported" is NOT green, and reading it as green is the defect
# measured on 2026-08-02: a cross-repo fork pull request holds its workflows at
# action_required until a maintainer approves them, so zero checks ever run and
# the pipeline reports that absence as a terminal success. Nothing was red
# because nothing executed. An absent verifier is a distinct state from a
# passing one and never maps to green here, so a head no verifier examined
# reaches firstmate as not-ready rather than as work ready for review.
nm_ci_checks_state() {
local run_id log_tail marker
run_id=$(strip_quotes "$(nm_field id)")
Expand All @@ -299,11 +311,15 @@ nm_ci_checks_state() {
| grep -E 'CI checks passed|no CI checks reported - still monitoring|no CI checks reported yet|checks failed|issues detected|CI checks running|base branch advanced.*re-arming CI monitor timeout' \
| tail -1)
case "$marker" in
*"checks passed"*|*"no CI checks reported - still monitoring"*) printf 'green' ;;
*"no CI checks reported yet"*|*"checks failed"*|*"issues detected"*|*"CI checks running"*|*"base branch advanced"*"re-arming CI monitor timeout"*) printf 'not-ready' ;;
*"checks passed"*) printf 'green' ;;
*"no CI checks reported - still monitoring"*|*"no CI checks reported yet"*|*"checks failed"*|*"issues detected"*|*"CI checks running"*|*"base branch advanced"*"re-arming CI monitor timeout"*) printf 'not-ready' ;;
*) printf 'unknown' ;;
esac
}

nm_ci_state_is_green() {
[ "${1:-}" = green ]
}
# Coarse fallback for cross-branch attribution. `no-mistakes axi status` (bare)
# reports the active-or-most-recent run for the CURRENT branch when one
# exists, else falls back to some other branch's run purely as informational
Expand Down Expand Up @@ -448,7 +464,29 @@ if [ "$HAVE_RUN" = 1 ]; then
if [ -n "$outcome" ]; then
case "$outcome" in
passed) RUN_STATE="done"; RUN_DETAIL="run passed: PR merged/closed" ;;
checks-passed) RUN_STATE="done"; RUN_DETAIL="checks green: PR ready for review" ;;
# checks-passed is the pipeline's REPORTED terminal claim, and on
# 2026-08-02 it was reported for a head whose check-run set was empty.
# Corroborate it against the run's own ci log before repeating it: a
# claim of green that the run's own evidence does not record is not a
# green head, and a task whose checks never ran needs firstmate rather
# than a place in the ready-for-review queue.
checks-passed)
CI_LOG_STATE=$(nm_ci_checks_state)
if nm_ci_state_is_green "$CI_LOG_STATE"; then
RUN_STATE="done"; RUN_DETAIL="checks green: PR ready for review"
else
case "$CI_LOG_STATE" in
not-ready)
RUN_STATE=blocked
RUN_DETAIL="run reported a passing result its own CI log does not record: nothing verified this head"
;;
unknown|"")
RUN_STATE=unknown
RUN_DETAIL="run reported checks-passed, but its CI log is unavailable: claim could not be corroborated"
;;
esac
fi
;;
failed) RUN_STATE=failed; RUN_DETAIL="run failed" ;;
cancelled) RUN_STATE=failed; RUN_DETAIL="run cancelled" ;;
*) RUN_STATE=unknown; RUN_DETAIL="outcome: $outcome" ;;
Expand Down Expand Up @@ -483,7 +521,7 @@ if [ "$HAVE_RUN" = 1 ]; then
case "$CI_STEP_STATUS" in
running)
CI_LOG_STATE=$(nm_ci_checks_state)
if [ "$CI_LOG_STATE" = green ]; then
if nm_ci_state_is_green "$CI_LOG_STATE"; then
RUN_STATE="done"
RUN_DETAIL="checks green: PR ready for review (still monitoring for merge/close)"
fi
Expand All @@ -498,18 +536,23 @@ if [ "$HAVE_RUN" = 1 ]; then

if [ "$RUN_STATE" = working ] && log_reports_ci_ready; then
if [ "$RUN_SOURCE" = coarse ]; then
emit "done" status-log "$(status_line_note "$LOG_LINE")${SEP}run still monitoring PR"
fi
[ -n "$CI_STEP_STATUS" ] || CI_STEP_STATUS=$(nm_effective_ci_step_status)
if [ "$RUN_STATUS" = fixing ]; then
CI_LOG_STATE=not-ready
elif [ "$CI_STEP_STATUS" = running ] && [ -z "$CI_LOG_STATE" ]; then
CI_LOG_STATE=$(nm_ci_checks_state)
elif [ "$CI_STEP_STATUS" = fixing ]; then
CI_LOG_STATE=not-ready
fi
if [ "$CI_LOG_STATE" != not-ready ]; then
emit "done" status-log "$(status_line_note "$LOG_LINE")${SEP}run still monitoring PR"
RUN_STATE=unknown
RUN_DETAIL="status log reported readiness, but coarse run data cannot corroborate the claim"
else
[ -n "$CI_STEP_STATUS" ] || CI_STEP_STATUS=$(nm_effective_ci_step_status)
if [ "$RUN_STATUS" = fixing ]; then
CI_LOG_STATE=not-ready
elif [ "$CI_STEP_STATUS" = running ] && [ -z "$CI_LOG_STATE" ]; then
CI_LOG_STATE=$(nm_ci_checks_state)
elif [ "$CI_STEP_STATUS" = fixing ]; then
CI_LOG_STATE=not-ready
fi
if nm_ci_state_is_green "$CI_LOG_STATE"; then
emit "done" status-log "$(status_line_note "$LOG_LINE")${SEP}run still monitoring PR"
elif [ -z "$CI_LOG_STATE" ] || [ "$CI_LOG_STATE" = unknown ]; then
RUN_STATE=unknown
RUN_DETAIL="status log reported readiness, but CI evidence is unavailable: claim could not be corroborated"
fi
fi
fi

Expand Down
Loading
Loading