Skip to content
Merged
2 changes: 2 additions & 0 deletions .agents/skills/operational-home-layout/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ bin/ helper scripts, committed; read each script's header before
config/crew-harness crewmate harness override; LOCAL, gitignored; absent or "default" = same as firstmate. Inherited as the literal file: a concrete primary adapter value also controls a secondmate home's own crewmates (section 4)
config/claude-permission-mode optional one-token permission posture for every Claude worker launch: absent or "bypass" keeps --dangerously-skip-permissions, "auto" launches with --permission-mode auto; LOCAL, gitignored; inherited by secondmate homes; see docs/configuration.md "Claude permission mode"
config/claude-account config/pi-account optional per-home worker account pin for Claude and Pi launches; LOCAL, gitignored, not inherited; absent keeps today's ambient account; present refuses a launch unless the pinned account resolves and is signed in (section 4 owns the refusal rule); see docs/configuration.md "Worker account pin"
config/crew-exclude-tools optional per-home tool exclusion list for ship and scout workers; LOCAL, gitignored, not inherited; see docs/configuration.md "Worker tool exclusions"
config/crew-dispatch.json optional crewmate dispatch profiles; LOCAL, gitignored; firstmate-maintained but human-editable natural-language rules that choose a per-task harness/model/effort profile (section 4). Inherited by secondmate homes
config/project-capacity optional per-machine count of workers each named project admits at once, read from the root home by every local home; LOCAL, gitignored; see docs/configuration.md "Project capacity"
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" = the configured 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), herdr has its own required CI lane (docs/herdr-backend.md), while zellij, orca, and cmux remain experimental with no dedicated real-backend CI lane (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
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ Tracked files hold shared instructions and tooling; `data/` holds durable privat

Load `operational-home-layout` when locating, interpreting, or changing Firstmate home, config, data, state, project, or generated runtime paths.


A `state/<id>.status` line is a wake event, not current-state truth; `bin/fm-crew-state.sh` owns current-state reconciliation.
Treat `data/captain.md` as the domain-local record of captain preferences, optional `data/captain-shared.md` as the main-authoritative shared captain-preference file for secondmate inheritance, and `data/learnings.md` as curated home-local knowledge, regardless of harness memory.

Expand Down Expand Up @@ -195,6 +194,7 @@ An unregistered project or absent registry resolves to `no-mistakes` with yolo o
Record the resulting mode, `yolo` merge posture, and the one-line reason for any deviation in the backlog item note.

