diff --git a/.opencode/models/README.md b/.opencode/models/README.md index 1b3c74704..6b432513d 100644 --- a/.opencode/models/README.md +++ b/.opencode/models/README.md @@ -15,6 +15,43 @@ Two kinds of files live here: Two paths read from this directory; both honour the same convention. +### ⚠️ Operational footgun: editing a `.txt` file does NOT live-update generation + +This is the single highest-leverage operational fact in this directory. +**Both paths below require an OpenCode server restart for an edit to a +`.txt` file to actually change what model OpenCode runs.** The dispatcher +PASSES the resolved model on every `POST /session`, but empirical +testing against OpenCode 0.x found that OpenCode re-resolves +`agent..model` from its own startup-cached `opencode.json` every +generation — the dispatcher's pass-through is recorded in the session +metadata for observability/auditability, **not** consumed by the +generation path. See the long comment in +[`tools/_opencode_worker.py`](../../tools/_opencode_worker.py) +(`run_session_blocking`, around the `POST /session` body assembly). + +The implication an operator MUST internalise: + +1. Edit `.opencode/models/.txt` (or `tiers.yaml` + regen). +2. **Restart OpenCode** (`pkill -f 'opencode serve'` then re-launch). +3. Verify with `tools/audit_opencode_session_models.py` (or by reading + a fresh session's metadata) that the new model is in effect. + +Skipping step 2 produces the worst kind of silent regression: the +dispatcher's logs say it requested the new model, the cycle archive +records the new model, but OpenCode generates with the old cached +value. There is no in-process diagnostic that surfaces the mismatch — +it is invisible from the dispatcher side. + +The G11 estimator-driven adaptive tier selection +([final-working-harvest-plan.md](../../docs/development/final-working-harvest-plan.md) +§ G11) and the in-cycle escalation plan +([implementer-in-cycle-escalation-plan.md](../../docs/development/implementer-in-cycle-escalation-plan.md)) +both depend on the worker actually generating with the +dispatcher-requested model. If model swaps land without a restart, +those features silently misbehave (a Tier 1 escalation will run on the +previously-cached Tier 0 model and produce the same outcome the Tier 0 +attempt produced — exactly the cycle they were meant to break out of). + ### Runtime (dispatcher path) `tools/_opencode_worker.py` resolves a model for an agent by: @@ -22,12 +59,16 @@ Two paths read from this directory; both honour the same convention. 1. Looking for `.opencode/models/.txt`. 2. If absent, falling back to `.opencode/models/default.txt`. 3. Parsing the single line as `providerID/modelID` and passing it as the - `model` field on `POST /session` and `POST /session/{id}/prompt_async`. + `model` field on `POST /session`. This is the path used by `dispatch_review.py`, `dispatch_implementer.py`, `conflict_drive.py`, and any other code that calls -`_opencode_worker.run_session_blocking`. Changes to a `.txt` file take effect -on the **next dispatched session** — no OpenCode restart needed. +`_opencode_worker.run_session_blocking`. The dispatcher includes the +resolved model on every session so the cycle archive carries the +**intended** model — but per the footgun above, OpenCode's generation +uses its startup-cached `agent..model` from `opencode.json`. Both +sides must agree, which is why an OpenCode restart after a `.txt` edit +remains required. ### Static (OpenCode config path) @@ -54,9 +95,14 @@ python3 tools/sync_tier_models.py # 3. Verify everything stays in sync (CI also runs this) python3 tools/sync_tier_models.py --check # exits 0 = clean -# 4. Pick up the new model on the next dispatch — runtime path re-reads -# .txt files per cycle, no restart needed. Static path (interactive -# sessions, Task-tool subagents) requires an OpenCode restart. +# 4. RESTART OpenCode so the new model actually takes effect. +# The dispatcher records the new model on every POST /session, +# but OpenCode generates from its startup-cached opencode.json — +# skipping the restart is the silent-regression footgun called +# out at the top of this README. +pkill -f 'opencode serve' && /usr/local/bin/opencode serve & # or your launcher + +# 5. Verify the new model is actually in effect on the next dispatch. python3 tools/dispatch_implementer.py --once ```