Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4ee8ddb
fix(spawn): select the OpenCode model the installed build actually ac…
kesslerio Oct 20, 2026
64bf92d
no-mistakes(review): Fix OpenCode configuration ordering and failed-l…
kesslerio Oct 25, 2026
2c8fb85
no-mistakes(review): Fix OpenCode 1.x readback and task-owned failure…
kesslerio Oct 25, 2026
a2ad622
no-mistakes(review): Fix OpenCode abort cleanup, executable identity,…
kesslerio Oct 25, 2026
eaa6fcb
no-mistakes(review): Establish OpenCode pin ownership before file pub…
kesslerio Oct 25, 2026
a727835
no-mistakes(lint): Fix ShellCheck initializer and source warnings
kesslerio Oct 25, 2026
abd2ea5
test: raise flaky watch-checkpoint budget from 2s to 12s
kesslerio Oct 29, 2026
f253f76
test: make watcher checkpoint assertions timing agnostic
kesslerio Oct 29, 2026
2fabf49
no-mistakes(document): Update OpenCode model selection documentation
kesslerio Oct 29, 2026
3a3376b
no-mistakes(ci): Fixed the checkpoint-test regression: quiet checkpoi…
kesslerio Oct 30, 2026
c2f2f1d
Merge remote-tracking branch 'origin/main' into fm/opencode-launcher-fix
kesslerio Nov 2, 2026
409ac14
no-mistakes(test): Define json_escape before OpenCode model selection…
kesslerio Nov 2, 2026
b15f6eb
no-mistakes(document): Align OpenCode model-pin docs and record owed …
kesslerio Nov 2, 2026
73cc7b7
no-mistakes(ci): Fixed the one evidenced cause of both failing checks…
kesslerio Nov 2, 2026
70394f8
no-mistakes(ci): Finding ci-2/ci-3: portable serial 1 and Full suite …
kesslerio Nov 2, 2026
b548f95
no-mistakes(document): Correct stale supervision-host shard inventory…
kesslerio Nov 2, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ On 2026-10-15 the dual-export primary plugins loaded once each and the turn-end
| Interrupt | Double Escape; it is known to be flaky while a long shell command runs, so use `../../../bin/fm-control.sh <task-id> relaunch` for a wedged pane. |
| Skill invocation | No separate verified form beyond normal slash-command behavior; use natural language when the exact command is uncertain. |
| Resume | Relaunch with `--continue` to resume the most recent session for the current directory, then send the next instruction after the TUI is ready because `--prompt` does not auto-submit alongside `--continue`. |
| Model flag | Version-sensitive, keyed to the detected major. OpenCode 2.0.21 has no interactive top-level `--model` (`opencode run --model` remains only on the non-interactive path), so Firstmate pins the interactive model through `OPENCODE_CONFIG_CONTENT` as global `model` plus `agent.build.model`. OpenCode 1.x still takes the interactive `--model` flag, so there Firstmate passes `--model` and does not write the 2.x global/build model pin. An undetected or unparseable version (opencode unresolvable, or `--version` without a parseable x.y.z) falls back to the 1.x shape, so only a parsed major >= 2 takes the 2.x pin. |
| Effort flag | None for Firstmate's interactive `opencode --prompt` launch; `opencode run` has model variant syntax, but that is not this path. The effort instead rides the launch's `OPENCODE_CONFIG_CONTENT` JSON as the `build` agent's `variant` keyed to the resolved model. It is emitted only when the resolved model's provider is known to expose that effort as a variant (`anthropic/*`: high, max; `openai/*`: low, medium, high, xhigh); with no model resolved, another provider, or an effort outside its family's list, the variant is omitted. On 2.x the global/build model pin remains even when the variant is omitted; on 1.x, where the model arrives via `--model`, omitting the variant leaves the permission-only config. |
| Model selection | When `--model` is requested, Firstmate resolves the installed `opencode` executable, probes its `--version` and top-level `--help`, and then uses the top-level `--model` flag when that build advertises one or writes `.opencode/opencode.jsonc` in the worker's worktree when it does not. An unreadable version or help surface refuses the launch with the build's own identity instead of running its default model. A project-owned config is never overwritten: one that already selects the requested model is kept as it is and is never recorded as Firstmate's, and one selecting anything else refuses. On the project-config path the write is verified through that build's own `opencode debug config` readback before launch; every model-pinned launch then asserts the model recorded on the worker's first assistant message after its own launch timestamp, and an absent, unreadable, or different observation refuses the spawn and closes the endpoint, retaining recovery records when that close cannot be confirmed. The pin file is git-excluded, recorded per task, and retired with the task, so a reused pool slot cannot inherit it. `provider/model#variant` is unsupported; use `provider/model` with `--effort`, and model-axis coverage plus its remaining live evidence are recorded in `docs/verification/runtime-backends.md`. |
| Effort | Effort is supplied with Firstmate's `--effort` option, not inline in the model identifier. For known provider variants (`anthropic/*`: high, max; `openai/*`: low, medium, high, xhigh), Firstmate includes the variant beside the model in project-config selection or in the launch config for a flag-based build. For other providers or unsupported effort values it omits the variant rather than guessing. |
| Model discovery | Run `opencode models` to list available provider/model identifiers; OpenCode 2.0.21 no longer accepts a provider positional argument. |
| Trust dialog | None. |
| Marker | None; OpenCode publishes no identity marker, so `../../../bin/fm-harness.sh` identifies it from process ancestry. |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ jobs:
# Every shard reports so one failure never hides another shard's result.
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
steps:
- uses: actions/checkout@v6
with:
Expand Down
29 changes: 27 additions & 2 deletions bin/fm-control-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,9 @@ fm_control_endpoint_absence_verdict() { # <backend> <target> [meta]
# table for Polytoken; both consumers preserve git-tracked Polytoken layers.
# Prints zero or more absolute paths, one per line: worktree-resident
# hook files and firstmate-owned state tokens only, never a harness's own
# managed config.
# managed config. An OpenCode model pin follows the same ownership rule: only
# the pin this task's own record names is returned, so a project's own
# .opencode/opencode.jsonc is never listed.
fm_control_harness_wiring_paths() { # <harness> <worktree> <state-dir> <id> [kind]
local harness=${1-} wt=${2-} state=${3-} id=${4-} kind=${5-}
[ -n "$wt" ] && [ -n "$state" ] && [ -n "$id" ] || return 1
Expand All @@ -520,7 +522,21 @@ fm_control_harness_wiring_paths() { # <harness> <worktree> <state-dir> <id> [ki
printf '%s\n' "$wt/.claude/settings.local.json"
fi
;;
opencode) printf '%s\n' "$wt/.opencode/plugins/fm-busy-state.js" ;;
opencode)
printf '%s\n' "$wt/.opencode/plugins/fm-busy-state.js"
# A build without the top-level --model flag is pinned through the
# worktree's project configuration, and that file is retired only while
# this task's own record names it: a project's own
# .opencode/opencode.jsonc predates firstmate and is never its to remove.
# A stale pin must not outlive the task either, because a returned pool
# slot is reused and a later launch that names no model would silently
# inherit it.
if [ -f "$state/$id.opencode-model-config" ] &&
[ "$(cat "$state/$id.opencode-model-config" 2>/dev/null)" = "$wt/.opencode/opencode.jsonc" ]; then
printf '%s\n' "$wt/.opencode/opencode.jsonc"
printf '%s\n' "$state/$id.opencode-model-config"
fi
;;
pi|pi-signed) printf '%s\n' "$state/$id.pi-ext.ts" ;;
omp) printf '%s\n' "$state/$id.omp-ext.ts" ;;
grok)
Expand Down Expand Up @@ -623,3 +639,12 @@ fm_control_harness_turnend_auth_path() { # <harness> <token>
*) return 0 ;;
esac
}

fm_control_retire_opencode_model_pin() {
local id=$1 state=$3 record="$3/$1.opencode-model-config" pin="$2/.opencode/opencode.jsonc" owned
[ -e "$record" ] || [ -L "$record" ] || return 0
owned=$(cat "$record") || return 1
[ "$owned" = "$pin" ] || return 1
rm -f -- "$pin" || return 1
rm -f -- "$state/$id.opencode-model-config"
}
Loading
Loading