Skip to content

Latest commit

 

History

History
487 lines (421 loc) · 33.8 KB

architecture.md

File metadata and controls

487 lines (421 loc) · 33.8 KB
title OpenCode Architecture and System Design
owner OpenCode team
status production
last_verified 2026-09-13
reproduce
files commands inputs expected_outputs
packages/opencode/src/provider/transform.ts
packages/opencode/src/session/llm.ts
packages/opencode/test/session/llm.test.ts
packages/opencode/src/cli/cmd/tui/component/dialog-routing-state.ts
packages/opencode/test/tui/dialog-routing-state.test.ts
packages/opencode/src/session/model-sampling.ts
packages/opencode/src/cli/cmd/tui/component/dialog-model-parameters.tsx
packages/opencode/src/provider/provider-sync.ts
packages/opencode/src/cli/cmd/tui/component/dialog-streamlake-vanchin-state.ts
packages/opencode/test/tui/dialog-streamlake-vanchin-state.test.ts
cd packages/opencode && bun test test/session/llm.test.ts
cd packages/opencode && bun test test/tui/dialog-routing-state.test.ts
cd packages/opencode && bun test test/session/model-sampling.test.ts test/session/session-settings-persist.test.ts test/tui/dialog-routing-state.test.ts
cd packages/opencode && bun test test/provider/provider-sync.test.ts test/tui/dialog-streamlake-vanchin-state.test.ts
An OpenRouter chat turn, including a child task with a reusable cache lease; or a StreamLake Vanchin Pay-as-you-go endpoint and its deployed model selected from the official catalog.
Mutable banner, body session_id, header x-session-id, and prompt_cache_key share one final provider cache namespace; per-model sampling survives the chosen scope; Vanchin endpoint configuration stores documented model limits and text/image/video input capabilities without an API key.

OpenCode Architecture & System Design (2026-06-24)

Status: production Last Updated: 2026-09-06 See also:

  • prompt_kernel/README.md — kernel gate graph, serialization order, install pipeline
  • docs/compaction.md — mechanistic compaction (stable continuous memory)

Production system kernel: prompt_kernel/source.py → packages/opencode/src/session/prompt/reasoning_prompt.txt (python -m prompt_kernel --install).


StreamLake Vanchin Provider Setup

  1. packages/opencode/src/provider/provider-sync.ts / PROVIDER_SOURCES

    • Input: canonical streamlake-vanchin provider ID.
    • Output: a first-class Pay-as-you-go OpenAI-compatible provider at https://vanchin.streamlake.ai/api/gateway/v1/endpoints, authenticated through STREAMLAKE_API_KEY.
    • Logic: this static source supplies no account-specific endpoint ID, so the provider chooser can render the provider without fabricating a model.
  2. dialog-streamlake-vanchin-state.ts / STREAMLAKE_VANCHIN_MODELS

    • Input: the official Vanchin model-list snapshot and one endpoint ID.
    • Output: a model profile containing documented context/output bounds, reasoning, Function Call, and text/image/video input modalities.
    • Logic: only text and multimodal chat profiles are selectable. Gateway GET metadata paths returned HTTP 400 with an authenticated key, so the profile comes from the official catalog while the endpoint ID comes from the Vanchin console. Reasoning profiles add the vendor-documented enable_thinking: true option to the request body.
  3. dialog-provider.tsx / connectStreamLakeVanchin

    • Input: API key, ep-… inference endpoint ID, and deployed profile selected by the user.
    • Output: an auth-store credential plus a minimal RFC 7386 /config patch, then the ordinary DialogModel picker.
    • Logic: the API key is never included in the configuration patch; the selected profile, rather than free-form modality input, determines model capabilities. The Chinese OpenAI protocol reserves wire-level modalities for Qwen-Omni audio output; normal image/video-understanding profiles do not send it.
  4. provider/transform.ts / providerOptions

    • Input: a Vanchin Pay-as-you-go OpenAI-compatible request.
    • Output: provider options containing the selected profile’s enable_thinking setting and no legacy coding-template field.
    • Logic: the old template option is now limited to a /api/gateway/coding/v1 URL and cannot leak into the Pay-as-you-go endpoint.

Oracle: bun test test/tui/dialog-streamlake-vanchin-state.test.ts and bun test test/provider/transform.test.ts --test-name-pattern "keeps Vanchin PayGo requests free".

Hugging Face Live Model Source

