Kin / Reference

Models & providers

Reference for choosing a model/backend wire, effort controls, context, and pricing. To connect a provider, follow the canonical /models first-run path.

Read as Markdown

Reference for choosing a model/backend wire, effort controls, context, and pricing. To connect a provider, follow the canonical /models first-run path.

kin separates provider identity from wire shape. Pick OpenAI or Anthropic with provider, pick Chat Completions or Responses with wire_api, pick the model with model, and pick the endpoint with base_url. Each knob resolves in this order: explicit/CLI override, environment, merged settings, preset, then the built-in default. See Configuration for the file precedence rules and Environment variables for the full list.

Current Kinra-hosted topology

Kin’s repository live gates follow two independently qualified routes. The main route is DS4 over OpenAI Responses at the stable private Tailnet relay, with a 131,072-token context window and 32,768-token output limit. Vision and utility side-calls use the one-slot Qwen route on Kloud, with a 32,768-token context window and a 1,024-token output cap. Run both proofs with:

task live-models

Hosted clients use the authenticated public DS4 door instead of copying the private addresses. Create a distinct bearer for each person/device in the Kinra console, then run this in that client’s terminal; the key prompt is masked and the secret is written to ~/.kin/credentials.toml, never argv or shell history:

kin connect kinra-api \
  --base-url https://api.kinra.ai/v1 \
  --wire-api responses \
  --model deepseek-v4-flash \
  --label 'Kinra hosted inference' \
  --vision-route kinra-vision \
  --vision-model qwen3.6-35b-a3b \
  --vision-description 'Kinra hosted vision on Kloud' \
  --vision-max-tokens 1024 \
  --vision-reasoning-profile qwen-llamacpp \
  --vision-effort thinking-512 \
  --utility-route kinra-vision

DS4 advertises exact input counting, tools, tool choice, and reasoning effort. It does not currently advertise Responses text.format, so Kin qualifies the strict tool-schema paths but does not claim server-constrained free-text JSON on this route. Historical vLLM, Laguna, and llama.cpp sections below are explicit compatibility profiles, not fleet defaults.

Chat, Responses, and Messages

There are three wire APIs across two provider families.

Backend provider wire_api Default model Notes
OpenAI Chat openai chat gpt-4o-mini Compatibility default. Works against OpenAI, OpenRouter, vLLM, LiteLLM, Together, and other Chat-compatible servers.
OpenAI Responses openai responses gpt-4o-mini Explicit opt-in. Uses typed Responses items/events and local replay. Qualified implementations include the current DS4 relay, vLLM 0.25.1, and Paddock-managed llama.cpp.
Anthropic Messages anthropic messages (internal) claude-opus-4-8 Native Anthropic, or a vLLM serve that exposes /v1/messages.

Public wire_api values are only chat and responses; either one implies the OpenAI provider. messages is Kin’s internal discriminator for Anthropic history and is not a valid CLI, environment, or settings value. A bare provider = "openai" still selects Chat, and the built-in OpenAI preset stays on Chat. Select Responses deliberately:

uv run kin --wire-api responses --base-url http://localhost:8000/v1
# or: KIN_WIRE_API=responses uv run kin
provider = "openai"       # optional: wire_api already implies OpenAI
wire_api = "responses"

Unknown providers/wires and provider = "anthropic" combined with an OpenAI wire fail at configuration time. An explicit wire_api also bypasses the bare base-URL Anthropic heuristic described below.

The OpenAI-compat backend streams text, reasoning, tool-call fragments, and usage, normalizing the quirks of various compat stacks (it trusts assembled tool calls over a stray finish_reason: stop, and reads reasoning from whichever field the provider uses). The Anthropic-compatible path likewise keeps a complete terminal response when a third-party endpoint omits the final usage object; token usage remains unknown for that response instead of turning already-streamed content into a backend error.

The Responses backend streams typed text, refusal, reasoning, reasoning-summary, function-draft, and terminal events. The terminal response is authoritative for exact output items, completed calls, model identity, usage, and incomplete/failed status. It sends the system prompt as instructions, the output cap as max_output_tokens, reasoning effort as reasoning.effort, and JSON Schema output through text.format. vLLM-specific top_k, priority, and chat-template values remain explicit compatibility extras.

The Anthropic Messages backend uses adaptive thinking with summarized display so reasoning streams, sets effort via output_config, preserves thinking-block signatures across turns so they replay on the same model, and supports prefix caching on the frozen system prompt. The thinking.type knob is knob-shaped (enabled / disabled / adaptive); real Anthropic defaults to adaptive so behavior is unchanged on a vanilla install. The Anthropic-compat presets (Z.ai, MiniMax) don’t implement output_config.effort — their /effort vocabularies map to each provider’s real knob instead (see Effort picker).

The KIN_BASE_URL heuristic

When you set base_url (via --base-url, KIN_BASE_URL, or a settings file) but leave both provider and wire_api unset, the factory defaults to the Anthropic Messages wire, not an OpenAI wire.

The reason is that a base_url almost always points at a self-hosted server (typically vLLM), and on such a server the Anthropic wire is the stronger choice:

  • Parallel-tool-safe blocks — typed tool_use blocks instead of the streamed-delta reassembly the compat wire has to do.
  • A real count_tokens — the per-component context breakdown in /tokens works, where the compat wire reports totals only.
  • Preserved thinking signatures — typed thinking blocks survive across turns for correct replay.

The escape hatch is explicit. Set provider (any of --provider openai, KIN_PROVIDER=openai, or provider = "openai" in a settings file) to force Chat Completions, or select wire_api = "responses" for Responses—for example when base_url points at real OpenAI, a qualified vLLM, OpenRouter, or another GPT-style endpoint.

Note

The heuristic only fires when both provider and wire API are unset. Anthropic-direct users (no base_url) keep OpenAI Chat as the default and opt in with KIN_PROVIDER=anthropic. Setting either identity axis explicitly always wins over the heuristic.