Treat file or subsystem overlap as a risk signal rather than an automatic reason to wait, and dispatch isolated work immediately with no concurrency cap when each change can be independently implemented and validated and the selected delivery path can reconcile ordinary rebases or conflicts.
A project's declared machine capacity (`config/project-capacity`) still bounds that dispatch: a spawn beyond it exits 75 without launching, and its item stays queued rather than blocked.
Serialize only for a true semantic dependency, shared mutable external state, incompatible concurrent migration, or another concrete condition that makes independent progress or reconciliation unsafe; same-file editing alone is insufficient, and genuine blockers remain durable.
Write the task-specific brief under section 11 before spawning.
Fill the task subsections according to section 11.
Expand Down Expand Up @@ -367,7 +367,7 @@ A decision is simply a task held for the captain: create the task with `bin/fm-t
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 and hold it through that wrapper.
Captain calls discovered by investigations or visual reviews follow `captain-hold-lifecycle`, which owns their completion gate and recorded-answer rules.
When the automatic transition gate applies, dispatch and completion move the item themselves - `bin/fm-spawn.sh` and `bin/fm-teardown.sh` own those transitions and refuse rather than report success without them - so what remains yours is filing the item before dispatch, recording decisions, and keeping notes current; `docs/configuration.md` owns gate applicability and the manual-backend exception.
Re-evaluate queued work after every teardown and heartbeat, dispatching items only when dependencies and time gates have cleared.
Re-evaluate queued work after every teardown and heartbeat, and also after a recorded PR-ready handoff when `config/project-capacity` caps that project, dispatching items only when dependencies, time gates, and project capacity 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, always through `bin/fm-tasks-axi.sh` so the call reaches this home's backlog from any directory, and the documented manual path otherwise; keep only the configured recent Done entries.
Expand Down
11 changes: 11 additions & 0 deletions bin/fm-control.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
# worker account pin (bin/fm-worker-account-lib.sh) here, so a pin
# that no longer resolves or is signed out refuses before the old
# agent stops.
# The same pre-stop refusal applies to this home's worker tool
# exclusions (bin/fm-exclude-tools-lib.sh): a malformed list, or a
# replacement runtime that cannot hide the listed tools.
# --note is required for a ship or scout, whose replacement
# inherits the local copy but none of the conversation; a
# secondmate reconciles its own home's records at startup, so its
Expand Down Expand Up @@ -176,6 +179,8 @@ DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
. "$SCRIPT_DIR/fm-wake-lib.sh"
# shellcheck source=bin/fm-worker-account-lib.sh
. "$SCRIPT_DIR/fm-worker-account-lib.sh"
# shellcheck source=bin/fm-exclude-tools-lib.sh
. "$SCRIPT_DIR/fm-exclude-tools-lib.sh"

POLL=${FM_CONTROL_POLL:-0.5}
SETTLE_WAIT=${FM_CONTROL_SETTLE_WAIT:-5}
Expand Down Expand Up @@ -956,6 +961,12 @@ resolve_relaunch_profile() {
[ "$account_model" != default ] || account_model=
fm_worker_account_select "$TARGET_HARNESS" "${FM_CONFIG_OVERRIDE:-$FM_HOME/config}" \
"$account_model" "$TARGET_HARNESS" >/dev/null || return 1
# Likewise config/crew-exclude-tools: a malformed file, or a replacement
# runtime that cannot hide the listed tools, refuses here, before the old
# agent stops. Secondmate agents are not covered.
if [ "$KIND" != secondmate ]; then
fm_exclude_tools_check "$TARGET_HARNESS" 0 "${FM_CONFIG_OVERRIDE:-$FM_HOME/config}" >/dev/null || return 1
fi
}

# safe_checkpoint: prove, before anything is stopped, that the work a relaunch
Expand Down
64 changes: 64 additions & 0 deletions bin/fm-exclude-tools-lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# fm-exclude-tools-lib.sh - shared parsing and runtime-support checks for
# config/crew-exclude-tools, sourced by bin/fm-spawn.sh and bin/fm-control.sh.
#
# docs/configuration.md "Worker tool exclusions" owns the file format and
# operator contract; bin/fm-spawn.sh's header owns launch-flag mechanics.
# Name verification belongs inside the worker's loaded-tool registry, never
# in an out-of-band server connection from these launch-time checks.

# fm_exclude_tools_names <config-dir>: print the comma-joined names, empty when
# the file is absent or lists nothing. Non-zero with the reason on stderr when
# the file or an entry is invalid.
fm_exclude_tools_names() {
local config=$1 file line names='' present
file=$config/crew-exclude-tools
present=$(perl -MErrno=ENOENT -e '
if (lstat $ARGV[0]) { print 1 }
elsif ($! == ENOENT) { print 0 }
else { die "error: cannot inspect config/crew-exclude-tools: $!\n" }
' -- "$file") || return 1
[ "$present" = 1 ] || return 0
if [ ! -f "$file" ] || [ ! -r "$file" ]; then
echo "error: config/crew-exclude-tools must be a readable regular file" >&2
return 1
fi
while IFS= read -r line || [ -n "$line" ]; do
line=${line#"${line%%[![:space:]]*}"}
line=${line%"${line##*[![:space:]]}"}
case "$line" in
'' | '#'*) continue ;;
esac
if [ -n "${line//[A-Za-z0-9_.-]/}" ]; then
echo "error: config/crew-exclude-tools has a malformed entry '$line'; expected one tool name per line using only letters, digits, _ . and - (blank lines and # comment lines are allowed)" >&2
return 1
fi
names="${names:+$names,}$line"
done <"$file" || {
echo "error: cannot read config/crew-exclude-tools" >&2
return 1
}
printf '%s' "$names"
}

# fm_exclude_tools_check <harness> <raw-launch 0|1> <config-dir>: succeed when
# this launch can honor the home's list (empty list, or a runtime that hides
# tools); otherwise refuse naming the file. Prints the names on stdout.
fm_exclude_tools_check() {
local harness=$1 raw=$2 config=$3 names
names=$(fm_exclude_tools_names "$config") || return 1
if [ -n "$names" ]; then
if [ "$raw" = 1 ]; then
echo "error: config/crew-exclude-tools lists tools to hide, but a raw launch command cannot hide tools; remove the entries or launch a runtime that supports them (pi, pi-signed)" >&2
return 1
fi
case "$harness" in
pi | pi-signed) ;;
*)
echo "error: config/crew-exclude-tools lists tools to hide, but the $harness runtime cannot hide tools, so this launch is refused rather than running with them available; empty the file or launch a runtime that supports exclusion (pi, pi-signed)" >&2
return 1
;;
esac
fi
printf '%s' "$names"
}
2 changes: 2 additions & 0 deletions bin/fm-pr-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
# draft state does not refuse, matching how the head read below is optional.
# bin/fm-pr-merge.sh records through this script with FM_PR_CHECK_MERGE=1 and
# skips this refusal, because its own merge-time draft refusal is authoritative.
# The recorded pr= also frees the task's place in a declared project capacity
# (bin/fm-project-capacity-lib.sh).
# Usage: fm-pr-check.sh <task-id> <pr-url>
set -eu

Expand Down
Loading
Loading