packages/opencode/src/provider/provider-sync.ts / PROVIDER_SOURCES (huggingface):

  • Input: https://router.huggingface.co/v1/models (HF Inference Providers chat router; anonymous listing works, HF_TOKEN is sent when present).
  • Logic: the router list discloses less metadata than the curated registry (no reasoning/interleaved/description/output-limit), so this source merges instead of replacing — curated fields survive, live pricing/context/modalities win, upstream-only ids are retained (models.dev deleteMissing:false semantics), and new ids are added. Quantisation variants (-BF16, -FP8, …) inherit capability metadata from their base model. Pricing collapses the router's per-provider table to the default :fastest route (routed provider, else the fastest priced one) — already USD per million tokens.
  • Oracle: bun test test/provider/provider-sync.test.ts; targeted refresh: bun run script/provider-sync.ts (packages/opencode).

1. Prompt System Architecture

┌─────────────────────────────────────────────────────────────┐
│                    PROMPT ASSEMBLY                           │
│                                                              │
│  prompt.ts: runLoop()                                        │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ 1. Checkpoint.load(sessionID, model)                  │   │
│  │    ├── HIT → reuse systemPrompt + messages            │   │
│  │    └── MISS → Effect.all([skills, env, instructions,  │   │
│  │               rules]) → assemble fresh                 │   │
│  │                                                       │   │
│  │ 2. system = [banner, ...rules, ...env, ...skills,     │   │
│  │              ...instructions]                          │   │
│  │                                                       │   │
│  │ 3. modelMsgs = toModelMessagesEffect(msgs, model)     │   │
│  │                                                       │   │
│  │ 4. llm.stream(system, modelMsgs, tools)               │   │
│  │    ├── Provider prefix cache: [session: key]          │   │
│  │    ├── plugin.transform hook (mutates system in-place) │   │
│  │    └── Cache collapse: header → second → tail         │   │
│  │                                                       │   │
│  │ 5. Success → Checkpoint.save(system + modelMsgs)      │   │
│  │    Failure → checkpoint untouched (rollback)           │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  System prompt order:                                        │
│  [immutable rules/env/skills/instructions] [session banner]  │
│  banner is the final mutable cache fence                     │
└─────────────────────────────────────────────────────────────┘

2. Per-Model Encrypted Checkpoint System

┌─────────────────────────────────────────────────────────────┐
│                   CHECKPOINT SYSTEM                          │
│                                                              │
│  Storage: {data}/log/.checkpoints/                           │
│    {provider}_{model}_{agent}_{sessionID}_S{0|1}.enc         │
│  Memory: process map published on save (no disk race)        │
│                                                              │
│  Encryption: AES-256-GCM                                     │
│  Key: SHA-256(projectID:sessionID:salt)                      │
│  Atomic: write .tmp → rename → .enc (2-slot rotate)          │
│                                                              │
│  CheckpointData v4 {                                         │
│    systemPrompt[]          path system (frozen until compact)│
│    identityFingerprint     kernel+agent prompt only          │
│    messages[] + messageIDs[] + messageFingerprints[]         │
│    model, agent, turn, timestamp                             │
│  }                                                           │
│                                                              │
│  Policy (KV continuous, multi-project stable):               │
│    • Path system (AGENTS.md/skills/rules) frozen mid-era     │
│    • Refresh only on compact OR identity fingerprint break   │
│    • Per model (+ agent) slots — switch models, nothing lost │
│    • Message reuse: ordered prefix + content fingerprints    │
│      (in-place edits re-convert from first dirty message)    │
│                                                              │
│  Diffs: request-diff remembers last formatted request so     │
│  post-compact turns still produce a diagnostic .diff         │
└─────────────────────────────────────────────────────────────┘

3. Agent Pipeline Architecture