base_url handling

The two SDKs disagree about what a base URL means, so the factory adjusts for the Anthropic wire.

KIN_BASE_URL is conventionally written the OpenAI way, ending in /v1 (the OpenAI SDK appends /chat/completions to it). The Anthropic SDK appends /v1/messages to whatever you give it, so passing http://host:port/v1 would produce http://host:port/v1/v1/messages and 404.

To let one env var drive both wires, the factory trims a trailing /v1 before handing the URL to the Anthropic backend:

KIN_BASE_URL=http://model-host:8000/v1
  → OpenAI-compat backend:  http://model-host:8000/v1   (used as-is)
  → Anthropic backend:      http://model-host:8000      (trailing /v1 trimmed)

You write the URL once, the OpenAI way; the trim happens only on the Anthropic path. With no base_url set, each backend talks to its provider’s public endpoint (api.openai.com / api.anthropic.com).

Model routes

Use /models for the human configuration surface. It opens on plain-language jobs—Main conversation, Images, Computer screenshots, Tasks, Workflows, Utility side-calls, and discovered agent profiles—then lets you assign an existing compatible route or create one inline. vision = false routes are unavailable for image/Rig jobs; unknown support remains selectable with a warning. /routes deep-links to the route editor, while /providers [id] deep-links to Connections.

Connections and routes are staged together. Save & apply validates the entire graph, refuses a stale same-section edit from another Kin, writes once, and installs one prepared live snapshot. Escape discards the whole draft — after a confirmation when edits (including a typed key) are staged, since a form’s ctrl+s only stages onto the draft and nothing is written until Save & apply. The model and AI-drafted settings editor cannot call this human-only transaction.

Connections also lists orphaned keys: a provider_keys entry whose id matches no connection (renaming or removing a connection leaves its key behind under the old id, and swaps read keys strictly by the current id). Select the orphan row and press del to remove the stranded secret, or add a connection with that exact id to reclaim it. kin doctor reports the same strays.

Named model routes let one session use different models or endpoints for different responsibilities without making provider details part of the task. For example, the main collaborative agent (MCA) can stay on a strong text-only model while image inspection goes to a smaller vision model, and repetitive task/workflow children go to a cheaper endpoint.

A route is wire-agnostic and operator-owned. It points to a trusted provider preset plus an exact model id; it cannot contain a URL or key. Provider presets remain the endpoint trust boundary and [provider_keys] remains the credential store. The route and assignment tables are global-only, so a cloned repository cannot redirect your prompts or images to another endpoint.

# ~/.kin/credentials.toml — kin writes this file (0600) for you
[provider_keys]
openai = "sk-..."
anthropic = "sk-ant-..."
# ~/.kin/settings.toml
[model_routes.primary]
provider_preset = "anthropic"
model = "your-text-model-id"
description = "Main collaboration model"
vision = false
effort = "high"

[model_routes.visual]
provider_preset = "openai"
model = "your-vision-model-id"
description = "Isolated image inspection"
vision = true
selectable_for = ["task", "workflow"]

[model_assignments]
mca = "primary"
vision = "visual"
rig = "visual"
task = "primary"
workflow = "primary"
utility = "visual"

[model_assignments.profiles]
researcher = "primary"

openai and anthropic are built-in direct presets, so the example does not need custom endpoint rows. An OpenAI-compatible server still uses an ordinary custom [[providers]] row; use that row’s id as provider_preset in as many routes as needed. A direct preset’s empty base URL means the vendor SDK endpoint and does not inherit an ambient KIN_BASE_URL intended for the default/local backend; an explicitly supplied caller base URL remains the override escape hatch.

Qwen / llama.cpp bounded thinking

[model_routes.kinra-vision]
provider_preset = "kinra-api" # existing custom Chat/Responses connection
model = "qwen3.6-35b-a3b"
vision = true
reasoning_profile = "qwen-llamacpp"
effort = "thinking-512"
max_tokens = 1024

reasoning_profile = "qwen-llamacpp" is an explicit route-local wire contract. It is never inferred from a model id, endpoint hostname, or the fact that a connection uses Responses. Its effort picker uses budget labels: default, off, thinking-128, thinking-512, thinking-1024, and thinking-2048. These do not pretend Qwen implements OpenAI’s semantic low/medium/high scale.

default emits no Qwen fields. off emits only chat_template_kwargs.enable_thinking=false. A thinking-N choice emits chat_template_kwargs.enable_thinking=true and the pinned Kinra gateway’s one validated per-request field, reasoning_budget_tokens=N, on both Chat and Responses. Kin deliberately does not also send current upstream llama.cpp’s thinking_budget_tokens spelling, and never enables preserve_thinking for isolated media or Utility calls.

max_tokens is the total reasoning-plus-final allowance and must exceed the reasoning budget. A route assigned to Utility must additionally leave 512 tokens for title output: 128/512/1024/2048 budgets therefore need at least 640/1024/1536/2560 total output tokens. Kin still clips the visible title to the existing short-title contract. The initial thinking-512 example is a validation candidate, not a claim that 512—or the one-case 128-token result—is optimal.

Resolution and model authority

Kin resolves each delegated call in one fixed order:

  1. [model_assignments.profiles].<profile>
  2. the consumer assignment (task or workflow)
  3. a model-requested route that the operator listed in selectable_for
  4. the legacy per-call model override
  5. the current MCA backend

route and model are mutually exclusive. A profile or consumer assignment is operator policy and therefore wins over a model request. An invalid model clone fails visibly instead of silently falling back to the parent model. fork_agent is different by design: a fork keeps its parent’s exact backend and represented identity.

The task/workflow model sees only selectable route ids, descriptions, and vision state. It never sees endpoint URLs, keys, or even preset/model ids.

The utility job (housekeeping side-calls)

