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>
.opencode/models/ — Model registry
Two kinds of files live here:
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.<agent>.txt— per-agent model assignment. Each contains exactly oneproviderID/modelIDline. The tier-.txtfiles are generated* fromtiers.yamlbytools/sync_tier_models.py— do not hand-edit them. Non-tier.txtfiles (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:
- Edit
.opencode/models/<role>.txt(ortiers.yaml+ regen). - Restart OpenCode (
pkill -f 'opencode serve'then re-launch). - 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:
- Looking for
.opencode/models/<agent-name>.txt. - If absent, falling back to
.opencode/models/default.txt. - Parsing the single line as
providerID/modelIDand passing it as themodelfield onPOST /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/modelIDsyntax (e.g.local-claude/claude-sonnet-4-6,anthropic/claude-sonnet-4-6). The provider id must already be declared inopencode.json'sproviderblock. - Tier
.txtfiles are generated. Hand-editing them is a CI failure (tests/auto_agents/test_tier_model_registry.py). Edittiers.yamland re-run the generator instead.
See also
tiers.yaml— the manifest itself.../../tools/sync_tier_models.py— generator.../../tests/auto_agents/test_tier_model_registry.py— drift-detection test.../../docs/development/models.md— full design rationale and the planned Stage 2 fallback layer.../../opencode.json—agent.<name>.modelblock that references these files.../../tools/_opencode_worker.py— runtime resolver:_resolve_role_model(agent_name).