┌─────────────────────────────────────────────────────────────┐
│                     AGENT SYSTEM                             │
│                                                              │
│  Primary agents (user-facing):                               │
│    build    — full-access development (provider prompt)      │
│    plan     — read-only planning (denies edits)              │
│                                                              │
│  Subagents (invoked via task tool or pipeline):              │
│  ┌──────────┬──────────────┬────────────────────────────┐   │
│  │ general  │ explore      │ coder      │ researcher    │   │
│  │ planning │ file search  │ implement  │ gather info   │   │
│  │ strategy │ code grep    │ edit/write │ read-only     │   │
│  ├──────────┼──────────────┼────────────┼───────────────┤   │
│  │ media    │ title        │            │               │   │
│  │ generate │ name         │            │               │   │
│  │ images   │ sessions     │            │               │   │
│  └──────────┴──────────────┴────────────┴───────────────┘   │
│  (No separate compaction or summary agent — summarization runs via
│  /summarize + SessionSummary; see § Mechanistic Compaction)   │
│                                                              │
│  Pipeline tool: chains subagents sequentially                │
│    researcher → coder: gather evidence → implement           │
│    explore → general: find files → plan approach             │
│    media → researcher: generate → verify                     │
│                                                              │
│  Tools (built-in):                                           │
│    capability, pipeline, task, bash, edit, write, read,      │
│    glob, grep, list, webfetch, universalsearch,              │
│    messagesearch, sessionread, joboutput, jobwait, jobkill,  │
│    todowrite, question, skill, lsp, planexit, fossilgrep,    │
│    codegraph, invalid                                        │
└─────────────────────────────────────────────────────────────┘

4. Multimodal & Media Architecture

┌─────────────────────────────────────────────────────────────┐
│                    MEDIA SYSTEM                              │
│                                                              │
│  Attachment Pipeline:                                        │
│    User upload → Handler.classify() → UniversalAttachment    │
│    → Handler.enrich() → metadata extraction                  │
│    → Handler.normalize() → resize/optimize                   │
│    → toModelMessagesEffect() → provider format               │
│                                                              │
│  14 Handlers:                                                │
│    audio, video, image, document, binary, text, code,        │
│    sensor, spatial, archive, data, spreadsheet,              │
│    presentation, image_vector                                │
│                                                              │
│  Terminal TUI Media (Module C):                              │
│    MediaImage  ← chafa --format symbols (inline terminal)    │
│    MediaVideo  ← ffmpeg thumbnail + mpv --vo=gpu (external)  │
│    MediaAudio  ← mpv --vo=null (terminal playback)           │
│                                                              │
│  Multimodal Messages (Module D):                             │
│    • case "file" in processor.ts → FilePart creation         │
│    • Output modalities in system prompt                      │
│    • FilePart rendering in assistant messages                │
│    • toModelMessagesEffect includes assistant media           │
│                                                              │
│  Capability Tool:                                            │
│    Queries models_capabilities.yaml → cross-refs auth.json   │
│    → returns ranked models by modality + API key status      │
└─────────────────────────────────────────────────────────────┘

5. Mechanistic Compaction (Stable Continuous Memory)

Canonical (code Exact): docs/compaction.md · session-memory-graph.md

Do not summarize this section from memory — the control-flow traps are real:

stop path:   finishStep → Checkpoint M → maybeCaptureSidecar? → break
             ^^^ no compact() here

in-band compact: only if loop does NOT break first (e.g. tool-continue)
                 AND needsContentCompaction(open ≥ 65_536)

emergency:   processor needsCompaction → compact()

injectSummaryRequest: implemented, NOT called from prompt.ts
Piece Tokens When
Sidecar ephemeral LLM stop + thresholds
compact() zero emergency, or in-band if loop continues
Safety size content/4 + 10k fit / usable (not cadence)

Model = Inferred prose. System = Exact IDs / diffs / materialize / soft-hide.

6. KV Cache & Diff Architecture

┌─────────────────────────────────────────────────────────────┐
│                 CACHE & DIFF SYSTEM                          │
│                                                              │
│  provider/transform.ts:                                      │
│    • Anthropic-dialect `cache_control: ephemeral` markers    │
│      on the last cacheable content block                     │
│    • md5 of the system parts feeds the prompt-cache key      │
│    • the speculative fingerprint audit was removed in        │
│      352e073279 — cache:broken fired on false positives      │
│                                                              │
│  request-diff.ts:                                            │
│    • Encrypted baseline (.enc files in .baselines/)          │
│    • AES-256-GCM, key = SHA-256(project:worktree:sid:salt)  │
│    • Section-aware diff: META + SYSTEM + MESSAGES            │
│    • alignLines: fast-path prefix scan (O(N) for identical  │
│      system prompts, avoids O(N²) sync-point search)         │
│    • Written every turn (fire-and-forget), survives restarts │
│                                                              │
│  checkpoint.ts:                                              │
│    • Reuses encryption + baselinePath from request-diff      │
│    • Stores structured prompt (not debug-formatted text)     │
│    • kind: "checkpoint" distinguishes from diff baselines    │
│    • Atomic write: tmp → rename → .enc                       │
│                                                              │
│  OpenRouter affinity (llm.ts + provider/transform.ts):       │
│    • banner == body session_id == header x-session-id        │
│      == prompt_cache_key (final provider cache namespace)    │
│    • a subagent uses its reusable task-cache lease, not its  │
│      physical child session ID                               │
│    • provider.only filters a single upstream without order   │
└─────────────────────────────────────────────────────────────┘

