Files
cleveragents-core/.opencode/models
drew d3cf710d29 fix(controller): tier-selection calibration — finalize timeout 30s→90s, estimator defaults to tier 1
Two coupled changes responding to the 2026-05-22 batch's tier-0 hit
rate of 0% on PRs the estimator judged "simple", plus three spurious
worker-internal-errors caused by the estimator's MCP-finalize wall
clock exceeding the previous 30s budget.

agent_runner.py — finalize timeout
----------------------------------
`production_agent_runner` previously hardcoded `finalize_timeout_s=30.0`.
On the 8-PR batch the worker recorded three `worker-internal-error`
attempts (PRs 47, 50, 54) all with the same message:

    role='estimator' did not emit canonical output within 30.0s ...

The retries succeeded with estimator self-time of 41.5s, 25.9s, 21.5s —
all within plausible bounds for a multi-subsystem PR (PR 54 needed
41.5s of reasoning + MCP IPC + finalize write). Each spurious timeout
cost ~90s of worker wallclock + a re-dispatch.

New module-level constant `_DEFAULT_FINALIZE_TIMEOUT_S` reads
`CONTROLLER_FINALIZE_TIMEOUT_S` (default 90s), matching the
`CONTROLLER_*_TIMEOUT_S` convention used by `opencode_session.py`.
Operators can tune via `.devcontainer/.env.{fork,prod}`.

estimator-implementation.md — TIER MAP + CALIBRATION
----------------------------------------------------
The estimator was wrong on 4 of 8 PRs in the May-22 batch, all in
the same direction (recommended tier 0, escalated to tier 1).
Three of the four were high-confidence picks — including a PR
described as "4 string constant replacements, isolated scope, no
logic changes" that still got bounced at tier 0.

Three prompt changes:
- TIER MAP rewritten: tier 1 is now the default for non-trivial
  work; tier 0 requires positive evidence the change is mechanical
  (single file, ≤50 LOC, no new logic, no test changes).
- CONFIDENCE RULES "When uncertain, prefer" flipped tier 0 → tier 1.
- New CALIBRATION section bakes in the empirical observation (0/4
  hit rate, why Haiku struggles in this codebase, cost calculus:
  wrong tier-1 < wrong tier-0 by an order of magnitude when measured
  per merged PR).

tiers.yaml — clarify scope
--------------------------
The previous "Used by estimator-implementation.md to make tier
choices model-agnostic" comment on the `capability` field misled
readers into thinking tiers.yaml drove classification. It doesn't —
the estimator agent reads its TIER MAP from its own prompt and
emits an integer; tiers.yaml only answers "for tier-N, which model
runs?". Updated header + field comments to flag this clearly so
future operators don't change descriptors here expecting the
estimator to honor them.

Tests
-----
+ test_default_finalize_timeout_reads_env: covers the env-var
  resolution for `CONTROLLER_FINALIZE_TIMEOUT_S` (default, override,
  float values). Restores the default at the end so subsequent
  tests in the session see the stock constant.

Existing tier-model registry + 27 agent_runner tests pass unchanged.

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

.opencode/models/ — Model registry

Single source of truth: models.yaml

models.yaml is the ONE file you edit to assign an LLM model to an agent. Every other model-bearing surface is generated from it by tools/sync_models.py — do not hand-edit the generated artifacts; the next sync overwrites them.

# models.yaml
default: local-claude/claude-haiku-4-5
agents:
  conflict-resolver-worker: local-claude/claude-opus-4-6
  estimator-implementation: local-claude/claude-sonnet-4-6
  # ... only agents needing a NON-default model appear here

tools/sync_models.py regenerates, from models.yaml:

Generated artifact Consumed by
.opencode/models/<agent>.txt, default.txt the dispatcher's _opencode_worker._resolve_role_model
opencode.jsonagent.<name>.model OpenCode's static agent registry
model: frontmatter in .opencode/agents/<agent>.md OpenCode's per-agent config

Both OpenCode-facing surfaces (the opencode.json block and the .md frontmatter) are written, because which one OpenCode honours has varied across versions — writing both makes the assignment correct regardless. Since all of them derive from the one manifest, they cannot disagree.

Swapping a model

  1. Edit models.yaml.
  2. Apply it — either:
    • relaunch the controller pipeline — tools/run-controller-state-machine-pipeline.sh runs sync_models.py automatically at startup (before OpenCode boots); or
    • run python3 tools/sync_models.py by hand.
  3. Restart OpenCode — it caches its config at process start, so a running server keeps the old model until restarted. (The pipeline launcher restarts OpenCode for you.)

Verify with no writes — python3 tools/sync_models.py --check exits 1 on any drift between models.yaml and the generated artifacts (suitable for CI or a pre-launch gate).

Tier-ladder agents — tiers.yaml

The task-implementor-tier-* agents are governed separately by tiers.yaml + tools/sync_tier_models.py (the escalation ladder has its own manifest, generator, and drift test). sync_models.py passes their opencode.json entries through untouched and never edits their .md files. To change a tier model, edit tiers.yaml and run tools/sync_tier_models.py.

Inheriting agents

task-implementor and agent-evolution-pool-supervisor have no opencode.json agent entry and no model: line — they deliberately inherit their caller's model. sync_models.py does not touch them.

File reference

File Role
models.yaml Source of truth — per-agent model assignment (hand-edited)
tiers.yaml Source of truth for the tier ladder (hand-edited)
<agent>.txt, default.txt Generated by sync_models.py
README.md This file

Every model id is providerID/modelID; the provider must be declared in opencode.json's provider block — sync_models.py validates this and aborts on an unknown provider.

See also