Repository navigation
research provider model params
Status 2026-10-15: the serverTools candidate design below shipped as issue #56 — durable version in
design-server-tools.md. The other follow-ups remain open.
How each ProviderModelConfig field (#51 registry) is actually consumed by
the platform assistant and each client Agent target, and what each target's
native config could additionally express. Research only — no code changed, no
decisions made here. Companion docs: design-provider-model-registry.md
(the registry), design-phase-9-w10-llm-providers.md (providers),
design-phase-9-harness-runtime.md §4.3 (the W3 push).
Method — three-way cross-verification:
-
Local code:
server/src/assistant/provider.ts(assistant model construction),cli/src/daemon/runtime-config.ts(the five W3 writers),core/src/domain/{llm-provider,runtime-config}.ts, shared zod schemas. -
Rig container (read-only): the installed pi bundle's TypeBox
ModelsConfigSchema(pi 0.85.1),@deepseek-ai/dsh-llm-pi-ai.d.tsdeclarations (dsh 0.2.0-rc.2), config-key strings in the codex binary (codex-cli 0.154.0). Versions in the container: pi 0.85.1, dsh 0.2.0-rc.2, codex-cli 0.154.0, opencode 1.18.31. -
Web/doc verification (Oct 2026): Claude Code official docs (line
2.1.296), openai/codex config reference + codex-rs source (master @3ee47c1,
tag
rust-v0.154.0diffed), sst/opencode source atv1.18.31/v1.19.0/ master +https://opencode.ai/config.json,@earendil-works/pi-ai@1.1.0and@earendil-works/pi-coding-agent@1.1.0/1.2.0npm tarballs,@deepseek-ai/dsh-*@0.2.0-rc.2npm tarballs.
Version anchors worth remembering: the pi repo moved
(badlogic/pi-mono → earendil-works/pi); pi-coding-agent npm latest is
1.2.0 while the rig runs 0.85.1 (1.x models.json adds inputLimits +
promptCache, otherwise the schema below is the same); dsh bundles pi-ai
0.87.x, not 1.x; opencode 1.19.0's provider/model schema is byte-identical
to 1.18.31 (master is the "V2" rewrite — re-verify wire behavior before any
major pin bump); codex keys below are unchanged between 0.154.0 and 0.162.1.
Registry fields per model: regular displayName, contextWindow,
maxOutputTokens, vision (+ id, type chat|embedding); advanced
temperature (0–2), topP (0–1), serverTools (meaningful for
openai-responses only), extraBody (meaningful for openai-compatible only).
The web dialog exposes all of them; api-irrelevant combos WARN, never reject.
-
Assistant (
provider.ts): the only full consumer. Regular fields → pi-aiModel(fallbacks 128k/8192 when unset). Advanced fields → per- request injection instreamFn:temperature→StreamOptions.temperature;topP/extraBody→samplingParams(top_p+ verbatim merge);serverTools→samplingParams.tools = [{type}](responses kind only). -
W3 push: the spec snapshot carries
modelEntries— the regular-fields subset only; advanced params never leave the server (by design). Writer utilization differs sharply per target (matrix below).
✅ consumed today · 🟡 expressible on the target but not written today ·
❌ no surface on that target (silently ineffective) ·
| Field | Assistant (pi-ai 1.1.0) | claude-code | opencode 1.18.31 | codex 0.154.0 | dsh 0.2.0-rc.2 | pi client 0.85.1 |
|---|---|---|---|---|---|---|
| displayName | ✅ Model.name
|
❌ | ✅ name
|
🟡 model_catalog_json display_name only |
🟡 models entry name
|
✅ name
|
| contextWindow | ✅ (fallback 128k) | ✅ env CLAUDE_CODE_MAX_CONTEXT_TOKENS (#60 panel; [1m] is the alternative) |
✅ limit.context (informational only) |
🟡 root key model_context_window
|
🟡 models entry contextWindow (route default 262,144) |
✅ contextWindow
|
| maxOutputTokens | ✅ (fallback 8192) | ✅ env CLAUDE_CODE_MAX_OUTPUT_TOKENS (#60 panel, global) |
✅ limit.output |
❌ key removed; unknown keys silently ignored | 🟡 models entry maxTokens (becomes per-request default; route default 32,768) |
✅ maxTokens
|
| vision | ✅ input:['text','image']
|
❌ (tier _SUPPORTED_CAPABILITIES is an effort/thinking-style list, not modality) |
🟡 modalities.input:['image'] / attachment:true
|
❌ | 🟡 models entry input
|
✅ input
|
| temperature | ✅ all three wires |
🟡 env CLAUDE_CODE_EXTRA_BODY (one global JSON, not per-model) |
🟡 entry temperature is a BOOLEAN capability gate; the value lives in agent.<name>.temperature
|
❌ no config key, no body passthrough | ❌ programmatic GenerateOptions only; no producer sets it |
❌ no static field anywhere; per-request RPC options only |
| topP | ✅ openai wires only (anthropic ignores samplingParams entirely) |
🟡 same EXTRA_BODY | 🟡 agent.<name>.top_p only |
❌ | ❌ | 🟡 per-model samplingParams (openai wires only) |
| serverTools | ❌ | ❌ (responses excluded for opencode) | ❌ (despite being the Responses target) | ❌ | 🟡 would ride samplingParams.tools — same clobber risk as §4.2 |
|
| extraBody | ✅ openai wires only | ✅ env CLAUDE_CODE_EXTRA_BODY (#60 panel, global blob) |
🟡 models.<id>.options → verbatim body keys on the openai-compatible wire |
❌ (responses_api_metadata is a string map, not body) |
❌ | 🟡 per-model samplingParams
|
| model list (W13) | ✅ picker | ✅ availableModels (documented allowlist semantics) |
✅ provider models map |
🟡 model_catalog_json (whole-catalog replace) vs today's ACP-only rewrite |
✅ models: list |
✅ models[] + enabledModels
|
-
StreamOptions.temperatureis first-class; all three adapters forward it into the request body. The anthropic-messages adapter gates it:temperature !== undefined && !thinkingEnabled && compat.supportsMidConvoEffort !== true && compat.supportsTemperature— i.e. it is silently DROPPED when thinking is enabled, on managed mid-conversation-effort models, and on models whose compat flags it off (the flag doc: "Claude Opus 4.7+ rejects non-default temperature values"). -
StreamOptions.samplingParamsis merged into the request body by the OpenAI-compatible adapters ONLY (completions, responses, azure-responses); the anthropic-messages adapter has zero references to it —topP/extraBody/serverToolssilently vanish on the anthropic wire. -
Model.samplingParams/samplingParamsByThinkingLevel/compatexist on the Model type (and in pi's models.json — §3.6) but the assistant's provider build sets none of them.
serverTools upstream history (pi issues, checked 2026-10-15). Three
community proposals exist, all auto-closed no-action by the repo's
contribution gate (github-actions[bot]; CONTRIBUTING: "All issues and PRs
from new contributors are auto-closed by default. Maintainers review
auto-closed issues daily and reopen worthwhile ones") and never reopened:
- #9560 + PR #9556 (2026-09-13, closed in seconds,
merged: false) —Model.serverToolsin models.json/modelOverrides: raw API-native tool entries APPENDED verbatim to the request's tools array by the openai-responses AND anthropic-messages transports (follows thesamplingParams/headerspassthrough precedent; replaces wholesale in modelOverrides). Empirically verified by the author: GLM coding plan executes{"type":"web_search"}on its Responses proxy (api.z.ai/api/v1), Anthropic executesweb_search_20250305on the Messages wire; server-side output items (web_search_call/server_tool_use) are already harmlessly ignored by pi's stream processing. The author also ran it as an extension ("wrap the built-in transport, inject the tool") — the pattern that works WITHOUT core support, and exactly the seam ourbuildGatewayModelalready has (we pass the api module intocreateProvider, so a wrapper adapter can append entries to the request tools before calling through). - #7704 (2026-08) — same concept as
builtinToolsinsideOpenAIResponsesCompat(DeepSeek V4 Flash web_search motivation); auto- closed, not adopted. - #8661 (2026-08) — client tools NAMED
web_searchare rejected by Responses upstreams ("custom function name "web_search" is reserved", upstream-dependent); proposed translating such client tools into the native{type:"web_search"}insideconvertResponsesTools. Not adopted either, but the reserved-name 400 is a live wire constraint.
Net: no released pi-ai (1.1.0/1.2.0) and no master packages/ai carries
server-side tool support (the lone serverTools code hit is an unrelated
local variable in the MCP extension). The append-not-replace semantics and
the both-wires scope of PR #9556 are the reference design if we ever fix
our assistant mapping; note Anthropic's dated tool types
(web_search_20250305) mean our bare-string serverTools list would need
full entry values to cover the anthropic wire.
3.2 claude-code (verified against the 2.1.205 binary + official env-vars/model-config docs; our writer: settings.json env)
Writer today (#60): env.ANTHROPIC_AUTH_TOKEN/ANTHROPIC_API_KEY
(panel-chosen slot), optional ANTHROPIC_BASE_URL, model,
availableModels, plus the spec.claudeCode knobs — tier pins, [1m]
markers, subagent, token caps, extra body, six behavior switches (see
design-claude-code-config.md for the full map). Additional verified
surface (all settable from the same settings.json we already own):
-
ANTHROPIC_DEFAULT_{SONNET,OPUS,FABLE,HAIKU}_MODEL— alias-tier pins, each with_NAME/_DESCRIPTION/_SUPPORTED_CAPABILITIEScompanions that DO take effect on gateways (ANTHROPIC_BASE_URLthird-party). The capabilities value is a comma-separated list (effort,thinkingexample; open vocabulary — declares which capabilities the pinned model supports). A fifth slotANTHROPIC_CUSTOM_MODEL_OPTION(+ companions) adds one custom /model picker entry (unused by us: allowlist + tiers supersede it). - The
[1m]suffix — claude-code's 1M-context switch on a model alias or full name (sonnet[1m],gw-plus[1m]): the id is sent marker-stripped with thecontext-1m-2025-08-07beta, and for ids it does not know it ASSUMES a 1M window (thenCLAUDE_CODE_MAX_CONTEXT_TOKENSdoes NOT apply;CLAUDE_CODE_DISABLE_1M_CONTEXTturns the variants off). -
CLAUDE_CODE_SUBAGENT_MODEL(+_FORCE) — subagent default model. -
CLAUDE_CODE_MAX_OUTPUT_TOKENS— request output cap; unresolvable model ids default 32,000, cap 128,000. One global value (not per-model). -
CLAUDE_CODE_MAX_CONTEXT_TOKENS— overrides the assumed context window; for unrecognized non-claude-ids (i.e. gateway aliases — our case) it applies directly. AlsoCLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT. -
CLAUDE_CODE_EXTRA_BODY— a JSON object merged into the top level of EVERY request body; the documented escape hatch for provider-specific parameters. Could carrytemperature/top_p/extraBody keys, but it is one global blob per install — per-model values would collide across the switchable set. - Behavior switches (#60 ground truth, cc-switch's live writer agrees):
settings
attribution = {commit:'', pr:'', sessionUrl:false}hides attribution (successor of the deprecatedincludeCoAuthoredBy);CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1;ENABLE_TOOL_SEARCH=true(UNPREFIXED — its documented spelling);CLAUDE_CODE_EFFORT_LEVEL(low/medium/high/xhigh/max);DISABLE_AUTOUPDATER=1(unprefixed);CLAUDE_CODE_DISABLE_ARTIFACT=1(settingsdisableArtifactequivalent). -
availableModelssemantics confirmed: restricts the /model picker AND--model/env/setting values, subagent/skill model pins (our W13 usage is the documented pattern;enforceAvailableModelsexists for managed locks); a tier pin the allowlist rejects would dead-letter the alias — our writer unions every managed id into the list, markers stripped. - No surface for: per-model display names or vision/capability declarations
behind a gateway beyond the tier
_NAME/_SUPPORTED_CAPABILITIESpins (gateway discovery reads onlyid/display_name/descriptionfrom the gateway's own/v1/modelsresponse;CLAUDE_CODE_ENABLE_GATEWAY_MODEL_ DISCOVERY=1populates the picker from it — unused by us). - Deprecated trivia:
ANTHROPIC_SMALL_FAST_MODEL→ANTHROPIC_DEFAULT_HAIKU_MODEL(our writer retires the legacy key on sight).
provider.<id>.models.<modelId> is additionalProperties: false; accepted
keys: id (wire-id override), name, family, release_date,
attachment, reasoning (BOOLEAN capability), temperature (BOOLEAN
capability — gates whether agent temperature is sent at all), tool_call,
interleaved, cost, limit (context + output REQUIRED, input?),
modalities, experimental, status, provider, options
(Record<string,any>), headers, variants. Entries MERGE onto the
models.dev catalog entry of the same id (per-field ??, deep-merge for
options/headers/variants).
- Sampling VALUES live in
agent.<name>.temperature/agent.<name>.top_p(+agent.<name>.options), not on the model entry; the model entry'stemperature: trueonly enables sending. We write no agent config today. -
limit.outputis a real request cap:maxOutputTokens = min(limit.output, OUTPUT_TOKEN_MAX)with a hardcoded globalOUTPUT_TOKEN_MAX = 32000(envOPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAXoverrides); it also sizes anthropic thinking budgets.limit.contextis informational (context-remaining UI, auto-compaction) and never sent. - Extra-body equivalent:
models.<id>.optionsmerges into theproviderOptions[<provider>]namespace and@ai-sdk/openai-compatiblespreads every unknown key VERBATIM into the JSON body (this is how opencode's ownenable_thinking/chat_template_argsreach the wire). No such passthrough for the@ai-sdk/anthropicflavor. Caveat: atemperaturekey inside modeloptionswould override the explicit agent param on the openai-compatible wire. - Vision is expressible (
modalities.input: ['image']orattachment: true) — our writer currently omits it.
Writer today (post-#63): root model + model_provider + the
[model_providers.harness_nexus] block + auth.json, PLUS the #63 panel
dials (model_reasoning_effort / model_reasoning_summary /
model_verbosity / model_context_window /
model_auto_compact_token_limit / check_for_update_on_startup) —
see design-codex-config.md. Additional surface:
- Root key
model_context_windowEXISTS in 0.154.0 (overrides the catalog's context window; clamped to the catalog'smax_context_window; unknown models fall back to 272k metadata). Single global value per config. -
model_max_output_tokensdoes NOT exist (zero occurrences in the repo; serde does not deny unknown fields, so a stale key is silently ignored). - No
temperature/top_panywhere (config, request payloads, docs). No arbitrary body merge:responses_api_metadatais a validated string map attached as client metadata, not body;query_params/http_headersare URL/headers. -
model_catalog_json(root or profile; landed 2026-02, present in 0.154.0): path to a JSON file in the Codex-nativeModelsResponseshape ({models: [{slug, display_name, description, default_reasoning_level, supported_reasoning_levels, visibility, context_window, max_context_window, …}]}). It REPLACES the whole catalog for the process (startup-only, ≥1 model). The in-session /model picker lists from it (or from<provider.base_url>/models— which must speak the Codex-native shape, not OpenAI/v1/models). This is the natural landing spot for our registry's model list + displayName + contextWindow — and it would silence the codex-acp "Model metadata for<id>not found" notices (chat.tsCODEX_MODEL_METADATA_NOTICE). Not used by our writer today. #63 verdict: rejected for now — the catalog owns the SYSTEM PROMPT.ModelInfodeserialization REQUIRES each entry to carrybase_instructions(ormodel_messages.instructions_template— the deserializer errors "missing both"), andget_model_instructionsreturns the template VERBATIM (no template → empty instructions + warn). There is no built-in-prompt fallback on that path, so owning the catalog means freezing codex's instruction text per version — fragile. Stays a design question. -
disable_response_storageis REMOVED (source-verified 2026-10-16: zero occurrences in 0.154's root config ANDModelProviderInfo; absent from the current official config reference). cc-switch's codex template still writes it at root — a silently-ignored dead key on current codex (serde does not deny unknown fields). Do not copy it. - Reasoning dials that exist but have no registry counterpart:
model_reasoning_effort(open-ended enum; docs list minimal–xhigh),model_verbosity(low|medium|high),[profiles.<name>]pinning model+effort+verbosity (profiles notably do NOT carrymodel_context_window).
Writer today (post-#64): settings.yaml llm-pi-ai.providers.harness-nexus
with displayName/api/baseURL/apiKeyEnv and per-model detail on the
models: entries (name / contextWindow / maxTokens / input for vision,
precedence panel > #51 seed > omitted — bare ids stay byte-identical for
untouched specs; see design-deepseek-config.md). The plugin's actual
PiAiModelProfile also accepts per entry: reasoningEfforts (false |
level→wire map) and compat (incl. supportsTemperature, thinkingFormat,
chatTemplateKwargs) — both OUT of #64 v1 (per-gateway wire spellings we
cannot guess; follow-ups).
Provider-level extras on the route: defaultContextWindow (default
262,144), defaultMaxTokens (default 32,768), defaultInput, compat,
headers, reasoning (route thinking level), thinkingBudgets,
cacheRetention, transport, timeouts, image budgets, retryPolicy.
Unset model fields default from the installed catalog entry of the same id
(our route ids are unknown to the catalog, so entries really start empty).
No sampling knobs: no temperature/top_p/samplingParams anywhere in the
settings schema. The internal seam (GenerateOptions.temperature) is
programmatic only; no dsh producer sets it. dsh-llm's own README: sampling
is temperature/maxTokens/stop only, "the vocabulary grows when a
producer lands".
models.json is JSONC validated by a compiled TypeBox schema
(ModelConfig.load). Provider fields: name, baseUrl, apiKey,
api, oauth, headers, compat, authHeader, models[],
modelOverrides{}. Per-model: id (required), name, api, baseUrl,
reasoning, thinkingLevelMap, input, cost, contextWindow,
maxTokens, samplingParams (free Record<string,unknown>),
samplingParamsByThinkingLevel, headers, compat (1.x adds
inputLimits, promptCache). Sampling merge order (pi-ai
resolveSamplingParams): {...model.samplingParams, ...samplingParamsByThinkingLevel[level], ...requestParams}.
- Our writer already lands name/contextWindow/maxTokens/input when present
(#65 adds the panel overlay
spec.pi.models+ the panel-onlyreasoningflag andsamplingParamsfree body keys — seedesign-pi-config.md). -
topP/extraBodyCOULD ride per-modelsamplingParams— effective on the openai wires, silently ignored on anthropic (same semantics as the assistant; consistent with the registry's warn-not-reject stance) (shipped #65 as the panel's 采样参数 editor). -
serverToolshas no dedicated field; ridingsamplingParams.toolshas the same clobber risk as §4.2. -
temperaturehas NO static surface: not in models.json, not in settings.json (55 fields, none sampling), no CLI flag, no TUI knob. Only per-request options carry it (the RPC request-options struct does — a seam our daemon-side PiRpcConnection could theoretically drive).
-
Assistant
serverToolsclobbers the agent's own tools (pi-ai 1.1.0). Inopenai-responses.jsthe client tools are built first (params.tools = convertResponsesTools(...), ~L258) andObject.assign(params, samplingParams)runs LAST (~L287, "Last so model and request sampling parameters override named request fields") — a shallow merge that REPLACES the tools array. The assistant sends its AgentTools (M2 MCP bridge +read_skill) on every turn, so any registry entry withserverToolsset strips them all. The #51 S0 spike verified the seam exists but not the merge semantics. (Same risk exists for pi clients viasamplingParams.tools.) -
Anthropic-wire temperature is silently gated in the assistant
(thinking enabled /
supportsTemperatureoff / mid-convo-effort models → dropped), andtopP/extraBodyare silently ignored on that wire. The web dialog warns only forserverToolsIrrelevantandextraBodyIrrelevant— these two paths have no warning. -
Fallback divergence: the assistant and the opencode writer fall back
to 128k/8192 for unsized models; dsh's own defaults are 262,144/32,768;
codex assumes 272k for unknown ids. Not a bug, but a dsh push of an
entry without
contextWindowmakes dsh assume 262k where the assistant would assume 128k — same registry row, different effective window.
-
Tier 1 — pure wins, no design tradeoff: dsh writer emits
name/contextWindow/maxTokens/inputper models entry (shipped #64 — panel value > #51 seed > omitted); opencode writer adds vision (modalities/attachment); codex writer adds rootmodel_context_windowfrom the default model's entry (shipped #63 — panel value > seed > key-removed, with the full dial set). -
Tier 2 — small tradeoffs to write down: claude-code writer derives
CLAUDE_CODE_MAX_OUTPUT_TOKENS/CLAUDE_CODE_MAX_CONTEXT_TOKENS/CLAUDE_CODE_EXTRA_BODYfrom the DEFAULT model's entry (all three are one-global-value-per-install — extras in the switchable set do not benefit; needs explicit doc); pi writer mapstopP/extraBodyinto per-modelsamplingParams. -
Fix-first: the §4.1/§4.2 assistant issues. For
serverToolsthe upstream reference is PR #9556 (§3.1): APPEND raw tool entries to the request tools — implementable without forking pi-ai by wrapping the api adapter module we already pass intocreateProvider(the same "extension wraps the transport" pattern the pi contributor shipped); extending the registry value shape (full entries, not bare strings) is a prerequisite for the anthropic wire's dated tool types. The §4.1 warning gap (temperature/topP silently dropped on the anthropic wire) is a shared-code warning addition. -
Candidate design for
serverTools+extraBody(2026-10-15, simplified — supersedes the earlier union-with-alias-map sketch): ONE reusable creatable multi-select interaction (chosen items render as removable × tags, arbitrary typed values allowed) for BOTH fields, with preset option lists per api kind, baselined on Alibaba Bailian/Model Studio (docs.bailian.console.aliyun.com/zh/model-studio/web-search, 2026-10):-
serverTools(value staysstring[], wire-ready WYSIWYG — no alias map, no request-build translation): openai-responses presetsweb_search/web_extractor/code_interpreter; anthropic preset IS the dated wire valueweb_search_20250305(picked from the list, never hand-typed; Bailian notes the date suffix is client-version- dependent and not server-validated; extra fields likemax_usesare deliberately out of scope for the string form); openai-chat has NO tools[]-shaped server tools on this wire — hint text instead, because Bailian's chat-wire search rides body params. -
extraBody(扩展参数, same interaction): keys are tag-selected with presets per kind — openai-chat:enable_search/search_options/enable_text_image_mixed/enable_thinking; openai-responses:enable_thinking; anthropic: none — then each selected key gets a value editor (toggle for known booleans, JSON input otherwise). On the openai-chat wire this field is not merely an escape hatch: it is the ONLY carrier for Bailian-style search. Typed custom entries in either combobox ARE the gateway-compat escape hatch (a future dated alias, a vendor-specific tool name) — no separate full-object arm. With append-injection in place (§3.1 adapter-wrapper fix),serverToolsIrrelevantnarrows to openai-chat. Reserved-name 400s (client tool literally namedweb_searchon a Responses wire, #8661) accepted as low priority: MCP bridge tools are<server>__<tool>- prefixed, only custom tool names can collide, and the upstream error message is self-explanatory.
-
-
Design questions, not urgent: adopting codex
model_catalog_json(solves multi-model + displayName + the metadata-not-found notices, at the cost of owning the WHOLE catalog replacement); whether assistant advanced params deserve a third warning class (temperature×anthropic×thinking).
- Claude Code: code.claude.com/docs/en/{model-config, settings, settings-reference, env-vars, llm-gateway, llm-gateway-protocol}; anthropics/claude-code CHANGELOG.
- codex: developers.openai.com/codex/{config-reference, config-schema.json};
github.com/openai/codex
codex-rs/{config/src/config_toml.rs, config/src/profile_toml.rs, model-provider-info, models-manager, model-provider, protocol/src/openai_models.rs}; discussion #7782. - opencode: opencode.ai/docs/{providers, config, agents, tui};
opencode.ai/config.json; github.com/sst/opencode at
v1.18.31(packages/core/src/v1/config/{provider,agent}.ts,packages/opencode/src/session/llm{,/request}.ts,packages/opencode/src/provider/{provider,transform}.ts); @ai-sdk/openai-compatible@2.0.41 dist. - pi: npm
@earendil-works/pi-ai@1.1.0dist (types.d.ts, api/*.js, api/simple-options.js) +@earendil-works/pi-coding-agent@1.1.0/1.2.0(dist/core/model-config.js, settings-manager.d.ts); rig bundle (pi 0.85.1 TypeBox schema); github.com/earendil-works/pi. - dsh: npm
@deepseek-ai/dsh-llm-pi-ai@0.2.0-rc.2(lib/types/*.d.ts + lib/index.js) + dsh-llm/dsh-agent/dsh-settings tarballs; github.com/deepseek-ai/deepseek-harness; rig install.