utility binds kin-internal housekeeping to a lightweight route — today that is session auto-titles; other bounded side jobs can opt in individually later. It exists for prompt-cache hygiene: a hosted main endpoint that keeps long conversations resident should not have its cache disturbed by a ~100-token title call, so assigning utility to a small model (the vision route works — one route can hold both jobs) moves that traffic off the main model entirely.

The job is deliberately different from task: task is delegated subagent execution and follows the full resolution order above, while utility is assignment-only — the model can never select it, and it accepts no selectable_for opt-in. Utility calls are bounded (titles cap at 64 output tokens regardless of the route’s max_tokens), best-effort (a route busy with interactive work makes the call skip its idle window instead of queueing), and never fall back: if the utility call fails, kin keeps the first-message fallback title rather than re-running the request on the main backend. Humans can inspect and edit the richer but still secret-free map in /models routes, which shows preset/model identity, credential readiness, assignments, manual/observed vision state, and this run’s call/token totals. Route calls and agent rows carry the same identity in the UI and metadata-only span sink.

Vision routing and manual overrides

vision = true or vision = false is authoritative. This is the direct override for a server whose model metadata is missing or wrong. If vision is omitted, the route begins as unknown; the first isolated semantic media call is attempted, and a successful call or a capability-shaped rejection is cached for the loaded catalog. /reload constructs a fresh catalog and therefore starts discovery fresh.

An @image attachment is inspected through the vision assignment before the MCA turn. That vision request contains exactly the raw user question and image bytes—no root system prompt, tools, conversation history, workspace context, or prior images. Its prose returns as provenance-labelled, untrusted evidence inside the durable user turn. If no vision assignment exists, Kin uses the current MCA only when it is not marked vision = false. A direct inspect_media call then fails with a configuration hint instead of sending the bytes anyway; automatic @image handling preserves the textual turn and adds a framed failure notice when the auxiliary route is unavailable.

A multi-image inspection is one vision request per image, sequentially. Some hosted gateways accept only a single image per request, so Kin never sends more than one image upstream: each image gets its own isolated request (carrying the same question plus a deterministic Image 2 of 3 (name.png) position note), and the labelled observations are combined into one observation for the main turn, in the original order. The single-image request shape is unchanged. If one image’s inspection fails, the error names that image and how many earlier images had succeeded — an image is never silently skipped.

Tool-produced images stay in canonical history. A text-only MCA receives a wire-only projection that replaces image payloads with deterministic refs such as media:call_7:0; canonical history is never mutated. The model can call inspect_media(question, paths, refs) to ask the vision route about those refs. When rig is assigned, Rig browser screenshots and marked desktop snapshots also gain a routed, untrusted visual observation while retaining the canonical image result.

Semantic vision and tool-image transport are deliberately separate. A model may understand an image in an initial user request while its endpoint rejects images nested in tool results (or vice versa); one successful transport probe does not claim the other capability.

Sessions, reloads, budgets, and concurrency

The active route id, non-secret fingerprint, and backend snapshot are saved in the session journal. Resume reconstructs that saved backend rather than retargeting an old conversation through today’s settings. A child likewise keeps the exact route/backend it started with across pause/restart. /reload validates and prepares the entire new catalog off to the side, then installs it atomically for the root and future dispatches; already-running children keep their old catalog snapshot.

Routed agent-loop and isolated-vision calls count toward the root run’s token budget and the /tokens input/output totals, but an isolated vision call does not inflate the MCA context meter. /routes breaks those streamed calls down by route. Pre-existing unmetered title, compaction, and structured-output side calls remain outside that counter. Each route keeps its provider-tier concurrency ceiling, and all routes share a catalog-wide ceiling; permits cover only the network stream, never tool execution, so a child can delegate without holding a nested model lease.

OpenAI endpoints and the Responses API

Direct OpenAI endpoints are supported through the built-in openai preset, and OpenAI-compatible endpoints continue to work through custom provider presets. The built-in stays on Chat Completions as the broad compatibility default. Set wire_api = "responses" on a custom row or top-level settings when the selected endpoint has qualified Responses support.

A custom row may also carry reasoning_effort = "high" (or any level the serve accepts) as the endpoint’s own default — it folds into the resolution chain like the row’s other fields, so selecting the row is enough; no global knob or per-session /effort needed. KIN_REASONING_EFFORT remains the higher-precedence one-run override, including for an Outpost job that selects an explicit effort. On the Responses wire a set effort does double duty: the request carries reasoning: {"effort": ..., "summary": "auto"}, and the summary opt-in is what makes a thinking-by-default serve (DS4, some vLLM Responses adapters) stream reasoning deltas at all. Without it, such a serve emits zero events for the entire reasoning phase, and Kin’s stream-stall watchdog (KIN_STREAM_STALL_TIMEOUT, default 120s) will kill a perfectly healthy multi-minute think as a stalled connection — then retry it, doubling the load on the endpoint. If you point a Responses row at a local reasoning serve, set reasoning_effort on the row.

Kin follows OpenAI’s Responses migration model but keeps conversation ownership local:

  • Every request sends store = false; Kin never uses previous_response_id, server Conversations, or background execution.
  • Kin asks for encrypted reasoning and stores the exact typed terminal response.output items in the existing journal history field. Function results are function_call_output items keyed by the response call_id.
  • Same-endpoint Responses replay is verbatim, including encrypted reasoning. A Responses endpoint change, Chat↔Responses move, or provider change rebuilds neutral text/calls/results/images and drops server item ids, encrypted reasoning, and endpoint-specific metadata.
  • Refusals remain visible assistant output. Reasoning items remain journaled for exact replay but are omitted from reconstructed UI transcripts, matching Anthropic thinking blocks.

The supported core is streaming text/refusals/reasoning, function tools (forced, parallel, and strict schemas), usage/model/incomplete/failure state, JSON-schema structured output, image inputs, and multimodal function results. This iteration deliberately excludes hosted tools, remote MCP tools through the Responses service, background responses, server-side conversations, and other server-managed state. Kin’s own MCP and local tools remain available through ordinary function calls.