7. Complete Data Flow

┌──────────┐    ┌──────────────┐    ┌─────────────┐    ┌──────────┐
│  User    │───▶│  prompt.ts   │───▶│   llm.ts    │───▶│ Provider │
│  Input   │    │  assemble    │    │  reorganize │    │   API    │
└──────────┘    └──────┬───────┘    └──────┬──────┘    └────┬─────┘
                       │                   │                │
                 ┌─────▼─────┐       ┌─────▼─────┐    ┌────▼─────┐
                 │ Checkpoint │       │  Plugin   │    │ Response │
                 │ Load/Save  │       │  Hook     │    │ Stream   │
                 └─────┬─────┘       └───────────┘    └────┬─────┘
                       │                                   │
                 ┌─────▼─────┐                       ┌────▼─────┐
                 │  .enc     │                       │Processor │
                 │  Files    │                       │  Events  │
                 └───────────┘                       └────┬─────┘
                                                         │
                     ┌───────────────────────────────────┤
                     │                                   │
               ┌─────▼─────┐                       ┌─────▼─────┐
               │  Diff     │                       │  Session  │
               │  Logger   │                       │    DB     │
               │  .enc     │                       │  SQLite   │
               │  .diff    │                       └───────────┘
               └───────────┘

8. AGI plan hygiene

See docs/agi-workflow.md. Runtime util/plan-status.ts standardizes plans/ vs plans_completed/ by checkbox presence; AGI only terminates when hygiene is clean.

8a. Provider request headers (2026-09-07)

session/llm.ts builds ONE correlation header set for EVERY provider — no per-provider branching:

  • x-opencode-session / x-opencode-request / x-opencode-project / x-opencode-client — opencode correlation; Zen reads these for routing/telemetry.
  • x-request-id (= message id) — fills provider dashboards' "Request ID" columns (e.g. Novita) and our gateway metrics.
  • x-session-id + x-session-affinity — unified affinity namespace: providerCacheKey for openrouter, sessionID otherwise. OpenRouter's former X-Session-Id merged here (commit 8e5bdc41).
  • x-parent-session-id — set when subagent parent exists.
  • X-OpenRouter-Cache / X-OpenRouter-Cache-TTL — strict opt-in via provider.<id>.options.responseCache (+responseCacheTtl, 1–86400s; see provider/response-cache.ts). Response caching only — NOT prompt caching.
  • User-Agent: opencode/<version>.

Wire policy: transports send headers VERBATIM. Gateway h1/h2 transports do not filter anything (user directive 2026-09-07; the earlier allowlist was superseded). Only adaptive-client removes consumed OAuth inputs (x-opencode-oauth-token → Authorization, x-opencode-account-id → ChatGPT-Account-Id, x-opencode-oauth-url → URL rewrite) so raw credentials never duplicate into provider logs. Local diagnostics (wireHeaders, sanitizeHeaders) still redact auth from gateway log files.

Anthropic subscription OAuth keeps its provider auth hook. The hook prepares the Messages request and passes it to __gatewayFetch when the gateway is active; without an active gateway it uses fetch. Its final body is a UTF-8 string so the gateway can classify streaming requests and send the complete body through the selected transport. The hook preserves the max_tokens value selected by the model layer; subscription OAuth does not impose a second 64k ceiling. The local wire test in test/plugin/anthropic-auth.test.ts covers HTTP/1.1 without credentials; Anthropic HTTP/2 and HTTP/3 still need separate transport probes. OAuth login and token refresh remain in the auth hook, outside the gateway transport.

8b. OpenRouter routing configuration (2026-09-12)

/agents → ctrl+o opens a model-aware routing editor. It fetches the selected model's live OpenRouter endpoints, offers a radio group for dynamic price, throughput, latency, or OpenRouter-default routing, and exposes checkbox controls for priority order, strict only, quantization, and fallback policy. The initial focus is the first sort choice; arrows skip section headings, and Space, Enter, or a mouse release activate the same row. Selecting a provider switches to manual policy; selecting any dynamic option, including the default, clears the manual provider list.

