Skip to content
Closed
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
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ config/crew-dispatch.json optional crewmate dispatch profiles; LOCAL, gitignore
config/secondmate-harness harness the PRIMARY uses to launch SECONDMATE agents, optionally followed by a model and effort token on the same line ("<harness> [<model>] [<effort>]"; section 4); LOCAL, gitignored; absent or "default" harness falls back to config/crew-harness then firstmate's own. The primary's own setting; NOT inherited into secondmate homes (secondmates do not spawn secondmates)
config/backlog-backend backlog backend override; LOCAL, gitignored; absent or "tasks-axi" = default tasks-axi backend, "manual" = force routine backlog updates to hand-editing; inherited by secondmate homes (section 10)
config/backend runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection (the runtime firstmate itself is executing inside), then tmux; tmux is the verified reference backend (docs/tmux-backend.md), while herdr, zellij, orca, and cmux are experimental spawn backends (docs/herdr-backend.md, docs/zellij-backend.md, docs/orca-backend.md, docs/cmux-backend.md) - herdr and cmux can also be selected by runtime auto-detection, zellij and orca never are (always explicit), and codex-app is not accepted; see docs/codex-app-backend.md; inherited by secondmate homes under the primary-authoritative contract in secondmate-provisioning
config/spawn-overlap duplicate-work overlap posture; LOCAL, gitignored; absent or "advisory" = surfaced and recorded but never refused, "enforce" = a live overlap needs an explicit acknowledgement, "off" = not scanned; inherited by secondmate homes; see docs/configuration.md "Duplicate-work overlap scan"
config/calm Pi Calm presentation preference; LOCAL, gitignored, and not inherited; see docs/configuration.md "Pi Calm preference"
config/startup-memory-budget primary-authoritative per-home startup-memory budget; LOCAL, gitignored, materialized as 7,500 estimated tokens by locked primary bootstrap and inherited into secondmate homes; see docs/configuration.md "Startup memory budget"
config/herdr-presentation-spaces optional "off" opt-out from, or "on" opt-in to, Herdr's default-on disposable single-task visual projection, which is unconfigured-default-on only at or above a Herdr version floor; LOCAL, gitignored; inherited by secondmate homes; see docs/herdr-backend.md "Presentation spaces"
Expand All @@ -92,7 +93,7 @@ state/ volatile runtime signals; gitignored
<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>.muse-session muse busy-source binding (sessions root plus task worktree) written by fm-spawn; removed by teardown
<id>.meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; 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 a Relay-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 intake duplicate-work result as overlap=, plus overlap_refs= and overlap_ack= when they apply (docs/configuration.md "Duplicate-work overlap scan"); 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 a Relay-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>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified Relay shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
<id>.check-trust private content binding created by fm-check-register.sh for an intentional custom check
Expand Down Expand Up @@ -283,6 +284,7 @@ Write the task-specific brief under section 11 before spawning.
### Dispatch and supervision handoff

Spawn only through `bin/fm-spawn.sh` after the profile and backend checks in section 4.
Every ship or scout spawn surfaces the open tasks, live branches, and open pull requests that share this task's subject; read that set and decide equivalence yourself, because code only lists candidates, and treat an unreadable source as an incomplete set rather than a clean one.
The spawn must resolve a genuine isolated task worktree distinct from the primary checkout; a failed isolation assertion stops the task.
After spawning, confirm the worker is processing the brief, handle any trust dialog through `harness-adapters`, and record ship or scout work as under way.
A persistent secondmate is recorded in the secondmate registry and runtime state, never as a backlog work item.
Expand Down
2 changes: 1 addition & 1 deletion bin/fm-config-inherit-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ FM_SHARED_CAPTAIN_MODE="444"
# The declared inheritable set (space-separated, config-dir-relative item paths).
# Extend here to inherit more of the primary's local config; override via the
# environment only in tests. Items must not contain whitespace.
FM_INHERITABLE_CONFIG="${FM_INHERITABLE_CONFIG:-crew-dispatch.json crew-harness backlog-backend backend herdr-presentation-spaces startup-memory-budget trace-context}"
FM_INHERITABLE_CONFIG="${FM_INHERITABLE_CONFIG:-crew-dispatch.json crew-harness backlog-backend backend herdr-presentation-spaces startup-memory-budget trace-context spawn-overlap}"

# Items whose value is a home-SESSION enablement decision rather than durable
# local configuration. They are inherited at the launch convergence point, where
Expand Down
94 changes: 94 additions & 0 deletions bin/fm-pr-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,100 @@ fm_pr_metadata_identity_parse() {
[ -n "$FM_PR_META_URL" ]
}