The implementation uses only the public AsyncOpenAI.responses SDK surface (openai>=2.43.0). Kin continues to own schema normalization, neutral history conversion, media policy, journals, permissions, and tool lifecycle behavior; it does not vendor OpenAI-generated types or vLLM protocol models. See the typed streaming guide and decision 0141.

vLLM wire behavior

This is an explicit compatibility profile, not the current Kinra fleet default. The connection commands and custom-provider row live in Connect a model and first run. At runtime, a vLLM endpoint follows the heuristic above: with both identity axes unset, a bare URL uses Anthropic Messages; explicit provider = "openai" selects Chat; explicit wire_api = "responses" selects Responses and bypasses the heuristic. model = "default" is the wire-side sentinel—vLLM resolves it to the live served-model-name, and Kin surfaces that resolved id in the welcome banner and top bar.

vLLM 0.25.1 is Kin’s first qualified self-hosted Responses implementation. The on-demand task live-vllm-responses gate requires explicit KIN_BASE_URL and KIN_MODEL, then checks /version for that exact release as qualification evidence, then exercises typed text/reasoning, strict single and parallel calls, structured output, two-turn local replay, journal resume, and model/usage reporting. Vision runs only when the operator declares the served model capable with KIN_VLLM_RESPONSES_VISION=1. /version never participates in runtime backend selection; Kin does not sniff hostnames, model ids, or server versions. See vLLM’s OpenAI-compatible server guide and Codex/Responses integration.

The context window starts from a conservative default and is refined from the server’s /v1/models metadata on the first turn; set KIN_CONTEXT_WINDOW to pin it. For a protected endpoint, that metadata request uses the same authentication and custom headers as the active inference client; a separate unauthenticated probe is not required. Discovery reads the selected model’s entry and normalizes the wire fields to Kin’s semantics:

  • shared total-context limit: max_model_len (vLLM), context_length (OpenRouter / DS4-style), and the common compat alias context_window;
  • maximum input limit: Anthropic’s official max_input_tokens;
  • maximum output limit: Anthropic max_tokens / OpenRouter-style max_completion_tokens or top_provider.max_completion_tokens;
  • exact Responses counting: capabilities.responses_input_tokens = true.

A shared context limit constrains input + requested output + Kin’s safety margin; max_input_tokens independently constrains the prompt, so the admissible prompt budget is the strictest applicable limit. The server’s maximum output capability is metadata only — Kin keeps its configured/requested output reservation. The entry whose id matches the selected model is used; a listing with exactly one entry is also trusted when the id differs (a single-model server — an aliased vLLM served-model-name or LM Studio — answers every request with that model), but with multiple entries and no exact match there is no fallback. Probe failures or malformed metadata silently keep the conservative default. An explicit context_window / KIN_CONTEXT_WINDOW (or an explicit model-window mapping) pins the numeric budget; Kin still reads metadata for optional capabilities. An automatically discovered value is live server state, not a saved pin: resuming the conversation rechecks /v1/models, while an explicit value remains fixed across resume.

The Responses backend calls POST /v1/responses/input_tokens before every model round only when that capability is proven by the selected model entry (or when using OpenAI’s own endpoint). Counting sends the exact prompt-bearing wire fields—input items, instructions, tools, tool choice, reasoning, and endpoint extensions—through the SDK’s typed responses.input_tokens.count resource. The result drives the same ensure_model_round proactive-compaction path as Anthropic Messages. Missing metadata, an unknown third-party endpoint, or an unsupported count route returns None, preserving provider admission and the generic one-shot context-rejection recovery instead of guessing with a client tokenizer. A server can implement this cheaply by rendering and tokenizing without reserving inference capacity or building KV; Kin counts every round because tool results can change prompt size sharply between rounds.

Sampling remains server-side on the Anthropic wire.

Paddock and llama.cpp Responses

Paddock-managed llama.cpp is also qualified for Kin’s text-only Responses path. Select it as an OpenAI custom provider and opt into responses on the provider row:

# ~/.kin/settings.toml
provider_preset = "kin-one-llamacpp"
max_tokens = 131072
context_window = 262144
compact_threshold = 0.45
enable_thinking = true

[[providers]]
id = "kin-one-llamacpp"
label = "kin-one · llama.cpp"
provider = "openai"
wire_api = "responses"
base_url = "http://model-host:5467/v1"
model = "laguna-s-2.1"
models = ["laguna-s-2.1"]
default_model = "laguna-s-2.1"
notes = "Text-only OpenAI Responses wire."

[providers.model_windows]
"laguna-s-2.1" = 262144

enable_thinking = true supplies Laguna’s normal reasoning posture; you do not need to set /effort before using the model. The optional picker exposes the custom OpenAI-compatible auto / on / off vocabulary for a live override.

The on-demand task live-llamacpp-responses gate defaults to this endpoint and model. The 2026-07-22 qualification exercised typed text and reasoning, strict single and parallel function calls, text.format JSON Schema output, same-endpoint typed-item replay through a reasoning → tool round, journal resume, and terminal model/usage reporting. Paddock preserves ordered Responses items and function call_id values while adapting llama.cpp’s Chat-backed translator; it also enables the installed reasoning parser for Kin’s reasoning request and normalizes the private GGUF path back to the public model id.

This is a local compatibility boundary, not hosted OpenAI state. The qualified llama.cpp build emits plaintext reasoning_text plus an empty encrypted_content placeholder. Paddock accepts that pair for same-endpoint local replay but rejects a non-empty hosted encrypted envelope, which is not portable to this endpoint. The worker remains text-only and does not add stored responses, Conversations, background execution, hosted tools, or server-side response retrieval. Kin continues to own the journal and tool execution.

Laguna S 2.1 on vLLM

Laguna is an explicit compatibility profile, not a model-name heuristic. Give the endpoint a custom provider row and select the Anthropic Messages wire so Kin retains the model’s signed thinking blocks across tool rounds:

# ~/.kin/settings.toml
provider_preset = "laguna"
model = "default"
context_window = 262144
max_tokens = 131072
compact_threshold = 0.45
enable_thinking = true

[[providers]]
id = "laguna"
label = "Laguna S 2.1 (staged serve)"
provider = "anthropic"
base_url = "http://model-host:8000/v1"
model = "default"
reasoning_profile = "poolside"

The profile changes only Poolside-specific request shaping:

  • /effort max and /effort off map to chat_template_kwargs.enable_thinking=true/false. Kin does not send Anthropic-native thinking or output_config fields to this serve.
  • Reasoning is retained in full for history/cursor copy. The collapsed TUI row does no body rendering; an expanded row refreshes at most four times per second and shows a bounded head/tail preview.
  • The 131,072-token output cap is a reservation, not merely a generation limit. With the recommended 262,144-token NVFP4 context, Kin admits at most 129,024 prompt tokens and compacts at 45% (117,964) in this example.
  • Before every model round, the Anthropic token counter checks the complete wire request. Older completed turns may compact normally, including a completed prefix that remains too large under the recent-turn keep policy. The current user input always stays verbatim and is refused intact if it cannot fit; only a completed tool continuation may use whole-history emergency compaction.
  • The compaction call itself is capped at 4,096 output tokens and sends enable_thinking=false, so summarizing a long trace cannot start another open-ended reasoning run.
  • Forced structured-output calls also send enable_thinking=false. They are schema-only control-plane requests; leaving Laguna thinking enabled can end the response after reasoning without emitting the selected tool.
  • Laguna S 2.1 is text-only, so the Poolside profile predeclares nested tool-result images unsupported instead of sending a synthetic PNG probe on every new Kin process. Canonical image bytes remain available to a separate semantic vision route; Laguna receives the normal stable text reference. Tool-argument validation is not part of the profile, though a compatible serve is what motivated it: on every backend, syntactically malformed, non-object, or JSON-Schema-invalid tool arguments return an error to the model before any permission prompt or tool execution.

For the NVFP4 checkpoint, Poolside currently recommends vLLM 0.25 or later, the 256K configuration, temperature 0.7, top-p 0.95, max-num-seqs=32, and a quantization-matched 15-token DFlash draft. The DGX Spark recipe is:

export CUTE_DSL_ARCH=sm_121a
export MAX_JOBS=4

vllm serve poolside/Laguna-S-2.1-NVFP4 \
  --speculative-config '{"model":"poolside/Laguna-S-2.1-DFlash-NVFP4","num_speculative_tokens":15}' \
  --enable-auto-tool-choice \
  --tool-call-parser poolside_v1 \
  --reasoning-parser poolside_v1 \
  --served-model-name poolside/Laguna-S-2.1-NVFP4 Laguna-S-2.1 default \
  --default-chat-template-kwargs '{"enable_thinking":true}' \
  --override-generation-config '{"temperature":0.7,"top_p":0.95}' \
  --max-num-seqs 32 \
  --max-model-len 262144 \
  --gpu-memory-utilization 0.85 \
  --host 0.0.0.0 --port 8000

The three served names preserve Kin’s default alias while making the short and canonical identities discoverable. Do not add min_p or logit_bias to this speculative configuration; Poolside notes that vLLM rejects them there. Keep MAX_JOBS=4 for a cold FlashInfer cache on a 128 GB unified-memory host. Run task live-laguna for a safe qualification that exercises count-tokens, replays any signed reasoning the model emits, performs synthetic tool-result replay, forces schema output, and checks thinking-off without executing a real tool. Max mode may choose a zero-token private budget; set KIN_LAGUNA_REQUIRE_SIGNED_REASONING=1 to require the live gate to observe a signed block. See Poolside’s Laguna S 2.1 model card, NVFP4 deployment notes, and release post.

The default alias pattern

If you register a stable alias for “whatever model is currently loaded” on your local server (e.g. vLLM’s --served-model-name default Ornith-1.0-35B …), hardcode that alias in settings:

# ~/.kin/settings.toml
base_url = "http://model-host:8000/v1"
model = "default"

The harness sends model="default" verbatim on the wire (so the server keeps routing to the active model across swaps) and surfaces the resolved name — the canonical first served-model-name entry — in the welcome banner and the top bar (left cell, beside ⭘ kin). Sources of the resolved name:

  1. A GET /v1/models probe on session start (silent-degrade; transport errors and empty listings are no-ops).
  2. response.model piggybacked off each completed turn (so a mid-session server swap self-corrects on the next turn without any restart).

The two paths dedup against a per-session cache — a stable server stays quiet. For Anthropic-direct or OpenAI-direct (base_url unset) with model = "default", the banner shows the literal "default" (real Anthropic / OpenAI have no such alias — the alias only makes sense for local serves that route it).

Swapping at runtime

Use /model inside the session to change the model without restarting:

/model Qwen/Qwen3.6-35B-A3B   # swap to this model id
/model glm-5.2                # cross-provider swap — Z.ai (Anthropic wire, Bearer)
/model                        # open the cross-provider picker

Keys are entered where you need them

A key is stored per-provider in provider_keys[<id>]; each provider keeps its own key, so switching between Z.ai and MiniMax never overwrites the other’s. There are two entry surfaces, both committing through the same locked settings transaction:

  • At swap time. Picking an unkeyed provider from either /model form opens a one-field masked key prompt right there; enter saves the key under that connection’s id and completes the swap in one motion.
  • In Model configuration. /models connections (or the /providers <id> deep link) manages every connection’s credential — replacement, removal, and extra headers — behind Save & apply.

/model swaps anywhere — a missing key is one field away

  • Bare /model opens a picker with a ✓ key badge on every provider that has a key on file (green for a stored per-provider key; dim when it resolves only via an env var or the shared api_key). Providers with no resolvable key show a needs key hint; selecting one opens the key prompt, and a saved key swaps immediately.
  • /model <id> swaps silently when the key chain resolves (provider_keys[id] → matching vendor/generic env → shared api_key). If nothing resolves, the same key prompt opens; esc cancels the swap and leaves the current backend untouched.

