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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,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>.parlay-listen-pid background `parlay listen` pid from a best-effort Parlay chat-panel enrollment at spawn (bin/fm-spawn.sh header); killed and removed by teardown
<id>.meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; kind=secondmate also records home= and projects=; 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=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); optional label= is recorded only when --label was passed at spawn; 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>.herdr-presentation quarantinable attempt and restart-binding journal for Herdr's optional visual projection; never task or endpoint authority; see docs/herdr-backend.md "Optional presentation spaces"
<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
<id>.check-trust private content binding created by fm-check-register.sh for an intentional custom check
Expand Down
24 changes: 20 additions & 4 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# Spawn a direct report: a crewmate in a treehouse or Orca worktree, or a
# secondmate in its isolated firstmate home.
# Usage: fm-spawn.sh <task-id> <project-dir> [--harness <name>|harness|launch-command] [--model <name>] [--effort <level>] [--backend <name>] [--scout]
# fm-spawn.sh <task-id> [<firstmate-home>] [--harness <name>|harness|launch-command] [--model <name>] [--effort <level>] [--backend <name>] --secondmate
# Usage: fm-spawn.sh <task-id> <project-dir> [--harness <name>|harness|launch-command] [--model <name>] [--effort <level>] [--backend <name>] [--label <string>] [--scout]
# fm-spawn.sh <task-id> [<firstmate-home>] [--harness <name>|harness|launch-command] [--model <name>] [--effort <level>] [--backend <name>] [--label <string>] --secondmate
# --harness <name> is the explicit per-spawn harness/profile adapter. The old
# positional harness arg still works for back-compat.
# --model <name> and --effort <low|medium|high|xhigh|max> are concrete profile
Expand All @@ -27,6 +27,11 @@
# A backend spawn refusal (missing dependency, version gate, unauthenticated
# socket, or unsupported secondmate mode) is terminal for that selected backend;
# callers must surface it instead of silently retrying another backend.
# --label <string> overrides the default tab/window label for the spawned task.
# Without --label, the window is named by the task ID in the form fm-<task-id>.
# With --label, the window is named fm-<label> instead. Task meta conditionally
# records label= only when --label was passed at spawn; an absent label= means
# the default task-ID-derived label was used.
# A herdr crewmate or scout is placed in the exact workspace of the firstmate
# or secondmate process launching it, resolved from that process's own herdr
# pane rather than from a workspace label (herdr enforces no label uniqueness,
Expand Down Expand Up @@ -97,7 +102,7 @@
# Batch dispatch: pass one or more `id=repo` pairs instead of a single <id> <project>, e.g.
# fm-spawn.sh fix-a-k3=projects/foo add-b-q7=projects/bar [--scout]
# Each pair re-execs this script in single-task mode, so the single path stays the only
# source of truth; shared --scout/--harness/--model/--effort/--backend applies to every pair.
# source of truth; shared --scout/--harness/--model/--effort/--backend/--label applies to every pair.
# If config/crew-dispatch.json exists, shared --harness is required for crewmate
# and scout batches. The loop lives here, in bash, so callers never hand-write a
# multi-task shell loop (the tool shell is zsh, which does not word-split unquoted
Expand Down Expand Up @@ -191,10 +196,12 @@ HARNESS_ARG=
MODEL=
EFFORT=
BACKEND_ARG=
LABEL_ARG=
HARNESS_SET=0
MODEL_SET=0
EFFORT_SET=0
BACKEND_SET=0
LABEL_SET=0
POS=()
want_value=
for a in "$@"; do
Expand All @@ -207,6 +214,7 @@ for a in "$@"; do
model) MODEL=$a; MODEL_SET=1 ;;
effort) EFFORT=$a; EFFORT_SET=1 ;;
backend) BACKEND_ARG=$a; BACKEND_SET=1 ;;
label) LABEL_ARG=$a; LABEL_SET=1 ;;
*) echo "error: internal parser state for --$want_value" >&2; exit 1 ;;
esac
want_value=
Expand All @@ -223,6 +231,8 @@ for a in "$@"; do
--effort=*) EFFORT=${a#--effort=}; EFFORT_SET=1 ;;
--backend) want_value=backend ;;
--backend=*) BACKEND_ARG=${a#--backend=}; BACKEND_SET=1 ;;
--label) want_value=label ;;
--label=*) LABEL_ARG=${a#--label=}; LABEL_SET=1 ;;
*) POS+=("$a") ;;
esac
done
Expand All @@ -231,6 +241,7 @@ done
[ "$MODEL_SET" -eq 0 ] || [ -n "$MODEL" ] || { echo "error: --model requires a non-empty value" >&2; exit 1; }
[ "$EFFORT_SET" -eq 0 ] || [ -n "$EFFORT" ] || { echo "error: --effort requires a non-empty value" >&2; exit 1; }
[ "$BACKEND_SET" -eq 0 ] || [ -n "$BACKEND_ARG" ] || { echo "error: --backend requires a non-empty value" >&2; exit 1; }
[ "$LABEL_SET" -eq 0 ] || [ -n "$LABEL_ARG" ] || { echo "error: --label requires a non-empty value" >&2; exit 1; }
case "$EFFORT" in
''|low|medium|high|xhigh|max) ;;
*) echo "error: --effort must be one of low, medium, high, xhigh, max" >&2; exit 1 ;;
Expand Down Expand Up @@ -978,7 +989,11 @@ herdr_projection_existing_meta_allows_flat() { # <meta>
esac
}

W="fm-$ID"
if [ -n "$LABEL_ARG" ]; then
W="fm-$LABEL_ARG"
else
W="fm-$ID"
fi
case "$BACKEND" in
tmux)
SES=$(fm_backend_tmux_container_ensure)
Expand Down Expand Up @@ -1623,6 +1638,7 @@ META_WINDOW=$T
echo "model=${MODEL:-default}"
echo "effort=${EFFORT:-default}"
[ -z "${BUSY_GEN:-}" ] || echo "busy_gen=$BUSY_GEN"
[ -z "$LABEL_ARG" ] || echo "label=$LABEL_ARG"
# backend= is written only for a non-default (non-tmux) backend, so the
# default path's meta stays byte-identical (absent backend= means tmux;
# data/fm-backend-design-d7's P1 compatibility contract).
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ A zellij spawn additionally version-gates against the installed `zellij` binary'
A cmux spawn additionally version-gates against the installed `cmux` binary's version, requires `jq`, and requires the control socket to be reachable and accessible (see [`docs/cmux-backend.md`](cmux-backend.md) "Setup" for the one-time socket-access configuration this needs; Automation mode is the recommended socket control mode, with Password mode supported via `config/cmux-socket-password`), refusing loudly and non-retryably on a `cmuxOnly`/unauthenticated socket.
A backend spawn refusal from a missing dependency, version gate, or unauthenticated socket is terminal for that selected backend; firstmate surfaces it as a blocker instead of silently retrying another backend.
Task meta records `backend=` only for a non-default backend; an absent `backend=` means `tmux`, preserving existing default-path meta files.
Task meta records `label=` only when `--label` was passed at spawn; an absent `label=` means the task ID was used as the default window label (window name is fm-<id> when --label is absent, fm-<label> when --label is passed).
Every new task records `endpoint_task_id=` as the cleanup binding between the metadata filename and its opaque runtime endpoint.
A herdr task additionally records `herdr_session=`, `herdr_workspace_id=`, `herdr_tab_id=`, and `herdr_pane_id=`.
A zellij task additionally records `zellij_session=`, `zellij_tab_id=`, and `zellij_pane_id=`.
Expand Down
Loading