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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,8 @@ jobs:
snapshot_output=$(/bin/bash tests/fm-fleet-snapshot-view.test.sh)
printf '%s\n' "$snapshot_output"
snapshot_count=$(printf '%s\n' "$snapshot_output" | grep -c '^ok - ')
[ "$snapshot_count" -eq 18 ] || {
echo "::error::expected 18 snapshot/fleet-view tests, got $snapshot_count"
[ "$snapshot_count" -eq 19 ] || {
echo "::error::expected 19 snapshot/fleet-view tests, got $snapshot_count"
exit 1
}

Expand Down
3 changes: 2 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,8 @@ An absent or unknown named row also makes the candidate unrankable and is report
`ultra` is native-only: the model-aware validation contract and launch mapping are owned by `bin/fm-harness.sh validate-native-effort` and `bin/fm-spawn.sh` respectively.
Codex `max` is valid when the profile selects `gpt-5.6-luna`, whose installed catalog entry supports that reasoning level.
An omitted model or effort means the selected harness uses its own default for that axis.
A `cline` profile's `model` is the full `<provider>/<model>` id cline expects (for example `cline-pass/deepseek-v4-flash` or `cline-pass/glm-5.3`); cline derives the provider from that prefix, so no separate provider field is needed.
A `cline` profile's `model` is the full `<provider>/<model>` id cline expects (for example `cline-pass/deepseek-v4-flash` or `cline-pass/glm-5.3`), and cline derives its launch provider from that prefix.
Typed resolution still needs the profile's `provider` (`cline-pass` for a ClinePass model), because cline is not in the single-provider table above and the resolver's `provider` names the quota-axi provider family rather than the launch prefix.
Every profile array is an implicit quota-aware choice resolved through `quota-array-dispatch`.
If no dispatch rule fits, firstmate resolves `default` through the same object-or-array path before falling back to `config/crew-harness`.
Except for `ultra`, which refuses unsupported profiles under the native-effort contract above, an effort value the chosen harness does not accept is recorded as `effort=` in task meta for traceability but omitted from the launch flags.
Expand Down
10 changes: 5 additions & 5 deletions docs/examples/crew-dispatch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
"use": [
{ "harness": "claude", "model": "claude-sonnet-5", "effort": "high" },
{ "harness": "codex", "model": "gpt-5.5", "effort": "high" },
{ "harness": "cline", "model": "cline-pass/deepseek-v4-pro", "effort": "high" }
{ "harness": "cline", "model": "cline-pass/deepseek-v4-pro", "effort": "high", "provider": "cline-pass" }
],
"why": "Use a strong coding profile for big, ambiguous work; resolve the alternatives through quota-array-dispatch. The ClinePass open-weights seat keeps a parallel lane open when a subscription quota dies."
},
{
"when": "The task is a medium-sized coding ship that can run in parallel with others and a ClinePass open-weights coding model is a cheaper seat than a frontier subscription.",
"use": [
{ "harness": "cline", "model": "cline-pass/glm-5.3", "effort": "high" },
{ "harness": "cline", "model": "cline-pass/kimi-k2.7-code", "effort": "high" },
{ "harness": "cline", "model": "cline-pass/deepseek-v4-flash", "effort": "xhigh" }
{ "harness": "cline", "model": "cline-pass/glm-5.3", "effort": "high", "provider": "cline-pass" },
{ "harness": "cline", "model": "cline-pass/kimi-k2.7-code", "effort": "high", "provider": "cline-pass" },
{ "harness": "cline", "model": "cline-pass/deepseek-v4-flash", "effort": "xhigh", "provider": "cline-pass" }
],
"why": "Use the ClinePass pool as a third coding lane for medium ships so a scarce frontier or Gemini seat is not overloaded."
}
],
"default": [
{ "harness": "codex", "model": "gpt-5.5", "effort": "medium" },
{ "harness": "pi", "model": "anthropic/claude-sonnet-5", "effort": "medium", "provider": "claude" },
{ "harness": "cline", "model": "cline-pass/deepseek-v4-flash", "effort": "high" }
{ "harness": "cline", "model": "cline-pass/deepseek-v4-flash", "effort": "high", "provider": "cline-pass" }
],
"providerCaps": { "default": 4 }
}
8 changes: 5 additions & 3 deletions tests/fm-claim.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,11 @@ case "$OUT" in
*) fail "list did not include the recorded claim: '$OUT'" ;;
esac