The picker (/model bare)

Bare /model opens a keyboard-driven picker spanning every provider — your favorites first (a group), then the built-in presets, your custom [[providers]] endpoints, and the active endpoint’s live /v1/models listing. Arrow keys move, enter picks, f toggles a favorite (persisted to model_favorites in your global settings), esc cancels. Each row shows a ✓ key badge (or a needs key hint) so you can see at a glance which providers are ready. Picking a model on a keyed provider does a live cross-wire swap: the harness rebuilds the conversation history into the target wire’s native shape and re-points the backend. Picking an unkeyed provider opens the one-field key prompt; the saved key lands in provider_keys[<id>] through the hub’s locked transaction and the swap completes.

Inline /model <id> (no picker)

/model <id> swaps in place. The cross-provider path resolves the key for the target preset from provider_keys[id], a matching environment variable, or the shared api_key; if any of those resolves, the swap is silent. Direct OpenAI uses OPENAI_API_KEY then KIN_API_KEY, while direct Anthropic uses ANTHROPIC_API_KEY then KIN_API_KEY—neither can accidentally borrow the other vendor’s key. Compatibility/custom presets retain the broad historical env chain because their wire does not identify the credential issuer. If no key resolves, the inline key prompt opens: enter saves the key under the row’s id and completes the swap, esc cancels and keeps the current backend. The [1m] suffix on Z.ai 1M-tier model ids (glm-5.2[1m]) is stripped before the catalog lookup so the plain id matches. History is stored in provider-native shapes; the cross-provider path converts it for you.

Where the keys live

# ~/.kin/settings.toml
provider_preset = "zai"            # active preset (built-in id or [[providers]] row)
model = "glm-5.2"                  # default model for the active preset
# ~/.kin/credentials.toml — kin writes this file (0600) for you
[provider_keys]                    # one key per provider id — the canonical store
zai = "sk-..."
minimax = "sk-..."
# ~/.kin/settings.toml (continued)

[provider_tiers]                   # model entitlement / subagent capacity tier
zai = "lite"
minimax = "plus"

/models writes these tables when you paste a key and select a plan (its /providers [id] deep link lands on the same Connections form); the save is one locked transaction that merges stored secrets, so adding MiniMax never clobbers Z.ai. Nothing else writes them — /model records only the active provider_preset / model. The tier also sizes ordinary subagent capacity for the curated cloud presets. The factory’s key-resolution chain (make_backend in src/kin/harness/backends/factory.py) is: caller-supplied api_key > provider_keys[<active_preset_id>] (via _preset_key) > preset-appropriate environment key(s) > top-level api_key. For direct OpenAI that environment order is OPENAI_API_KEY then KIN_API_KEY; for direct Anthropic it is ANTHROPIC_API_KEY then KIN_API_KEY. Other presets retain ANTHROPIC_API_KEYKIN_API_KEYOPENAI_API_KEY for compatibility. The active preset is the most specific selector — a stray ANTHROPIC_API_KEY baked into an image can’t silently shadow the Z.ai key you stored in provider_keys.zai. See the settings-toml reference for the full precedence + GLOBAL_ONLY rules.

Saved sessions keep the non-secret provider id, endpoint, model, and auth style, but never the key. On resume, Kin uses that identity to reattach the matching current provider_keys[id] secret while leaving the saved wire configuration unchanged. Pre-id journals use an exact, unique endpoint/auth match; a custom provider whose URL changed is not allowed to send its new key to the old URL.

The mode controls and the rest of the in-session verbs live in Slash commands.

Sampling

Sampling knobs apply on the OpenAI-compat wire only. The backend forwards temperature and top_p as standard params and rides top_k on extra_body. enable_thinking normally follows that OpenAI-compatible path; the explicit Poolside profile is the exception and sends the same chat-template field over Anthropic Messages:

Knob Env Effect
temperature KIN_TEMPERATURE 0 = greedy, higher = more random
top_p KIN_TOP_P Nucleus sampling
top_k KIN_TOP_K Top-k sampling (via extra_body)
enable_thinking KIN_ENABLE_THINKING Qwen/Poolside chat-template soft switch; false hard-disables thinking

Warning

The Anthropic Messages backend sends no per-request sampling — current Opus / Fable models reject temperature, top_p, and budget_tokens with a 400. This includes a vLLM serve reached through the heuristic. Set sampling on the server instead, e.g. with vLLM’s --override-generation-config. That is also the only place to set server-only knobs like min_p or presence_penalty.

See settings.toml reference for the sampling block and the per-knob scope.

Priority-scheduling passthrough (vLLM)

When several kin surfaces share one self-hosted vLLM (interactive sessions, subagent fan-outs, headless cron runs), vLLM can schedule them by priority: serve with --scheduling-policy priority and requests carrying a lower priority integer are scheduled earlier, with genuine KV-swap preemption of lower-priority work when demand spikes (vLLM ≥ 0.23). kin passes this through as request_priority (KIN_REQUEST_PRIORITY env / settings key) → extra_body["priority"] on every request — e.g. KIN_REQUEST_PRIORITY=10 on your cron jobs keeps them behind your live sessions.

Two hard caveats, both server-side facts rather than kin choices:

  • Serve-gated, and it fails loudly. vLLM errors on any request carrying a non-zero priority when the serve doesn’t run --scheduling-policy priority. That is why kin’s default is absent from the request — not 0 — and why you should only set this knob against a serve you know runs the priority policy.
  • OpenAI-compat wire only. The Anthropic /v1/messages protocol has no priority field (verified against vLLM’s entrypoints/anthropic/protocol.py), so a bare self-hosted URL selected through the KIN_BASE_URL heuristic cannot carry it — select OpenAI Chat deliberately for a qualified priority-enabled vLLM. DS4 does not implement this vLLM request field at all; keep it unset on the current fleet. Kin doesn’t fake priority on either unsupported wire, and the per-run token budget remains the provider-independent safety mechanism.