The form displays effective inherited routing but labels the write destination explicitly. Its Save row is the sole write action; it does not open a second confirmation dialog. In global /agents model editing, model and variant are staged together and committed by one Save and one global-config update. If that save targets the active agent, the current session adopts the same model and variant for its next prompt, overriding older worktree state; an already running request stays pinned to the model recorded when it began.

8c. Per-model sampling controls (2026-09-13)

/agents exposes Sampling parameters (ctrl+g) for every agent's resolved model. The form initializes the balanced thinking-agent profile: temperature: 0.65, repetition_penalty: 1.1, top_p: 0.95, and presence_penalty: 0.2; each numeric field is editable and the final row is the only write action. Session overrides in sessions/{sessionID}.jsonc take precedence over worktree model.json, then over provider.<id>.models.<id>.sampling configuration. The request maps temperature/top-p/presence to the AI SDK's top-level parameters and routes repetition_penalty through the existing provider option transformation.

The OpenRouter endpoint form retains a permanent one-line endpoint-status region while live data arrives. Its headers no longer change between loading and loaded states, avoiding transient text flashes and vertical form jumps.

8d. End-of-turn stop check — the k2 mechanism (2026-10-13)

Owner, 2026-10-13: «в кернеле критический баг. Он позволяет не доделывать работу». The mechanism half ships on this host (session/stop-check.ts); the kernel text is a separate task (t37). At the runLoop exit (session/prompt.ts, the exiting loop branch — a completed turn: finish set, no pending tool call) a primary-agent turn may not END on a prose question/offer to the owner («Хотите, поищу…?» / «Want me to…?») or by handing him an action («запусти …» / «you need to run …»): the decision function fires two detectors — the same ones as the Claude host's Stop hook no_offer_stop.py (trailing question after the SV block is stripped; the hand-off regex after fenced code and quoted «…»/"…"/`…` spans are dropped, so a citation is not a hand-off) — and the runtime continues the turn ONCE with a synthetic user message carrying the reason (metadata.stop_check is the latch, so it can never fire twice in a row, restarts included). A turn that called the question tool is never blocked; sub-agent turns are never blocked; OPENCODE_STOP_CHECK=0|false|off|no disables it. The injection is a tail message — no byte of the system prefix changes (KV).

2026-10-14 — the stop-claim form (owner «Да + механизм», t42 report §1.5/§2). A blocked/stopped outcome is a claim about the whole route space and cannot be verified, only scoped: routes tried, each with a receipt and the failing component; untried routes named; reopen_when. Kernel text binds nothing at runtime (experiments/2026-10-13_kernel-exit-audit/PREDICATES.md: the FSM predicates have 0 runtime consumers), so the form is carried by two mechanisms over one predicate (util/stop-claim.ts):

  • util/plan-status.ts audits every plan under plans/postponed/ and every plan under plans/ whose stated lifecycle is BLOCKED/WAITING/POSTPONED — a missing or incomplete claim is reported as a STOP CLAIM: N plan(s) … violation that NAMES each missing piece (the No checklist contract, never a silent accept). The 8 plans parked before this change are the red set the check exists to surface; fixing them is their owners' decision, not the audit's.
  • session/stop-check.ts adds a third detector: a primary-agent turn whose LAST paragraph declares a blocked/stopped outcome (BLOCKED, «заблокировано», «не могу продолжить», postponed) and carries no valid claim is continued ONCE with STOP_CLAIM_REASON — the same latch, never twice in a row.

9. Key Files