# A corrupt record fails closed rather than being trusted.
CORRUPT=$(find "$FM_CLAIM_ROOT" -name '*.claim' | head -n 1)
[ -n "$CORRUPT" ] || fail "no claim file was created to corrupt"
# A corrupt record fails closed rather than being trusted. Three claims exist
# here, so pick the one whose documented key= line names this target instead
# of trusting directory order, which differs between filesystems.
CORRUPT=$(grep -l '^key=pr:github.com/owner/repo#11$' "$FM_CLAIM_ROOT"/*.claim 2>/dev/null | head -n 1)
[ -n "$CORRUPT" ] || fail "no claim record for owner/repo#11 was found to corrupt"
printf 'garbage\n' >"$CORRUPT"
run "$CLAIM" status "owner/repo#11"
assert_rc 5
Expand Down
2 changes: 1 addition & 1 deletion tests/fm-dispatch-resolve.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ assert_contains "$out" " profile: --harness 'gemini' --model 'gemini-3.8-flash-

cp "$ROOT/docs/examples/crew-dispatch.json" "$RULES"
cat > "$RESPONSE" <<'JSON'
{"model":"jev-1.13.0","answers":{"rule":{"type":"choice","choice":"default","confidence":0.9,"probabilities":{"rule_1":0.02,"rule_2":0.02,"rule_3":0.02,"default":0.94}}},"usage":{"input_tokens":812,"output_tokens":60}}
{"model":"jev-1.13.0","answers":{"rule":{"type":"choice","choice":"default","confidence":0.9,"probabilities":{"rule_1":0.02,"rule_2":0.02,"rule_3":0.02,"rule_4":0.02,"default":0.92}}},"usage":{"input_tokens":812,"output_tokens":60}}
JSON
reset_log
TYPESAFE_API_KEY=$KEY run code out err "$BRIEF"
Expand Down
12 changes: 9 additions & 3 deletions tests/fm-home-summary-refresh.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,14 @@ while [ ! -e "$RESTART_LOCK_MARKER" ] && [ "$i" -lt 100 ]; do
i=$((i + 1))
done
[ -e "$RESTART_LOCK_MARKER" ] || fail "could not hold the publication lock for restart coverage"
# FM_HOME_SUMMARY_TIMEOUT below bounds the watcher's own detached refresh,
# which fires on every poll while the ledger is absent and races the idle-only
# refresh at the end of this section for the dead lock. It must comfortably
# exceed one producer run on a slow CI runner: a killed attempt publishes
# nothing and releases the lock, the next poll's attempt dies the same way,
# and the ledger never appears.
PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=2 \
FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=30 \
FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
"$WATCH" > "$TMP_ROOT/restart-watch-one.out" 2> "$TMP_ROOT/restart-watch-one.err" &
WATCH_PID=$!
Expand All @@ -865,7 +871,7 @@ wait "$WATCH_PID" >/dev/null 2>&1 || true
WATCH_PID=
rm -f "$RESTART_HOME/state/.last-watcher-beat"
PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=2 \
FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=30 \
FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
"$WATCH" > "$TMP_ROOT/restart-watch-two.out" 2> "$TMP_ROOT/restart-watch-two.err" &
WATCH_PID=$!
Expand All @@ -884,7 +890,7 @@ if ! kill -0 "$WATCH_PID" 2>/dev/null; then
wait "$WATCH_PID" >/dev/null 2>&1 || true
rm -f "$RESTART_HOME/state/.last-watcher-beat"
PATH="$FAKEBIN:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$RESTART_HOME" \
FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=2 \
FM_POLL=1 FM_HOME_SUMMARY_INTERVAL=999999 FM_HOME_SUMMARY_TIMEOUT=30 \
FM_SIGNAL_GRACE=0 FM_CHECK_INTERVAL=9999999 FM_HEARTBEAT=9999999 \
"$WATCH" > "$TMP_ROOT/restart-watch-three.out" 2> "$TMP_ROOT/restart-watch-three.err" &
WATCH_PID=$!
Expand Down
Loading