Files
cleveragents-core/.opencode/models
drew 91073ef104 chore(auto-agents): rebind default + tier-0 to local-claude/claude-haiku-4-5
Both ``.opencode/models/default.txt`` (the agent default model when
no tier slot is specified) and the tier-0 entry in
``.opencode/models/tiers.yaml`` were pointing at OpenAI's GPT-5
family (gpt-5-mini and gpt-5-nano respectively). Rebinds to
``local-claude/claude-haiku-4-5`` to keep the default workload on
the local proxy at the per-million rate:

  $1 in / $5 out / $0.10 cached  (Haiku 4.5)

versus

  $0.25 in / $2.00 out / $0.025 cached  (gpt-5-mini)
  $0.05 in / $0.40 out / $0.005 cached  (gpt-5-nano)

Haiku-4-5 is more expensive per-token but Anthropic's prompt
caching (90% off cached input) typically wins for workloads with
large repeated system prompts — which the auto-agents pipeline
absolutely has. The actual cost/PR comparison will be visible
once a few cycles have run through the now-functional cost
dashboard (telemetry commit 62d4e8f07).

No code changes — pure configuration. Revert is one-line if the
observed cost profile favours the OpenAI defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:18:18 -04:00
..

.opencode/models/ — Model registry

Two kinds of files live here:

  1. tiers.yaml — the manifest. Single source of truth for the implementer pipeline's tier → model mapping. Edit this file when you want to swap which model serves a tier slot.
  2. <agent>.txt — per-agent model assignment. Each contains exactly one providerID/modelID line. The tier- .txt files are generated* from tiers.yaml by tools/sync_tier_models.py — do not hand-edit them. Non-tier .txt files (e.g. default.txt, ca-test-infra-improver.txt) are hand-maintained as before.

How agents pick their model

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.<name>.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 (run_session_blocking, around the POST /session body assembly).

The implication an operator MUST internalise:

  1. Edit .opencode/models/<role>.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 § G11) and the in-cycle escalation plan (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:

  1. Looking for .opencode/models/<agent-name>.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.

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. 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.<name>.model from opencode.json. Both sides must agree, which is why an OpenCode restart after a .txt edit remains required.

Static (OpenCode config path)

opencode.json declares an agent.<name>.model for every agent that has an explicit assignment, using {file:./.opencode/models/<name>.txt} so OpenCode reads the same file at config-load time. This covers interactive sessions and any Task-tool subagent invocations that bypass the dispatcher API override.

OpenCode caches its agent registry at process start, so static-path changes require a server restart (pkill -f 'opencode serve' then re-launch).

How to swap a model

For a tier slot (use the manifest)

# 1. Edit .opencode/models/tiers.yaml — change the `model:` line for
#    the slot you want to repoint. Example: change Tier 0 to Sonnet.
$EDITOR .opencode/models/tiers.yaml

# 2. Regenerate the .txt files and the mapping table in tier-dispatcher.md
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. 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

The provider half of every model: value (e.g. local-claude/…) must already be declared in opencode.json's provider block; the drift-detection test tests/auto_agents/test_tier_model_registry.py verifies this on every CI run.

For a non-tier agent (still by .txt edit)

# Switch the default worker model for the next dispatch
echo "anthropic/claude-sonnet-4-6" > .opencode/models/default.txt
python3 tools/dispatch_implementer.py --once

Inventory

Tier slots — defined in tiers.yaml, .txt files generated

Tier Agent Where the model lives
-1 tier-min tiers.yaml (entry tier: -1) → tier-min.txt generated
0 tier-0 tiers.yaml (entry tier: 0) → tier-0.txt generated
1 tier-1 tiers.yaml (entry tier: 1) → tier-1.txt generated
2 tier-2 tiers.yaml (entry tier: 2) → tier-2.txt generated

The matching agent files (.opencode/agents/tier-{min,0,1,2}.md) are model-agnostic pass-throughs — they reference the slot, never a specific model. The actual model that serves the slot is whatever tiers.yaml says today.

Hand-maintained role files

Role file Agents that resolve to it
default.txt All agents that don't have a name-matched file (pr-review-worker, implementation-worker, auto-agents, supervisor, every git-*-util, every session-health-*-util, work-group-util, async-agent-util, tier-dispatcher, estimator-implementation, conflict-resolver-worker, pr-merge-worker, pr-merge-supervisor)
ca-test-infra-improver.txt ca-test-infra-improver

Agents that deliberately inherit from their caller (no model: line, no override file): task-implementor, agent-evolution-pool-supervisor.

File format

tiers.yaml

YAML list at tiers:, one entry per tier. Each entry has tier: (int), agent: (str), model: (str providerID/modelID), capability: (str, one of cheapest / default / advanced / complex — consumed by estimator-implementation), and description: (str, one-line operator-facing summary). See the file itself for the canonical example.

<agent>.txt

  • Exactly one line per file (the model id).
  • Trailing newline is stripped by the resolver and by OpenCode's {file:...} interpolation. Do not put anything else in these files (no comments, no blank lines, no extra whitespace).
  • The model id must be valid providerID/modelID syntax (e.g. local-claude/claude-sonnet-4-6, anthropic/claude-sonnet-4-6). The provider id must already be declared in opencode.json's provider block.
  • Tier .txt files are generated. Hand-editing them is a CI failure (tests/auto_agents/test_tier_model_registry.py). Edit tiers.yaml and re-run the generator instead.

See also