# List a repository's OPEN pull requests as "<number>\t<title>" rows, one per
# line, on stdout. This is the single forge reader for the OPEN-SET question -
# what work is in flight right now - asked by bin/fm-spawn.sh's intake overlap
# scan. bin/fm-research-scan.sh's --landing prover asks a different, all-state
# delivery question through its own reader; only the open-set question routes
# through here, so that question has one failure semantics rather than one per
# caller.
#
# The empty-set law binds here and is the whole reason this returns a status.
# A missing CLI, an unauthenticated CLI, a network failure, a rejected listing,
# or a listing that accounts for less than the forge's reported open total all
# return non-zero with FM_PR_LIST_ERROR set to a one-line reason.
# A caller must render that as "unknown" and NEVER as "no open requests": the
# absence of an answer is not an answer. A successful listing with no rows is
# the only thing that means no open requests, and it returns zero.
#
# The window SELF-SIZES: the caller's limit is only the initial window, and a
# forge-reported total above it re-runs the listing exactly once with a window
# covering that total, so a growing open set does not age a fixed default into
# permanent unavailability. FM_PR_LIST_CEILING (default 5000) bounds that
# re-listing; a reported total above the ceiling fails with the reason rather
# than chasing a pathological forge without bound.
#
# No --fields is passed: the default gh-axi listing already carries number,
# title, and state, and a rejected field list would fail the whole call.
fm_pr_open_request_titles() { # <repo-dir> [limit]
local repo=$1 limit=${2:-600} ceiling=${FM_PR_LIST_CEILING:-5000} out rows header counted total listed relisted=0
FM_PR_LIST_ERROR=
case "$limit" in
''|*[!0-9]*) FM_PR_LIST_ERROR="pull request limit '$limit' is not a number"; return 1 ;;
esac
[ "$limit" -gt 0 ] 2>/dev/null || { FM_PR_LIST_ERROR="pull request limit must be positive"; return 1; }
case "$ceiling" in
''|*[!0-9]*) FM_PR_LIST_ERROR="pull request ceiling '$ceiling' (FM_PR_LIST_CEILING) is not a number"; return 1 ;;
esac
[ "$ceiling" -gt 0 ] 2>/dev/null || { FM_PR_LIST_ERROR="pull request ceiling (FM_PR_LIST_CEILING) must be positive"; return 1; }
[ -d "$repo" ] || { FM_PR_LIST_ERROR="no repository directory at $repo"; return 1; }
command -v gh-axi >/dev/null 2>&1 || { FM_PR_LIST_ERROR="gh-axi is not on PATH"; return 1; }
while :; do
if ! out=$( (cd "$repo" && gh-axi pr list --state open --limit "$limit") 2>&1 ); then
FM_PR_LIST_ERROR=$(printf '%s' "$out" | head -n 1 | cut -c 1-200)
[ -n "$FM_PR_LIST_ERROR" ] || FM_PR_LIST_ERROR="gh-axi pr list failed with no output"
return 1
fi
# gh-axi prints a TOON block: a count header, a "pull_requests[N]{...}:"
# header, then one indented "<number>,\"<title>\",<state>,<author>,<draft>,
# <review>" row each. The title is matched greedily up to the last quote
# that is followed by the four trailing scalar fields, so a comma or quote
# inside a title cannot truncate it.
rows=$(printf '%s\n' "$out" \
| sed -n 's/^[[:space:]]\{1,\}\([0-9]\{1,\}\),"\(.*\)",[^,]*,[^,]*,[^,]*,[^,]*$/\1\t\2/p')
listed=$(printf '%s\n' "$rows" | grep -c .)
# The count header is "count: N of M total" when the listing is bounded
# below the open total and a bare "count: N" when it is complete. The
# invariant is that the listing must ACCOUNT FOR the whole reported open
# total: whenever the header's own listed count or the rows actually
# parsed here fall short of that total, the call FAILS, because a window
# onto the open set must never read as the open set. Without a parseable
# total the total is unknown rather than proven, so a listing that filled
# its whole window is refused too; only a listing smaller than its window
# - which no limit can have cut - passes without a total.
header=$(printf '%s\n' "$out" \
| sed -n 's/^count:[[:space:]]*\([0-9]\{1,\}\)[[:space:]]\{1,\}of[[:space:]]\{1,\}\([0-9]\{1,\}\)[[:space:]]\{1,\}total$/\1 \2/p' \
| head -n 1)
if [ -n "$header" ]; then
counted=${header%% *}
total=${header##* }
if [ "$counted" -ge "$total" ] && [ "$listed" -ge "$total" ]; then
break
fi
# Self-sizing: one re-list, with one row of headroom so a complete
# listing sits strictly inside its window and passes the no-total rule
# above even when the forge then omits the total.
if [ "$relisted" -eq 0 ] && [ "$total" -gt "$limit" ]; then
if [ "$total" -gt "$ceiling" ]; then
FM_PR_LIST_ERROR="the forge reports $total open pull requests, above the $ceiling-row ceiling (FM_PR_LIST_CEILING); an open set that large is refused rather than chased"
return 1
fi
relisted=1
limit=$((total + 1))
continue
fi
FM_PR_LIST_ERROR="the forge reports $total open pull requests and only $listed were listed; a listing short of the open total must not read as the open set"
return 1
fi
if [ "$listed" -ge "$limit" ]; then
FM_PR_LIST_ERROR="the listing filled its whole $limit-row window and reported no total, so truncation cannot be ruled out"
return 1
fi
break
done
[ -z "$rows" ] || printf '%s\n' "$rows"
}

# Sidecar layout: provider, url, host, path, number, one per line. A sidecar
# written before the provider tag existed has a URL on its first line and one
# line fewer, so it fails both the field count and the provider comparison and
Expand Down
Loading
Loading