Run and observe
Server status, metrics, UI, and config
GET /health — readiness plus bounded backend, memory, batching, graph, training, and provenance diagnostics.
GET /v1/health — the same health response under the OpenAI-compatible prefix.
GET /metrics — Prometheus latency, throughput, memory, scheduler, graph, and training metrics.
GET /ui — the embedded dashboard for status, requests, adapters, training, evals, and configuration.
GET /v1/stats/decode — live decode rate and inter-token latency.
GET /v1/stats/recent-requests — bounded recent request history and latency phases.
GET /v1/models — the served model identity.
GET /v1/config — immutable effective startup configuration and resolved runtime policy.
GET /v1/debug/model-state — gated trusted-debug state; routine operations should use health and metrics.
The generated Observability API Schema is the
canonical field, requiredness, nullability, enum, and closed-object contract for these
JSON responses and for GET /v1/cache/stats. The generated
HTTP API Contract records that health returns the same
diagnostic body with HTTP 200 when ready and HTTP 503 when degraded or in maintenance,
and records the gated debug endpoint's distinct HTTP 403 and provenance-validation HTTP
500 bodies. The operational guidance below does not redefine those wire contracts.
Deep reference: resolved runtime configuration
Policy IDs, backend decisions, graph state, memory ownership, and typed provenance
Use GET /v1/config to inspect the immutable startup configuration,
effective backend policy, graph state, capacity resolution, and live memory snapshot.
Each typed setting reports its resolved value, source, canonical environment name,
redaction state, and restart requirement. Kiln keeps configuration intent separate
from backend-selected execution policy and reports unsupported or unavailable
measurements as null rather than inventing values.
curl -fsS http://localhost:8420/v1/config | python3 -m json.tool
curl -fsS http://localhost:8420/v1/config | jq '.effective_configuration'
See the Configuration Reference for common settings,
the Complete Configuration Reference for
every field and precedence rule, and the
Observability API Schema for the exact
response shape.
The loader hashes every immutable startup-snapshot shard once and retains a strict
kiln.base-weight-shards.v1 manifest. Health exposes a base-weight aggregate;
trusted debug state exposes the complete base-weight shard manifest. Training and eval
artifacts reuse this identity without reading model files mid-inference. Legacy
aggregate-only checkpoints cannot establish exact shard identity. See the
base-weight provenance contract.
Production startup also constructs a strict kiln.execution-provenance.v1 record over the backend/device, numerical runtime, exact executable, optional source revision, model/tokenizer/template, precision, compiled kernel contract, and effective configuration/environment digests. Response metadata names the typed configuration digest effective_config_hash; the separate provenance environment digest includes redacted presence markers for secret-bearing KILN_* inputs. Health exposes only the bounded identity summary and fails readiness if a real backend lacks a valid record; the gated debug endpoint returns the complete typed record without echoing raw process values. See the execution provenance contract.
First requests
Copy-paste first requests
Once Kiln is serving on localhost:8420, start with chat.
The SFT and GRPO examples use the default stable profile and
require a backend whose runtime capability report admits the requested
training workload.
Both examples leave the new adapter inactive so evaluation can decide
whether it should be served.
First chat completion
curl -fsS http://localhost:8420/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 128,
"thinking_budget_tokens": 32
}' \
| python3 -m json.tool
First SFT correction submission
curl -fsS http://localhost:8420/v1/train/sft \
-H "Content-Type: application/json" \
-d '{
"examples": [{"messages": [
{"role": "user", "content": "Hi"},
{"role": "assistant", "content": "Hey there!"}
]}],
"config": {
"training_profile": "native_online_lora_v1",
"output_name": "first-sft",
"epochs": 3,
"invalid_row_policy": "fail",
"auto_load": false
}
}' \
| python3 -m json.tool
First GRPO scored-completions submission
curl -fsS http://localhost:8420/v1/train/grpo \
-H "Content-Type: application/json" \
-d '{
"groups": [{
"messages": [{"role": "user", "content": "Name a warm color."}],
"completions": [
{"text": "Orange", "reward": 1.0},
{"text": "Blue", "reward": 0.0}
]
}],
"config": {
"output_name": "grpo-demo",
"behavior_policy": "no_importance_correction",
"kl_coeff": 0.1,
"auto_load": false
}
}' \
| python3 -m json.tool
Training status check
Use the CLI for a friendlier summary, or hit the endpoint directly:
kiln train status
curl -fsS http://localhost:8420/v1/train/status | python3 -m json.tool
Advanced flows
Copyable advanced requests
After the first chat and training jobs work, these examples cover provenance-bound rollouts,
text-only batch generation, adapter portability, adapter merging, per-request composition, and training-completion webhooks.
Upload, merge, and live composition are coordinated adapter mutations
supported by the default stable profile; use
maintenance only when inference must be drained.
Recorded-policy rollout dataset
kiln rollout-generate \
--adapter base \
--thinking false \
--tasks tasks.jsonl \
--seeds 8 \
--request-template request.json \
--scorer ./score_one.py \
--output rollouts.scored.jsonl
The command requires exact server-issued behavior provenance, validates it before scoring,
and atomically publishes JSONL ready for behavior_policy: "recorded".
Text-only batch completions
curl -fsS http://localhost:8420/v1/completions/batch \
-H "Content-Type: application/json" \
-d '{"prompts": ["Name a warm color.", "Name a cool color."], "max_tokens": 32, "seed": 7}' \
| python3 -m json.tool
This endpoint returns text, not trainer-ready scored groups, and
does not emit per-token behavior probabilities. Score and reshape
the completions before training, then explicitly use
behavior_policy: "no_importance_correction".
Chat and batch requests accept ignore_eos: true for bounded fixed-length
generation. EOS ids are treated as ordinary generated tokens, explicit
stop sequences still apply, and the effective max_tokens
remains a hard bound. The flag is cache-keyed and cannot be combined with
rollout_provenance: true until that provenance schema can represent the
altered EOS policy.
Download and upload an adapter archive
curl -fsS http://localhost:8420/v1/adapters/default/download \
-o default-adapter.tar.gz
curl -fsS http://localhost:8420/v1/adapters/upload \
-F "archive=@default-adapter.tar.gz" \
-F "name=default-copy" \
| python3 -m json.tool
Merge adapters with TIES
curl -fsS http://localhost:8420/v1/adapters/merge \
-H "Content-Type: application/json" \
-d '{
"output_name": "merged-ties",
"mode": "ties",
"sources": [
{"name": "default", "weight": 0.7},
{"name": "grpo-demo", "weight": 0.3}
]
}' \
| python3 -m json.tool
Concatenate adapters into a wider LoRA
curl -fsS http://localhost:8420/v1/adapters/merge \
-H "Content-Type: application/json" \
-d '{
"output_name": "merged-concat",
"mode": "concat",
"sources": [
{"name": "default", "weight": 1.0},
{"name": "format-fixes", "weight": 1.0}
]
}' \
| python3 -m json.tool
Compose adapters per request
curl -fsS http://localhost:8420/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Write a short checklist."}],
"max_tokens": 96,
"adapters": [{"name":"default","scale":0.7}]
}' \
| python3 -m json.tool
The on-disk composition cache is keyed by each source’s name, scale, and exact content revision. Source resolution, hidden staging, atomic publication, live swap, and eviction share the adapter revision barrier, so a same-name source rewrite cannot serve a stale composition.
Notify another service when training completes
# kiln.toml
[training]
webhook_url = "https://example.internal/kiln/training-complete"
# or configure the same target with an environment variable
export KILN_TRAINING_WEBHOOK_URL="https://example.internal/kiln/training-complete"
kiln serve --config kiln.toml
Inference
OpenAI-compatible generation
POST /v1/chat/completions — chat completions with OpenAI-shaped request and response bodies, including SSE streaming, normalized tool-call deltas, and opt-in exact rollout provenance.
POST /v1/completions — bounded, vLLM-shaped prompt-logprobs scoring for remote OPD teachers (no generation-only mode, no streaming; prompts capped at the smaller of 4096 tokens and the served model context).
POST /v1/completions/batch — multi-prompt text generation, with request-wide effective budget provenance in metadata.thinking_budget and completion-specific outcomes, but without per-token behavior-policy provenance.
The generated Inference API Schema is the canonical,
complete field reference for these three endpoints: request requiredness and nullability,
runtime bounds, ignored-unknown compatibility policy, response closure, SSE event variants,
thinking and performance metadata, prompt-logprob maps, and exact rollout provenance. The
HTTP API Contract is the canonical route, media, status, header,
and handler inventory. The guidance below explains workflows and operational tradeoffs; it
does not redefine either machine-readable contract.
Deep reference: latency attribution
Phase ownership, nullability, streaming events, and qualification evidence
Attribute first-token latency
Set include_performance: true on a chat request to receive terminal
metadata.performance. Batching-engine responses partition TTFT into
actor_queue_ms (API enqueue to admission start),
actor_admission_ms (slot preparation), and
actor_prefill_wall_ms (admission completion to first sampled-token readiness),
alongside accumulated model prefill_ms, decode_ms, TTFT, and total
latency. resident_prefill_used is request-scoped route evidence: it is
true only after a successful native multi-row resident-prefill forward included
that request, false for batching requests that never entered the route, and
null for direct paths. It is not inferred from process-global counters, and a
mutation-free native decline remains false. Production currently advertises
resident_prefill_enabled=false, so all batching requests must report
resident_prefill_used=false. Non-batching paths return the actor
fields as null. Both real-model
streaming paths publish a performance object on the terminal chat chunk; explicit opt-in
also emits a separate kiln.token_timing SSE object for each model token with a
bounded path source, exact accepted token_id, plus producer-ready/delivered,
handler-received, and response-queue times. The token ID remains observable when a special
token decodes to an empty text fragment, without enabling logprobs or behavior capture.
Direct streams retain the same request summary while leaving actor-only phases
null and classifying otherwise unpartitioned gaps as
unexplained. Batching-engine streams also propagate exact, invocation-owned
gpu_lock_wait_ms and synchronization_ms candidates. ROCm sampled
paged decode also reports its distinct post-transformer final-norm, LM-head, filtering,
selection, and token-return tail as sampling_ms; behavior-logprob capture
reports the same boundary across backends. Qualified ROCm W8 sampling splits the existing
token-index transfer into readback_ms without adding work and subtracts it from
sampling to prevent double-counting; other readback routes remain null until their owner
exposes the exact boundary. Greedy or native fused-forward routes leave sampling
null when no independent boundary exists. Qualified ROCm direct and batching
graph routes also report request-owned graph_capture_ms and
graph_replay_ms. One graph-runner lock encloses the before/call/after phase
snapshot, capture time survives eager fallback, and replay launch excludes the separately
reported external-yield synchronization. A shared batched invocation is attached only to
the ready requests whose rows participated, so request totals are overlap evidence and may
duplicate device work by at most observed decode width; lifetime graph telemetry remains
the process-work total, and ROCm qualification enforces that conservation bound.
Other-backend graph, resize/trim, adapter, and
training phases remain null until their owning runtime returns correlated
timings; unlocked process-global counter deltas are never presented as per-request
evidence. Mixed-load, development-soak, and endurance
qualification receipts retain each phase as a measured-window millisecond total plus an
observation count, so unsupported null remains distinct from a measured zero.
A successful measured request without terminal phase metadata fails qualification, and
broad actor and narrower backend totals must be ranked within their layer rather than
summed as a critical path. The complete phase, overlap, reason-set,
nullability, rolling-statistics, and Prometheus contract is documented in
Latency Observability.
The ROCm mixed-load qualification runs an isolated fixed-seed sampled profile before its
greedy measurement window: eight concurrent requests, 32 tokens each, temperature 0.7,
top-p 0.9, top-k 40, and min-p 0.0. Dedicated sampled_profile_* metrics retain
aggregate completion-window throughput, median per-request throughput, broad decode and
sampled-tail costs, nullable readback population, and batch-width evidence. The driver
drains the actor and captures a new health baseline before ordinary load, so the profile
cannot contaminate established mixed-load counters. A pass requires sampled attribution
for every request, no separately claimed readback timing, and proven multi-row decode.
Greedy measured requests bind prompt identity
variant_invariant_fixed_output_v5. They request exactly 64 ascending
six-digit integers while stating that server truncation before the target is expected.
At roughly seven model tokens per integer, the target exceeds every 32-, 128-, and
256-token measured cap without the output-limit refusals caused by million- and
1,024-item measured requests. The prompt requires immediate sequence output, forbids
explanation, refusal, summaries, and output-limit discussion, and uses only nonnumeric
itemNN padding that is explicitly unrelated to output length. The separate
unvalidated stalled socket retains a 1,024-integer fill target for its 4,096-token cap.
Receipts record the prompt identity,
response_oracle_target_integer_count = 64,
slow_response_target_integer_count = 1024, and
long_prefill_marker_role = "long-prefill". The diagnostic and acceptance
paths therefore tokenize the same named long request for exact A/B configuration identity.
Slow-consumer containment uses a 256-token pressure peer, twice the ordinary response
length. It dispatches that peer before opening the stalled socket
and waits for that peer's first producer-ready token. Acceptance then requires additional
producer-ready tokens during and after the request-attributed backpressure window. The
typed schedule records
pressure_peer_dispatch = "before_slow_start_after_first_token", proving
continuity of an already-decoding request instead of comparing a queued peer with a
pressure interval that began first.
Score prompt tokens
Send token IDs to POST /v1/completions with max_tokens set to
0 or 1 and prompt_logprobs from 0 through
256. This serialized scoring route is intended for identity-bound teacher
queries, not high-throughput generation.
Deep reference: prompt-scoring guarantees
Result shape, validation, backend routes, limits, and settlement
Set max_tokens to 0 or 1 and request
prompt_logprobs from 0 through 256. The first result is
null; each later position uses the preceding logits row and includes the observed
prompt token plus top K, so its map has K or K+1 entries. K=0 returns only the observed token.
Scores remain F32, extra observed tokens report full-vocabulary rank, and split UTF-8 display
tokens use preceding actual prompt context. Invalid token IDs, decoding failures, non-finite
values, and vocabulary-width mismatches fail closed. Text prompts default to
add_special_tokens: true; -1 all-vocabulary requests are unsupported.
The model ID must match the served base model, active LoRAs are rejected until adapter revision
identity can be pinned, and the response is capped at 65,536 candidates. Scoring takes exclusive
GPU admission, explicitly settles each projection chunk and final scorer state, and drains a timed-out
worker before responding. Failed or panicked settlement quarantines the backend and retains admission.
Every response carries a canonical teacher identity whose inference-contract v2 hash binds the complete
startup-resolved streaming-prefill policy. Changing mode, threshold, any tile, or final-tile LM-head
behavior changes the teacher revision and invalidates identity-bound logit caches and OPD resume bindings.
CUDA and ROCm validate every logit and derived F32 log-probability on device, compute stable
normalization and the observed token's exact full-vocabulary rank, and select top K from original
logits with ascending token-ID tie breaking. Each row transfers 36 fixed bytes plus 12 bytes per
requested candidate, so device-to-host transfer and host work are O(TK); the kernel still scans all
V logits to retain exhaustive validation and exact semantics. Vulkan, Metal, and CPU retain the
correctness-first O(TV) host fallback inside the same 64 MiB and 32-row projection-chunk bounds.
Both routes use the same cancellation, external-yield settlement, and backend-quarantine boundary.
Route selection follows the compiled backend and has no tuning variable. Prompt scoring remains a
serialized teacher query, not a high-throughput generation route. Prometheus exposes only settled
chunks and rows through
kiln_prompt_logprob_selection_chunks_total and
kiln_prompt_logprob_selection_rows_total, labeled with the closed routes
compact_device and bounded_host_fallback.
{
"prompt": [1, 42, 314],
"max_tokens": 0,
"prompt_logprobs": 5,
"add_special_tokens": true
}
Capture an exact training rollout
Set rollout_provenance: true on one non-streaming chat choice to receive a
validated kiln.rollout-provenance.v1 record at
choices[0].rollout_provenance. The record binds the resolved seed, exact prompt
and generated token IDs, sampled post-filter log-probabilities, runtime-forced tokens,
behavior/base/adapter content identity, tokenizer and template hashes, effective sampling
and thinking-budget controls, template invocation, scored content, and generation backend.
{
"messages": [{"role": "user", "content": "Solve 47 + 138."}],
"temperature": 0.9,
"max_tokens": 64,
"seed": 42,
"stream": false,
"n": 1,
"rollout_provenance": true
}
This correctness-first path requires real model weights, the batching engine, a
content-addressed base-policy identity, and room for at least one sampled action. It bypasses
text-only request/completion caches. Unsupported streaming, multiple-choice, mock, unverified,
or tool-definition/tool-choice paths return
rollout_provenance_unavailable; malformed trace state fails generation instead of
returning a partial record. Prior assistant tool_calls and role: "tool"
responses are supported context; their names and call IDs are preserved and bound into the
prompt hash. Use kiln rollout-generate to validate, score, and atomically persist
these records for behavior_policy: "recorded".
Bound thinking without disabling it
Set thinking_budget_tokens, thinking_budget_ms, or both on a chat or
batch request. An omitted field inherits its server default; explicit null makes
that dimension unlimited, even when the server has a default; 0 closes an open
thinking block immediately. See the
Thinking Budget Contract
for the normative wire schema and executable semantics.
{
"messages": [{"role": "user", "content": "Solve this carefully."}],
"max_tokens": 512,
"thinking_budget_tokens": 128,
"thinking_budget_ms": 3000
}
When both budgets are active, the first reached wins. The time clock starts at the first
decode candidate, excluding queue and prefill, and Kiln checks it between tokens. A natural
</think> wins if it arrives first. Otherwise Kiln feeds the forced close-tag
tokens into model context and continues decoding the answer. Those tokens count toward
max_tokens and completion usage. Budgets are inert unless the rendered prompt
starts inside thinking.
Time-budgeted requests bypass deterministic completion caches because their boundary depends
on runtime speed. Token-budgeted requests remain cacheable under a budget-aware key. Official
OpenAI clients can send these Kiln extensions through extra_body.
Deep reference: thinking-budget results
Outcome fields, streaming placement, request history, and metrics
{
"triggered": true,
"trigger": "tokens",
"closed": true,
"thinking_tokens": 128,
"thinking_time_ms": 742
}
This outcome appears on each non-streaming choices[].thinking_budget and
completions[].thinking_budget. trigger is tokens,
time, or max_tokens; a natural close reports
triggered=false with closed=true. Chat
and batch metadata.thinking_budget also report effective limits and each
dimension's source: request, server_default,
request_unlimited, or unlimited. Batch outcomes remain per
completion rather than being aggregated at the root. In SSE streams, the chunk containing
finish_reason carries chat metadata and the final outcome before the optional
usage chunk and [DONE]. Cached responses preserve their original outcome while
reporting provenance for the current request.
Each /v1/stats/recent-requests row includes the same effective pair in
thinking_budget, plus applied and any final
triggered, trigger, closed,
thinking_tokens, and thinking_time_ms fields. Outcome fields are
absent for inert or not-yet-resolved budgets instead of fabricating a natural close. The
dashboard request drill renders the limits, independent sources, application state, and outcome.
Prometheus uses only closed label sets: kiln_thinking_budget_source_total
counts each dimension's provenance, kiln_thinking_budget_outcomes_total
counts unconfigured, inert, natural-close, forced-close, unclosed, interrupted, and
unresolved outcomes, and kiln_thinking_budget_effective_tokens plus
kiln_thinking_budget_effective_seconds are fixed-bucket histograms. Numeric
request limits never become labels.
Server defaults
# kiln.toml; omit either setting for unlimited
[server]
default_thinking_budget_tokens = 512
default_thinking_budget_ms = 5000
# equivalent environment variables
KILN_SERVER_DEFAULT_THINKING_BUDGET_TOKENS=512
KILN_SERVER_DEFAULT_THINKING_BUDGET_MS=5000
Adapters
LoRA lifecycle
Adapter reads and coordinated loading, unloading, uploading, merging,
deleting, or otherwise changing adapter weights are available under
the default stable profile. maintenance performs
the same mutations with inference drained.
GET /v1/adapters — list saved/available LoRA adapters, identify the active adapter, and report the exact loaded name/content revision.
POST /v1/adapters/load — load an adapter from disk, or explicitly reload its already-live revision at the request barrier, and return the exact content revision.
POST /v1/adapters/unload — unload the active adapter.
DELETE /v1/adapters/{name} — delete an idle adapter; active or physically loaded adapters return 409.
GET /v1/adapters/{name}/detail — files, training history, and eval history for one adapter.
GET /v1/adapters/{name}/receipt — the adapter's train_receipt.json.
GET /v1/adapters/{name}/download — export an adapter as a tar.gz archive.
POST /v1/adapters/upload — stage, validate, and atomically publish a multipart tar.gz archive.
POST /v1/adapters/merge — stage and atomically publish a weighted average, TIES, or concatenation merge.
Adapter identity is content-addressed rather than name-only. A successful load returns content_revision; the list response exposes the authoritative loaded_adapter_identity; /health exposes loaded_adapter_revision; and chat responses include x-kiln-loaded-adapter-revision (base without a LoRA). The revision is published with the weight flip and binds queued work plus prefix and deterministic response caches, so an in-place rewrite cannot reuse results produced by the prior bytes.
The load body accepts required name plus default-false allow_quarantined and reload booleans. reload: true re-reads an already-live exact on-disk revision outside the actor barrier, then waits for active requests before republishing weights and invalidating affected caches; an ordinary repeated load remains a no-op. Health and trusted debug state expose actor_barrier_adapter_active and actor_barrier_resize_active, with matching kiln_batching_engine_actor_barrier_*_active Prometheus gauges. Those process gauges identify a live boundary; an individual request's nullable metadata.performance.latency.phases.adapter_ms or resize_ms remains the causal record of whether it waited.
Upload, merge, delete, training publication, eval-gate demotion, and live weight transitions share one serialized revision barrier. Hidden staging directories never appear in the adapter list. Delete returns adapter_active when the name is the server default and adapter_loaded when its revision is physically loaded; both are HTTP 409 responses with an unload-and-retry hint. Gate demotion swaps loaded rejected weights to base before renaming them to .failed.
Public-mutation qualification verifies the load, list, request-header, unload,
graph-invalidation, memory-resize, failure, and cleanup boundaries on the backend and
device named by each receipt. A device-specific result is evidence for that run; it does
not define a product default or restrict other compatible devices. See
Hardware Qualification
for the protocol and current receipts.
Training
SFT, GRPO, OPD, status, and queue control
The default stable serving profile supports the complete
train, eval, and activation loop with coordinated live weight transitions.
maintenance admits drained training but disables generation
and eval; its artifact remains inactive until a later stable
process evaluates or loads it. GPU training
takes exclusive accelerator ownership even though jobs are managed by
a background queue. Native training is capability-gated: for example,
the current hybrid Vulkan server rejects it before dataset admission
and directs broader workloads through the verified HF/TRL handoff.
POST /v1/train/sft — submit supervised fine-tuning examples and return the exact effective seed.
POST /v1/train/hf/sft/exports — atomically publish an immutable SFT handoff with exact model, tokenizer, template, corpus, provenance, optional split/adapter, and pinned runner identities.
POST /v1/train/hf/grpo/exports — atomically publish an immutable recorded-GRPO handoff from exact inline groups or server-local canonical JSONL.
GET /v1/train/hf/exports — list server-owned HF/TRL export summaries.
GET /v1/train/hf/exports/{name}, /v1/train/hf/exports/{name}/download — fully revalidate one export, then return its manifest or stream its .kiln-hf tar.gz; creation, detail, and download expose the quoted export identity as ETag.
DELETE /v1/train/hf/exports/{name} — durably delete a server-owned export after it has been downloaded. Send the prior ETag as If-Match to refuse a concurrently replaced name with HTTP 412; omit it only for deliberate operator cleanup, including damaged bundles.
POST /v1/train/hf/peft/imports/{name} — stream one exact {name}.kiln-hf-import tar.gz, verify both manifests and every transported byte, require exact resident model/tokenizer/template identity and loadable PEFT tensor shapes, then publish without replacement. Success returns the import-receipt digest as ETag and the adapter content revision.
POST /v1/train/grpo — submit a GRPO batch of prompts, completions, and rewards and return the exact effective seed.
POST /v1/train/agentic — canonical alias of /v1/train/grpo for multi-turn trajectory rollouts (ECHO applies automatically).
POST /v1/train/opd — submit on-policy or off-policy distillation prompts against a registered, identity-bound teacher and return the exact effective seed.
POST /v1/train — intent-tagged SFT, GRPO, OPD, or distillation front door with the same effective-seed contract.
POST /v1/distill/refresh — distinct fail-closed DistillRefresh workload; no job or seed is created until admission pins separate exact SFT and OPD phase plans, exact SFT rows, and the maximum sequential working set.
POST /v1/distill/pump, /v1/distill/self, /v1/adapters/distill_merge — OPD-backed distillation jobs with exact effective seeds.
GET /v1/recipes — list built-in recipe descriptors with static admission {supported, unavailable_reason} derived from every step’s workload and optimizer/rank tuple.
POST /v1/recipes/run — queue a typed multi-step recipe and return effective_seeds keyed by job ID.
POST /v1/agent/judge_distill, /v1/agent/self_improve — queue agent-training phases and return their exact seed or per-job seed map.
GET /v1/train/status — summarize training queue, job state, and effective seeds.
GET /v1/train/status/{job_id} — inspect one training job.
GET /v1/train/jobs/{job_id} — rich job detail (base-weight identity, loss curve, linked evals, and latest exact SFT/GRPO/OPD resume checkpoint).
DELETE /v1/train/jobs/{job_id} — remove an archived terminal job.
GET /v1/train/queue — list queued training jobs.
DELETE /v1/train/queue/{job_id} — cancel a job: queued jobs leave the queue; running jobs stop at the next step boundary.
An admitted SFT, GRPO, OPD, or OPD-backed distillation job resolves its seed before it
enters the queue. Set config.seed, or omit it and retain the decimal-string
effective_seed returned by submission and status. Resume inherits the
checkpoint’s LoRA-initialization seed and rejects a conflicting request seed. A seed
is an initial condition, not a promise of byte-identical results across builds, backends,
devices, precision policies, or environments.
Deep reference: training admission and identity
SFT loss, native profiles, optimizers, recipes, ingestion, and checkpoint planning
Native SFT renders TRL’s prefix-preserving Qwen3.5 training template with add_generation_prompt=false. Loss covers the thinking/answer/tool-call body, <|im_end|>, and trailing newline; assistant role headers plus system, user, and tool-response turns use the -100 ignore label. Exact rendered text, token IDs, and labels are checked against source-pinned Hugging Face and TRL sources for plain, thinking, tool-call, tool-response, and multi-turn examples. See SFT Tokenization and Loss for the normative contract and reproduction command.
Native SFT accepts one fixed profile: native_online_lora_v1. One conversation is one optimizer update at a constant learning rate; accumulation is 1, with no warmup, decay, or gradient clipping. Unknown profile names and general-trainer fields return structured training_invalid_request errors. Server SFT always normalizes omitted train_mtp to false and rejects explicit true before queue publication; the deferred MTP alignment phase remains offline-only until it participates in server GPU coordination, memory admission, cancellation, and settlement. A hybrid Vulkan server deliberately returns training_backend_unsupported before dataset admission: its serving weights use CPU-host handles, while the multi-GiB full-model resident Vulkan training path has not passed the production qualification gates. There is no environment bypass; use HF/TRL export/import until that substrate is proven. See the Native SFT Profile for update, precision, optimizer-state, checkpoint, and MTP semantics. Use the HF/TRL route for broader training: SFT and recorded-GRPO exports snapshot their canonical corpus, exact model/tokenizer/template provenance, optional split, and optional adapter into one private immutable registry, embed the task-aware pinned runner, and revalidate every byte before download. GRPO accepts exactly one provenance-complete inline group array or server-local canonical JSONL path. kiln train hf export-sft and export-grpo wrap creation, redirect-free streamed download, strict single-root archive validation, manifest verification, atomic no-clobber local publication, and default server cleanup; list and delete manage retained exports. kiln train hf import-peft verifies the completed local bundle before connecting, streams its deterministic corpus-free envelope through bounded memory, and accepts success only when HTTP 201, JSON, strong ETag, import digest, PEFT content revision, installed byte count, six-file count, task, and source identities match values derived locally; it retains the source bundle on every outcome. The raw PEFT import API accepts only the derived ten-file envelope, caps compressed/expanded/metadata resources, compares the complete current resident identity, validates every LoRA A/B shape, shares the adapter mutation barrier and strict disk quota, and stores a self-verifying kiln.hf-trl-import.v1 receipt before kernel-enforced no-replace publication. The versioned identity model, 256 MiB inline-body ceiling, 256-export registry cap, 2 GiB import-body ceiling, and lifecycle are documented in HF/TRL Interoperability.
config.optimizer is tagged by kind; omission selects Muon, and
{"kind":"adam_w"} or {"kind":"muon"} selects that
optimizer's defaults. Expanded forms may set
beta1, beta2, eps, and
weight_decay for AdamW or momentum,
nesterov, ns_iters, and weight_decay
for Muon. SGD accepts only {"kind":"sgd"}, and unknown fields
are rejected. AdamW betas and Muon momentum must be finite in
[0,1); epsilon must be finite and positive; both weight-decay values
must be finite and non-negative; and Muon iterations must be in 1..=20.
An explicit learning rate must remain finite and positive after F32 conversion.
Omitted SFT rates resolve to 1e-3 for Muon and 1e-4 for
AdamW/SGD; GRPO/OPD resolve 2e-3 and 1e-5, respectively.
The cheap per-workload gate and resident optimizer tuple are checked before checkpoint or
corpus materialization, at dequeue before memory reservation, and before device residency.
This applies to direct training, the intent front door, every recipe step, judge/self-improve,
DistillRefresh, and OPD-backed distillation. Cheap teacher-alias validation and metadata pinning
may occur first; checkpoint loading, remote/local teacher materialization, corpus scanning,
memory preflight, and GPU reservation occur only after the static workload check. Invalid optimizer kind, rank, or
hyperparameters return structured training_invalid_request; unsupported base dtype,
backend/device identity, Marlin-packed weights, serving-profile ownership, or authoritative
tape/loss substrate return training_backend_unsupported. Neither changes route
mid-run. A recipe descriptor is a static preview rather than a memory reservation; run
submission preflights every step again before preparing any of them.
{
"recipes": [{
"name": "frontier-pump",
"description": "Distill a frontier teacher into a local adapter",
"num_steps": 1,
"admission": {
"supported": false,
"unavailable_reason": "step 1 (opd) is unavailable: resident backend tuple does not support OPD training"
}
}]
}
description may be null. unavailable_reason is null exactly when
the descriptor is supported; a false descriptor reports the first failing step. The
descriptor is advisory process-lifetime admission, so clients must still handle a run-time
rejection caused by changed lock health or live memory.
Any descriptor containing a DistillRefresh step is currently unsupported with the stable
two-phase planning reason above.
Every SFT source uses one row-admission contract. config.invalid_row_policy is fail by default; explicit skip queues only valid rows and records ordered kept/rejected SHA-256 identities under train_receipt.json → data.sft_ingestion. Server-local JSONL is revalidated against its submit-time manifest before GPU ownership. See SFT Ingestion and Row Identity.
New exact checkpoints, train_receipt.json, and adapter_manifest.json persist the full validated base-weight shard manifest and kiln.execution-provenance.v1 process/runtime envelope. Exact resume first passes the current workload and resident tuple gates, then compares shard bytes and the canonical execution digest before GPU ownership. It separately validates the backend/device, optimizer kind and state, rank, concrete parameter/activation/gradient dtypes, immutable rounding mode, and checkpoint/tape/loss routes. Server training records only {"mode":"round_to_nearest"}. A legacy checkpoint recording stochastic rounding fails closed on precision mismatch before GPU ownership; Kiln does not discard its seed or continue under a different update rule. A changed native identity, newly Marlin-packed weight, unavailable authoritative route, aggregate-only checkpoint, or partial-runtime checkpoint is likewise not exact-resumable.
Queued resume admission fully validates the checkpoint manifest plus every declared artifact size and SHA-256, then retains only the checkpoint ID, a digest of that validated manifest (whose entries cover the artifact hashes), and the effective seed. Before memory reservation at dequeue, the worker fully reloads the checkpoint and requires both the recomputed identity and effective seed to match. This is revalidation, not a filesystem snapshot: the queue does not copy or pin checkpoint files, and it cannot eliminate a mutation race after the reload. Keep an exact checkpoint directory immutable for its entire use.
Native SFT checkpoint-boundary replay is also startup-authoritative. [training].recompute_checkpoint_boundaries defaults to auto, whose inclusive sequence threshold defaults to 8,192 tokens. checkpoint_boundary_anchor_stride defaults to auto and derives one positive stride from the admitted shape and the default 6 GiB checkpoint_boundary_cache_gb target; explicit positive strides bypass only that shape calculation. Canonical environment names derive mechanically as KILN_TRAINING_<FIELD>; the four historical unsectioned names are deprecated, parsed strictly, and must agree with a present canonical value. The same pure policy functions drive admission and execution, and no runtime path re-reads those names. Exact SFT checkpoints additionally bind the admitted backend loss route as sft_loss_route under kiln.training-checkpoint-planning.v4; prior SFT v3 checkpoints fail closed as planning drift. GRPO and OPD continue to retain every segment boundary and remain under kiln.training-checkpoint-planning.v3. A v2 checkpoint or any applicable policy change is planning drift and cannot resume exactly; the outer checkpoint envelope remains schema v1. Restart the server to change startup policy. See the complete configuration reference and Native Training Checkpoints.
Cancel or resume training
curl -fsS http://localhost:8420/v1/train/queue | python3 -m json.tool
JOB_ID=<job-id>
curl -fsS -X DELETE http://localhost:8420/v1/train/queue/$JOB_ID | python3 -m json.tool
DELETE removes queued jobs or requests cooperative cancellation of a running job. Terminal jobs are removed through /v1/train/jobs/{job_id}.
Checkpoint and publication examples
SFT, GRPO, and OPD resume requests, revision conflicts, and GRPO receipts
# Start exact SFT checkpoints every 25 committed steps
curl -fsS http://localhost:8420/v1/train/sft \
-H 'content-type: application/json' \
-d '{"dataset_path":"/data/corrections.jsonl","config":{"training_profile":"native_online_lora_v1","output_name":"support-bot","epochs":3,"checkpoint_interval":25}}'
# Continue with identical data/config and the basename reported by job detail
curl -fsS http://localhost:8420/v1/train/sft \
-H 'content-type: application/json' \
-d '{"dataset_path":"/data/corrections.jsonl","config":{"training_profile":"native_online_lora_v1","output_name":"support-bot","epochs":3,"checkpoint_interval":25,"resume_checkpoint":"support-bot-checkpoint-step-00000025.kiln-checkpoint"}}'
# The same exact contract applies to streamed GRPO optimizer groups
curl -fsS http://localhost:8420/v1/train/grpo \
-H 'content-type: application/json' \
-d '{"dataset_path":"/data/scored-groups.jsonl","config":{"output_name":"reward-bot","checkpoint_interval":25}}'
curl -fsS http://localhost:8420/v1/train/grpo \
-H 'content-type: application/json' \
-d '{"dataset_path":"/data/scored-groups.jsonl","config":{"output_name":"reward-bot","checkpoint_interval":25,"resume_checkpoint":"reward-bot-checkpoint-step-00000025.kiln-checkpoint"}}'
# OPD defaults to an exact checkpoint every 25 committed optimizer steps
curl -fsS http://localhost:8420/v1/train/opd \
-H 'content-type: application/json' \
-d '{"prompts":[{"messages":[{"role":"user","content":"Explain why the sky is blue."}]}],"teacher":"qwen35@vllm","config":{"output_name":"distilled-bot","checkpoint_interval":25}}'
# Resume with identical prompts/config and the exact registered teacher revision
curl -fsS http://localhost:8420/v1/train/opd \
-H 'content-type: application/json' \
-d '{"prompts":[{"messages":[{"role":"user","content":"Explain why the sky is blue."}]}],"teacher":"qwen35@vllm","config":{"output_name":"distilled-bot","checkpoint_interval":25,"resume_checkpoint":"distilled-bot-checkpoint-step-00000025.kiln-checkpoint"}}'
SFT, GRPO, and OPD checkpoints are immutable .kiln-checkpoint directories published directly beneath the adapter registry while training runs. Cancellation checkpoints at the next committed SFT step, GRPO group, or settled OPD candidate boundary. Resume requires the same output name, data bytes and route, effective configuration, precision, model/base weights, tokenizer, and backend; OPD additionally binds the exact teacher content revision and keeps its optimizer-step and candidate cursors separate. The server validates the complete adapter, optimizer, reference/EMA where applicable, loop-state artifact set, and checksums before continuation. Job detail reports training_kind, data_source_kind, the next epoch/group/candidate cursor, validated effective configuration and data identity, plus OPD teacher identity. PEFT snapshots remain serving-only. See Native Training Checkpoints for the exact contract.
Training writes every artifact beneath a hidden staging root and captures the target’s starting content revision. Publication compares that revision again: if another operation won, the job fails with adapter_revision_conflict and preserves the newer adapter. An ungated same-name adapter that is already loaded is reloaded atomically with the directory replacement, even when auto_load=false. A gated post_eval.min_accuracy rewrite of a physically loaded same-name target is rejected before GPU work; unload it or choose a versioned config.output_name.
A GRPO run that reaches the training loop records grpo.policy_audit in the adapter receipt returned by GET /v1/adapters/{name}/receipt. The versioned kiln.grpo-policy-audit.v1 object reports behavior-policy importance ratios, independently referenced K1/K3 metrics, entropy-mask coverage, clipping tails, and content-addressed rollout-source identities. No-correction runs report unit ratios; they never substitute the KL reference for missing behavior probabilities. Token PPO and sequence GSPO use clip_epsilon/clip_eps_high; CISPO uses the separate absolute upper-only cispo_max_weight cap and has no lower clip count. See the GRPO audit guide for denominator and count semantics.
OpenEnv RL
Collect, train, evaluate, replay, and recover agent runs
OpenEnv is a native persisted training lifecycle, not an environment-specific adapter.
Kiln discovers any compatible environment from its URL, preserves its action and observation
schemas, drives the stateful WebSocket episode protocol, collects scored GRPO groups, submits
training, and can compare the base and candidate policies on held-out environment returns.
Public loopback environments need no credentials; protected origins use server-owned opaque
credential IDs aligned with their URLs so secrets never enter requests, run records, or artifacts.
Valid runs are accepted into a bounded FIFO when every complete-workflow slot is occupied;
v5 status reports one-based queue position, admission timestamp, and wait duration. A restart
resumes only never-admitted FIFO entries; interrupted executors fail explicitly. Optional
bounded idempotency_key values make concurrent and post-restart submission retries
atomic while the original run is retained. Every training entrance materializes the exact effective
GRPO config and preflights its behavior adapter, static post_eval suite,
backend/workload, optimizer, and rank before persistence or environment discovery. Direct clients
receive that config from a side-effect-free preflight and must submit it unchanged after collection.
Accepted train status atomically retains kiln.openenv-training-contract.v1;
restart and execution use it without recomputing defaults, and summary v5 embeds the same
settings before artifact publication.
After the final episode, status enters revalidating while Kiln re-reads each
endpoint’s complete stable discovery identity. The canonical discovery digest binds
every raw metadata, schema, inventory, and OpenAPI field before typed projection. A metadata,
advertised-name, OpenAPI, authentication, URL, digest, or schema mismatch fails as
environment_identity_changed at identity_verification and publishes
no mixed-identity artifacts.
Failed workflows retain a bounded kiln.openenv-run-failure.v1
diagnosis with a closed stage and code, retryability, next-step hint, and exact
OpenEnv protocol code or HTTP status when the peer supplied one. The legacy
error string remains for compatibility; automation should consume
failure. Metrics expose only the fixed stage × retryability matrix.
Rejections therefore spend no episodes or artifacts. Training → OpenEnv exposes that suite as Prove it after
training; collect-only requests reject every training-only field.
POST /v1/openenv/inspect — discover and canonically content-address complete protocol metadata, schema, inventory, and OpenAPI JSON before typed projection, then compile the self-contained action schema without resolving external references.
POST /v1/openenv/tasks — list the environment’s reset-task catalog through the same origin and credential policy.
POST /v1/openenv/training/preflight — validate direct OpenEnv training before collection and return its exact effective GRPO config plus a non-reserving queue/tracked-capacity snapshot. Final submission rechecks live queue and memory.
POST /v1/openenv/runs — preflight and accept a collect-only or collect-and-train run into bounded FIFO execution. A new run returns 202; the same retained idempotency key and normalized request return the original status with 200; changed reuse returns 409.
GET /v1/openenv/runs — list retained runs with progress, terminal evidence, gates, and published artifact manifests.
GET /v1/openenv/runs/{run_id} — retrieve one durable run snapshot across collection, identity revalidation, training, paired evaluation, completion, failure, or cancellation.
DELETE /v1/openenv/runs/{run_id} — immediately cancel queued work or request cooperative cancellation from the active collector, trainer, or evaluator while retaining completed evidence.
GET /v1/openenv/runs/{run_id}/artifacts/{kind} — download a published training or paired held-out evaluation artifact by the exact link returned in artifacts.
curl -fsS http://localhost:8420/v1/openenv/runs \
-H 'content-type: application/json' \
-d '{"kind":"train","idempotency_key":"experiment:math:17",
"environment_urls":["http://127.0.0.1:8990"],
"adapter":"base","output_adapter":"math-agent",
"groups":8,"group_size":4,"max_steps":8,
"post_eval":{"suite":"qwen3.5-agentic-core",
"data_scope":"held-out","include_baseline":true},
"environment_eval":{"groups":20,"group_size":1}}'
curl -fsS http://localhost:8420/v1/openenv/runs | python3 -m json.tool
kiln openenv start --request openenv-run.json \
--idempotency-key experiment:math:17 --follow
kiln openenv artifact <run-id> environment_eval_receipt --output receipt.json
The CLI submits that same JSON object and independently materializes returned artifacts. Follow
artifact links from the returned manifest; do not construct filenames. Training publishes
dataset, replay, and summary evidence, while paired evaluation can additionally publish baseline
and candidate datasets, replays, summaries, and its receipt. Only manifest-declared artifacts are
downloadable. Every download rechecks the exact byte count and SHA-256 on the same opened file
descriptor before streaming, and returns exact Content-Length, a strong digest
ETag, Cache-Control: private, no-store, and
X-Content-Type-Options: nosniff. Drift returns HTTP 409 with
openenv_artifact_integrity_failed and no artifact bytes. The CLI additionally stages
beside the destination, rehashes the response, and publishes atomically only after all checks pass.
For request schemas, reset-task alignment, math and arcade action examples, lifecycle semantics,
replay verification, and recovery, see the OpenEnv guide,
replay reference,
and generated HTTP contract.
Distillation
Verified remote teachers and identity-bound cache
Remote teachers use vLLM numeric-ID prompt_logprobs and must be launched with
scripts/vllm_teacher.py.
Registration probes K=1 and the advertised maximum K, verifies the complete tokenizer ID mapping
against the student, and persists a canonical identity for exact base and optional static-adapter
content, runtime, protocol, and scoring bounds. Kiln repeats that handshake at every job start before
GPU ownership or accepting a cache hit. Stock vLLM fingerprints and caller-supplied capability fields
are rejected.
GET /v1/teachers — list status, usability, capabilities, full identity revision, and canonical off-policy manifest.
POST /v1/teachers — register and operationally verify a local, fixture, or explicit vLLM teacher.
DELETE /v1/teachers/{alias} — explicitly remove an immutable alias.
GET /v1/cache/stats — validate and summarize canonical cache-v3 entries by teacher revision. Expensive cache operations are serialized and run off the async executor.
GET /v1/cache/export — stream a deterministic validated v3 export, bounded to one million scanned files and 16 GiB of source entries. A concurrent scan returns cache_operation_busy; archive import is intentionally unavailable.
curl -fsS -X POST http://localhost:8420/v1/teachers \
-H 'content-type: application/json' \
-d '{
"alias": "qwen35@vllm",
"kind": "remote",
"provider": "vllm",
"model_id": "qwen35-teacher",
"url": "http://127.0.0.1:8000"
}'
Plain HTTP and credential-free registration are loopback-only. Off-host endpoints require HTTPS and
a server-owned credential_id configured under [teachers.credentials]; API
callers never submit an environment-variable name or secret. See the
immutable teacher guide.
OPD checkpoint status separates teacher_identity_revision, which is comparable with GET /v1/teachers, from teacher_content_revision, which binds the exact live source, materialized logit rows, or composite algorithm. Re-registering the same alias with different model, tokenizer, adapter, runtime, protocol, or scoring bounds does not authorize resume. The dashboard prepares an OPD resume only when the exact usable identity revision is registered, deliberately leaves prompts blank for the operator to reinsert, and lets server admission reconstruct and verify the content revision.
Evals · datasets · judgments
Run eval suites, synthesize datasets, train a local judge
Run a registered or inline suite against one target, compare targets
on shared sampling inputs, or bind post-training activation to held-out
evidence. The default stable profile supports saved-adapter
eval; maintenance rejects eval admission. See the task-oriented
Evals guide and generated Eval guide
for complete request shapes, scorer behavior, and kiln-eval commands.
GET /v1/eval/suites — list registered eval suites.
POST /v1/eval/suites — create a suite; add ?force=true to replace an existing name deliberately.
GET /v1/eval/suites/{name} — fetch one suite as JSON.
DELETE /v1/eval/suites/{name} — delete a suite.
POST /v1/eval/run — queue a registered or inline suite against the base model or one resolvable adapter; an optional top-level seed preserves the suite's other generation settings.
POST /v1/eval/compare — run one suite across multiple adapters with one shared effective seed.
GET /v1/eval/jobs — list eval jobs, headline accuracies, exact effective seeds, and admission-time base-weight and execution identities.
GET /v1/eval/jobs/{id} — fetch one eval job with its full base-weight shard manifest, validated execution-provenance record, decimal-string job/per-completion seeds, and outcomes.
DELETE /v1/eval/jobs/{id} — cancel a queued/running eval (partial outcomes kept), or delete a terminal job from tracking.
POST /v1/eval/jobs/{id}/rerun — re-run only failures using the original effective seed unless explicitly replaced.
POST /v1/eval/jobs/{id}/replay — queue a strict byte replay of a completed run; optional run_index selects a compare arm, while admission requires exact replay, execution, base-weight, candidate, and judge identities.
POST /v1/eval/datasets/upload — multipart upload of an SFT or GRPO JSONL file.
POST /v1/eval/datasets/{name}/synthesize — decompose a dataset into a graded suite via final_assistant, first_assistant_turn, every_assistant_turn, or tool_call_predict.
GET /v1/judgments — list judgment datasets.
POST /v1/judgments — create a judgment dataset for A/B/Tie/Skip preferences.
POST /v1/judgments/{name}/rows — append one A/B/Tie/Skip preference.
POST /v1/judgments/{name}/compile — compile judgments into an SFT dataset for a local judge LoRA.
POST /v1/judgments/{name}/validate — score a judge LoRA against a held-out judgment slice.
An SFT or GRPO request can include post_eval. Pair
config.auto_load: true with a versioned held-out suite,
data_scope: "held-out", and
min_accuracy when the exact trained revision must pass the
fixed evidence gate before activation. Diagnostic post-eval runs do not
imply promotion. See post-training eval and promotion.
Eval admission snapshots the resident shard manifest before queue publication. Job archives and raw JSON retain the complete list; kiln-eval and the dashboard show a compact aggregate/count/byte summary, and downloaded outcome JSONL includes the full manifest on every standalone row. Corrupt archived manifests fail validation on restart; missing manifests identify only legacy or synthetic jobs.
New completed production runs also retain a kiln.eval-replay.v1 record over exact suite/generation/seed/budget, model/adapter/judge/scorer, execution/base-weight, and raw/normalized completion identities. A replay job publishes its expected source hashes at queue admission and a terminal matched, mismatch, or error verdict. See Strict replay; the generated HTTP contract is the normative field and status reference.
Security note
Treat every training endpoint as privileged
Kiln’s training endpoints are privileged: do not expose /v1/train/sft or
/v1/train/grpo to untrusted inputs. A job can create or
replace an adapter artifact and, when explicitly or implicitly
auto-loaded, change the model serving subsequent requests. Kiln
validates request structure, not whether an example is semantically
safe or desirable. Review training data and activation policy as
carefully as code, and start with the Security guide
and Troubleshooting guide when hardening a deployment.
Response bodies
Response shapes
Inference responses follow OpenAI-compatible choices payloads. Training submissions return
queued job metadata with a job_id, then /v1/train/status and the per-job
/v1/train/status/{job_id} lookup report state, loss, adapter name, and any failure message.
Use the generated Inference API Schema for every inference
request, response, nested object, optional field, and stream event rather than treating these
workflow summaries or examples as a field contract.
For thinking responses, choices[].message.reasoning_content contains text before the
natural or forced close, while content contains the final answer. Streaming uses the
corresponding delta.reasoning_content and delta.content channels; raw thinking
tags are not emitted on the separated path.
Failure handling
Branch on HTTP status and error.code
Non-success responses use one structured body with
error.code, error.message, and
error.hint. Treat the status and code as the stable
programmatic signal; retain the message for diagnosis and present the
hint to the operator. A retryable HTTP 503 may also carry
Retry-After. Do not infer retry safety from prose alone.
{
"error": {
"code": "serving_profile_conflict",
"message": "Serving profile `maintenance` prohibits inference while drained work is active",
"hint": "Restart with the default stable profile for inference, evaluation, and coordinated live adapter transitions."
}
}
During diagnosis, omit curl’s --fail flag so the JSON
body remains visible:
curl -sS -w '\nHTTP %{http_code}\n' \
http://localhost:8420/v1/adapters/load \
-H "Content-Type: application/json" \
-d '{"name":"support-bot"}'
The generated HTTP contract lists the
status codes and response schemas for every operation.