Strict decoding (schema-valid tool calls)

Structured decoding makes the server grammar-constrain tool-call arguments to the tool’s JSON Schema, so malformed args can’t reach the harness. What you actually get depends on the endpoint and the call shape (probed against the fleet’s vLLM 0.23.0 — research/probe_strict_decoding.py has the receipts):

Path Guarantee today
Forced tool calls (the workflow schema= structuring call — force_tool_call, both wires) On vLLM, a named/forced tool_choice is grammar-constrained unconditionally — on /v1/chat/completions and the /v1/messages bridge. The OpenAI-compat wire additionally ships a strict-normalized schema + strict: true so OpenAI-proper-class endpoints enforce too.
The streaming tool registry (tool_choice: "auto") Opt-in via strict_tools / KIN_STRICT_TOOLS=1 (OpenAI-compat wire only). Each tool spec is normalized (additionalProperties: false, all-required, optionals as ["type","null"] unions) and stamped strict: true at request-build time. vLLM 0.23 ignores strict on auto (no-op); vLLM ≥ 0.24 and OpenAI-proper grammar-enforce it.

The Anthropic Messages wire never sends strict — real Anthropic rejects unknown tool fields, and the vLLM bridge already constrains forced calls without it. Either way the harness keeps its client-side net: malformed argument JSON still parses to {} (the tool then reports the missing args), and the workflow schema= result is still validated against your original schema — server enforcement makes that net near-never-fire; it doesn’t replace it.

A tool schema that can’t be normalized losslessly (an open additionalProperties, an unsupported keyword like uniqueItems, an empty enum) is shipped unmodified without strict — better unenforced than a 400. All bundled kin tools normalize cleanly.

Google’s DiffusionGemma 26B A4B is a discrete diffusion language model (dLLM): it denoises 256-token canvas blocks in parallel rather than generating one token at a time. vLLM shipped first-class day-0 support in June 2026 (the vLLM blog post walks the architecture — speculative-decoding path, entropy-bound sampler, dynamic per-sequence causal attention). It serves from a normal vllm serve with the model id set.

A few practical notes for kin:

  • Wire — prefer the heuristic default (Anthropic), with openai as the fallback. vLLM shipped day-0 dLLM support in June 2026 via the speculative-decoding path (originally OpenAI-side), and recent builds (≥ v0.22.1rc1) wire it through /v1/messages too — live-verified on this serve with curl: both wires return HTTP 200 for the dLLM model id, and streaming works on both with the expected chunk cadence. For dLLMs that think, the Anthropic wire is actually the better default: the response is typed thinking + text blocks with a signature, so the harness’s existing reasoning handling carries the thinking blocks across turns replay-safe the same way it does on Qwen / MiniMax / Z.ai serves. The OpenAI wire puts reasoning in a bare choices[0].message.reasoning field with no signature — ephemeral. If your serve only exposes the OpenAI-side dLLM integration (an older vLLM build, or a custom router), set KIN_PROVIDER=openai to escape the heuristic; the curl probe at the end of this section is the verification.

    The full canonical recipe:

    KIN_BASE_URL=http://model-host:8000/v1 \
    KIN_MODEL=google/diffusiongemma-26B-A4B-it \
    uv run kin

    KIN_MODEL=default also works — it resolves to RedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamic on this serve (the first alias in /v1/models). The short alias dgemma-26b is a cleaner handle if you’ve registered it; google/diffusiongemma-26B-A4B-it is the upstream HF id.

  • Reasoning budget — give it space to think. DiffusionGemma is a reasoning model (the model card describes configurable <|think|> control tokens). A small max_tokens will cut the response off mid-thought (stop_reason: "max_tokens", content: null) — the same shape the harness already salvages on Qwen via loop._salvage_reasoning. Set KIN_MAX_TOKENS generously (8k–32k is fine; the dLLM’s per-block throughput is high enough that the wall-clock cost stays modest).

  • Sampling — server-side, not request-side. DiffusionGemma uses an Entropy-Bound (EB) sampler rather than temperature / top_p / top_k — the model walks canvas positions from most to least confident, accepting tokens until accumulated entropy exceeds a fixed budget, and commits the block once the argmax has been stable for N steps. Configure EB on the vLLM serve (--override-generation-config or the model card’s recommended DiffusionSampler settings), not in the request body. The harness’s existing “sampling is OpenAI-compat-only” precedent works in your favor here: any KIN_TEMPERATURE / KIN_TOP_P / KIN_TOP_K you set are silently ignored by the dLLM backend, so they don’t break anything — they just don’t do anything.

  • Streaming cadence. The vLLM dLLM sampler reports num_sampled = 0 during the denoising loop and only emits num_sampled = 256 on the commit step. From the client’s perspective you’ll see 256-token chunks landing on the SSE stream rather than token-by-token deltas — the harness consumes them transparently; no KinApp changes needed. Total throughput is roughly 5–6× a comparable autoregressive model (~1,000 tok/s on H100, ~1,290 tok/s on H200 for FP8), so the user-perceived cadence is “small delay, then a 256-token block snaps in, then another, then another” rather than a per-character trickle.

  • Wire-support probe. A quick curl against your serve confirms whether dLLMs are wired through /v1/messages:

    curl -sS http://model-host:8000/v1/messages \
      -H 'content-type: application/json' \
      -d '{"model":"dgemma-26b","max_tokens":256,"messages":[{"role":"user","content":"hi"}]}' \
      | head -c 400

    Expected on a healthy dLLM serve: HTTP 200 with a typed thinking block + text block in the content array. A 404 NotFoundError means the serve doesn’t have a dLLM loaded (check curl /v1/models); a 4xx validation error means the dLLM is loaded but isn’t wired through the Anthropic adapter on this vLLM version — escape with KIN_PROVIDER=openai.

