Files
cleveragents-core/.opencode/models
drew a6986008ee feat(controller): trial-5 batch — dispute path, merge pipeline split, conflict-resolver hardening
T5-1   reviewer feedback rendered full-body to the implementer
T5-4/9 implementer dispute path — dispute-at-any-tier with per-tier cap,
       OPERATOR_ATTENTION state on stalemate, pr-review-worker-dispute agent
T5-5   reviewer BLOCKING ISSUE EVIDENCE RULE + 5-step validation
T5-7   merge step split into a singleton process — impl/review masters write
       APPROVED and stop; merge_drive owns APPROVED -> MERGING -> MERGED
T5-10  merge process is fully deterministic; base conflicts bounce to the
       controller's CONFLICT_RESOLVING (LLM); conflict_drive sidecar retired
T5-11  implementer fast success path — verified-clean outcome so a no-op
       after conflict resolution doesn't force busywork
T5-12  conflict-resolver permissions fixed across all paths (/tmp/** glob)
T5-13  conflict-resolver PR-intent prehydration (title/body/comments)

Adds tools/_controller_db_bridge.py so merge_drive reads the controller DB
directly (Option B), plus APPROVED + OPERATOR_ATTENTION states, the
dispute/verified-clean events, and the V1 contract fields backing them.
Reviewer model: baseline -> sonnet, dispute -> opus.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 17:54:07 -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