File Purpose Lines
session/prompt.ts Main prompt loop, checkpoint, captureSidecar, Layer-2 cadence gate —
util/plan-status.ts Plan progress + reconcilePlans hygiene; the STOP CLAIM violation for parked/blocked plans —
util/stop-claim.ts The ## Stop claim form: section finder, problems, validity — one home for both surfaces —
cli/cmd/tui/context/agi-mode.tsx AGI loop, plan hygiene integration —
session/llm.ts LLM orchestration; request size ≈ content/4+10k; correlation headers (§8a) —
provider/response-cache.ts OpenRouter response-cache opt-in headers (§8a) —
provider/balance.ts Model status registry: deepseek/openrouter/novita-ai balance, usage windows —
session/system.ts Environment, capabilities, provider prompts —
session/instruction.ts AGENTS.md/rules loading, caching —
session/checkpoint.ts Per-model encrypted checkpoint save/load —
session/incremental-checkpoint.ts Sidecar project_checkpoint CRUD / materialize —
provider/transform.ts Anthropic cache_control breakpoints, prompt-cache key —
session/request-diff.ts Encrypted baseline, diff engine —
session/compaction.ts compact() + legacy summary helpers —
session/overflow.ts Cadence vs safety gates; needsContentCompaction —
sync/index.ts SyncEvent.run / runBatch (finishStep one TX) —
session/message-v2.ts Schema, conversion, filterCompacted* —
session/stop-check.ts k2 end-of-turn stop check: offer/hand-off/stop-claim detectors, decision, latch key —
agent/agent.ts 10 built-in agent definitions 395
tool/pipeline.ts Pipeline tool (agent chaining) 191
tool/capability.ts Model capability lookup tool 101
tool/registry.ts 23 built-in tool registry 376
attachment/handlers/ 14 media type handlers ~800
cli/cmd/tui/component/ Terminal TUI components (31 files) ~3000

Appendix — moved verbatim from AGENTS.md (2026-10-13 split)

(links inside the moved text use repo-root-relative paths, as they were written in AGENTS.md)

Project paradigm — the layer table

Layer Mechanism Kills
1. Task geometry Fractal lattice + cosine filter + k-medoids → CENTRAL_TASKS Monolith plans; goal drift
2. Prior art universalsearch web/code/hybrid; local codegraph Reinvention; guesswork
3. Oracles Baseline [Exact] before edit; post-impl pass before [x] "Works on vibes"
4. Conversation memory Mechanistic compaction → message*; never delete Memory soup
5. Working-copy memory Fossil snapshots + CodeGraph folder-scoped impact "What did we change?"
6. Direction lock SV / decisions preserved; re-cluster against original goal Silent mission creep

The product is a CLOSED loop — closure and safety are one property (2026-09-30)

Owner, verbatim: «любая тема должна быть экономически целесообразной и достаточно замкнутой чтобы не трахать мозги обслуживающему персоналу», and on the statement below: «обвести в рамку и повесить на стенку».

Closure and safety are not in tension — they are the same property. A loop that proves its own results and cannot leave its envelope needs neither a supervisor nor a prompt engineer standing next to it. What industry buys is not a clever model but a stated price per verified task and near-zero human touches.

Safety here is MECHANICS, not warnings: the envelope, an oracle per task, rollback, one store arbiter. A loop is judged by three measured numbers per verified task — Claude/verifier tokens, robot $, and owner touches (answers, restarts, manual fixes); the last one → 0 for the routine class is what «closed» means. Sizing and blockers: plans/2026-09-30_robot-delegation-and-orchestration.md § Scaling.

Agent inventory

Agent Mode Prompt Description
build primary provider family prompt Default full-access development agent
plan primary provider family prompt Read-only planning (denies edits)
orchestrator primary prompt/orchestrator.txt Autonomous orchestrator for AGI mode
general subagent prompt/general.txt Planning, design, root-cause analysis
explore subagent prompt/explore.txt Fast file/code/conversation search
coder subagent prompt/coder.txt Code implementation (edit/write/bash)
researcher subagent prompt/researcher.txt Read-only research (code+web+history)
media subagent prompt/media.txt Media generation via capability tool
title primary (hidden) prompt/title.txt Session title generation

Tools: pipeline chains subagents sequentially. capability looks up model modalities. compact arms a boundary fold of the session window — primaries only (see Mechanistic Compaction § three triggers).

Per-identity sampling. Each native subagent declares its own temperature / topP / options.repetition_penalty in src/agent/agent.ts — verification identities (explorer, coder) are sampled tight and unpenalised so their output is reproducible and their repeated tokens (paths, identifiers) survive; generative ones (general, media) are sampled loose so a candidate set actually differs. Model-wide sampling merges before the agent in session/llm.ts, so the narrower declaration wins. presence_penalty/frequency_penalty are NOT sendable: the vendor documents both as deprecated no-ops and its endpoint rejects them beside repetition_penalty with a 400, so the pair was removed from the whole assembly on 2026-09-30 (plans/2026-09-30_drop-penalty-sampling-params.md) — upstream opencode sends neither. Only what the provider honors binds — see the vendor reasoning contract below.