Cost display

Cost display is opt-in (default off): kin’s primary endpoint is local vLLM (no cost at all), and the subscription providers (MiniMax/z.ai) bill flat-rate, so per-token cost is noise for most sessions. Turn it on with /cost on (persists to ~/.kin/settings.toml) or KIN_PRICING=1 / pricing_enabled = true. /cost (bare) reports the current state and, when on, the running total; /cost off turns it back off and resets the running total.

Once on, /tokens shows a running USD cost total alongside the per-component token breakdown, and the StatusBar carries a recessive $0.04 cell on its right edge (next to the mode badge). The figures come from a local, stdlib-only pricing table in harness/pricing.py — keyed by (provider, sanitize_model_id(model)) so the SAME model-id drift tolerance that protects the wire-side display covers the pricing boundary too.

Coverage and rates (2026-07 snapshot, per million tokens, USD):

Provider Models Notes
Anthropic Opus 4.6+ / Sonnet 4.x / Haiku 4.x Incl. prompt-cache read + write rates. The kin default claude-opus-4-8 is the current generation ($5/$25), not the legacy 4.1 premium tier.
z.ai GLM-4.5 / 4.5-Air / 4.6 / 4.7 / 5.x Fleet default glm-5.2 at $1.40/$4.40. glm-4.5-flash deliberately omitted (not on the current z.ai pricing page).
MiniMax M3 / M2.x List price (≤512k input tier for M3).
OpenAI gpt-4o / 4.1 family When kin is pointed at real OpenAI.

Unknown / unpriced serves — your local vLLM (Qwen3.6-27B, Ornith-1.0-35B, dgemma-26b, the default alias), a model id the table doesn’t carry — surface nothing rather than a misleading $0.00 (free by omission, never a fake zero). Add a row to PRICES in harness/pricing.py when a new fleet model lands; the rate is a snapshot, not a live scrape, so revisit the cited provider page when prices change.

Accuracy boundary (cache-write approximation)

The kin Usage shape collapses Anthropic’s per-component tokens into three fields: prompt_tokens = input + cache_read + cache_creation, cached_tokens = cache_read, completion_tokens = output. The cache_creation count — which Anthropic bills at ~1.25x the input rate — is folded into prompt_tokens and indistinguishable from a fresh input token at this layer. compute_cost therefore charges cache-creation tokens at the input rate, a documented underestimate. The bound is tight in practice: cache writes happen once per cache lifetime (5 min / 1 h), not per round, so for an agentic loop with many cache hits the missed 25% surcharge applies to a small slice of total tokens.

Edge cases

  • Provider swap (/providers or /model cross-provider): the running total resets because the new provider’s per-token rate differs from the old one (Opus $5/M → GLM $1.40/M makes pre-swap cents incomparable to post-swap). The pre-swap total rides forward in a system-note footnote so you can still see what the prior provider cost.
  • Resume: usage events aren’t durably stored in the journal, so a resumed session starts the counter at $0 and accumulates only the new turns. /tokens notes this honestly.
  • Opt-in / off: /cost off (or KIN_PRICING=0 / pricing_enabled = false) disables the accumulator entirely (no table lookup, no display cell) and clears the running total. This is also the default — nothing to opt out of unless you’ve already turned it on. See pricing_enabled.

Effort picker

/effort <level> flips a per-serve reasoning-depth knob live, and /effort (bare) opens a picker that’s curated for the active backend — not a server-cap probe. The vocabulary differs by wire and by endpoint — the two Anthropic-compat presets don’t implement Anthropic’s output_config.effort at all, so they get their providers’ real knobs instead:

Active backend Vocabulary Wire knob
AnthropicBackend (real Anthropic, vLLM Anthropic serve) low / medium / high / xhigh / max output_config.effort
AnthropicBackend against the MiniMax preset adaptive / enabled / disabled thinking.type (M2.x silently ignores disabled)
AnthropicBackend against the Z.ai preset max / high top-level reasoning_effort (the only two operative values on GLM-5.2; max is Z.ai’s default)
AnthropicBackend with the Poolside profile max / off extra_body.chat_template_kwargs.enable_thinking
OpenAIBackend against OpenAI / OpenRouter low / medium / high top-level reasoning_effort
OpenAIBackend against Z.ai OpenAI-compat (GLM-5.2+) auto / max / xhigh / high / medium / low / minimal / none top-level reasoning_effort
OpenAIResponsesBackend against a custom Responses endpoint (wire_api = "responses") max / xhigh / high / medium / low / minimal / none (pass-through; the serve owns which levels it accepts) reasoning: {"effort": ..., "summary": "auto"}
OpenAIBackend against a vLLM OpenAI-compat serve (Qwen-style, Chat wire) auto / on / off extra_body.chat_template_kwargs.enable_thinking (auto = unset)

The picker treats the (default) row as “leave it to the server / wire defaults” — pick it on cold start to wipe the override. Picking always invalidates the prompt cache, so inline /effort <level> pops a confirmation modal first; the picker is the confirmation by itself.

The setter is HUMAN-ONLY — the model has no set_effort tool and reaches the knob only through propose_settings effort=..., which still requires your approval before anything changes. Resuming from the journal restores the active level; the active wire knob stays put so a resume_config() against anthropic.py / openai.py re-points it on reload. See Slash commands for the /effort row + the cache-invalidation caveat.

First-party presets

For MiniMax and Z.ai, the harness ships a curated preset that bundles the URL, model id, and auth style. Set provider_preset = "minimax" in ~/.kin/settings.toml (or KIN_PRESET=minimax uv run kin, or uv run kin --preset minimax) and the harness picks the right wire and auth header automatically. The /models connections hub in the TUI walks the same catalogue interactively and writes the choice back to settings; /providers [name] is its deep link.

See Provider presets for the catalogue, the Connections section, custom endpoints, and the GLOBAL_ONLY rationale.