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 MarkdownReference 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_useblocks instead of the streamed-delta reassembly the compat wire has to do. - A real
count_tokens— the per-component context breakdown in/tokensworks, 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 withKIN_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:
[model_assignments.profiles].<profile>- the consumer assignment (
taskorworkflow) - a model-requested
routethat the operator listed inselectable_for - the legacy per-call
modeloverride - 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 usesprevious_response_id, server Conversations, or background execution. - Kin asks for encrypted reasoning and stores the exact typed terminal
response.outputitems in the existing journal history field. Function results arefunction_call_outputitems keyed by the responsecall_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 aliascontext_window; - maximum input limit: Anthropic’s official
max_input_tokens; - maximum output limit: Anthropic
max_tokens/ OpenRouter-stylemax_completion_tokensortop_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 maxand/effort offmap tochat_template_kwargs.enable_thinking=true/false. Kin does not send Anthropic-nativethinkingoroutput_configfields 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:
- A
GET /v1/modelsprobe on session start (silent-degrade; transport errors and empty listings are no-ops). response.modelpiggybacked 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
/modelform 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
/modelopens a picker with a✓ keybadge 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 sharedapi_key). Providers with no resolvable key show aneeds keyhint; 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 → sharedapi_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_KEY → KIN_API_KEY →
OPENAI_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, andbudget_tokenswith 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 likemin_porpresence_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 — not0— and why you should only set this knob against a serve you know runs the priority policy. - OpenAI-compat wire only. The Anthropic
/v1/messagesprotocol has no priority field (verified against vLLM’sentrypoints/anthropic/protocol.py), so a bare self-hosted URL selected through theKIN_BASE_URLheuristic 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
openaias 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/messagestoo — live-verified on this serve withcurl: 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 typedthinking+textblocks 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 barechoices[0].message.reasoningfield with no signature — ephemeral. If your serve only exposes the OpenAI-side dLLM integration (an older vLLM build, or a custom router), setKIN_PROVIDER=openaito escape the heuristic; thecurlprobe 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 kinKIN_MODEL=defaultalso works — it resolves toRedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamicon this serve (the first alias in/v1/models). The short aliasdgemma-26bis a cleaner handle if you’ve registered it;google/diffusiongemma-26B-A4B-itis 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 smallmax_tokenswill cut the response off mid-thought (stop_reason: "max_tokens",content: null) — the same shape the harness already salvages on Qwen vialoop._salvage_reasoning. SetKIN_MAX_TOKENSgenerously (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-configor the model card’s recommendedDiffusionSamplersettings), not in the request body. The harness’s existing “sampling is OpenAI-compat-only” precedent works in your favor here: anyKIN_TEMPERATURE/KIN_TOP_P/KIN_TOP_Kyou 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 = 0during the denoising loop and only emitsnum_sampled = 256on 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; noKinAppchanges 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
curlagainst 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 400Expected on a healthy dLLM serve: HTTP 200 with a typed
thinkingblock +textblock in thecontentarray. A404 NotFoundErrormeans the serve doesn’t have a dLLM loaded (checkcurl /v1/models); a 4xx validation error means the dLLM is loaded but isn’t wired through the Anthropic adapter on this vLLM version — escape withKIN_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 (
/providersor/modelcross-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.
/tokensnotes this honestly. - Opt-in / off:
/cost off(orKIN_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. Seepricing_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.