A kovara.yaml describes one AI workload so that someone else can run it again: which model (and which commit of it), which runtime engine and version, which accelerator and how many, which precision and parallelism, and the default benchmark to measure it with. Each published manifest becomes an immutable version of a compute repository, identified by the SHA-256 of its canonical JSON.
The same rules are implemented in three places that must agree:
| Where | Implementation |
|---|---|
| Kovara web app and API | lib/workload-manifest/ (TypeScript) |
kovara CLI | packages/kovara-cli/src/kovara/manifest/ (Python) |
| Database | kc_manifest_check and kc_canonical_json (structural minimum and hashing) |
Shared fixtures (packages/kovara-cli/tests/fixtures/) are checked against all of them.
Kovara never executes a manifest. It is a description, not a script.
Example
schema_version: "1"
kind: inference
model:
source: huggingface
id: Qwen/Qwen3-32B
revision: <40-character commit SHA from the model page>
runtime:
engine: vllm
version: "0.11.0"
image: vllm/vllm-openai@sha256:<64 hex digits>
hardware:
accelerator: nvidia-h200-sxm
gpu_count: 2
inference:
precision: bf16
quantization: none
tensor_parallel_size: 2
max_model_len: 8192
benchmark:
profile: interactive-chat
concurrency: [1, 8, 32]
input_tokens: 1024
output_tokens: 256
notes: Two-way tensor parallel on one node.More in packages/kovara-cli/examples/.
Fields
Required fields are marked required. Everything else may be left out; do not write a key with an empty value (revision: with nothing after it is null, which is refused: remove the line instead).
Top level
| Field | Type | Rules |
|---|---|---|
schema_version | text | required. Exactly "1" (quoted: an unquoted 1 is a number and is refused). |
kind | text | required. One of inference, batch-inference, training, fine-tuning, embeddings, image-generation, video-generation, other. |
model | map | required. See below. |
runtime | map | required. See below. |
hardware | map | required. See below. |
inference | map | For inference, batch-inference, embeddings, image-generation, video-generation and other. |
training | map | For training, fine-tuning and other. |
benchmark | map | The default benchmark kovara benchmark runs. |
notes | text | Free text, at most 500 characters. |
x-… | any | Your own fields (see Extensions). |
Any other top-level key is an error (typos such as hardwre: are caught rather than ignored).
model
| Field | Type | Rules |
|---|---|---|
source | text | required. huggingface or other. |
id | text | required, 1–200 characters. For huggingface: a repository id, name or owner/name (letters, digits, ., _, -), e.g. meta-llama/Llama-3.1-8B-Instruct. |
revision | text | Recommended. 1–100 characters of letters, digits, ., /, -, _. A 40-character commit SHA makes the reference reproducible; a branch or tag is accepted with a warning (it can move). Missing: warning. |
runtime
| Field | Type | Rules |
|---|---|---|
engine | text | required. A lowercase id: ^[a-z0-9][a-z0-9._-]{0,39}$. Known engines: vllm, sglang, tensorrt-llm, llama.cpp, tgi, ollama, other; any other id in that format is accepted. |
version | text | 1–60 characters starting with a letter or digit, then letters, digits, ., _, +, -. Quote it: version: "0.11.0" (an unquoted 0.11 would be read as a number and refused). |
image | text | Container image reference (letters, digits, ., _, /, :, @, -). Pin it by digest (…@sha256:<64 hex>); a tag only is accepted with a warning. |
hardware
| Field | Type | Rules |
|---|---|---|
accelerator | text | required. A Kovara GPU slug (gpu_models.slug), e.g. nvidia-h100-sxm, nvidia-h200-sxm, amd-instinct-mi300x-192gb. Validators check the format (^[a-z0-9]+(-[a-z0-9]+)*$); publishing checks that the GPU exists in Kovara's catalogue. |
gpu_count | whole number | required. 1 to 4096. |
inference
| Field | Type | Rules |
|---|---|---|
precision | text | fp32, bf16, fp16, fp8, int8, int4 or other. |
quantization | text | Lowercase id (none, awq, gptq, fp8, …), same format as engine. |
tensor_parallel_size | whole number | 1 to 4096, and not more than hardware.gpu_count. |
max_model_len | whole number | 1 to 10,000,000. |
training
| Field | Type | Rules |
|---|---|---|
precision | text | As for inference.precision. |
method | text | full, lora, qlora or other. |
global_batch_size | whole number | 1 to 10,000,000. |
sequence_length | whole number | 1 to 10,000,000. |
benchmark
The defaults kovara benchmark uses when the command line does not override them. See benchmarking.md.
| Field | Type | Rules |
|---|---|---|
profile | text | Lowercase id, ^[a-z0-9][a-z0-9-]{0,47}$. Named profiles: interactive-chat (1024 input / 256 output tokens), long-context (8192 / 256), long-generation (256 / 2048). Another id is accepted; set input_tokens and output_tokens with it (warning otherwise). |
concurrency | list of whole numbers | 1 to 16 distinct levels, each 1 to 4096, e.g. [1, 8, 32]. |
input_tokens | whole number | Prompt length, 1 to 2,000,000. |
output_tokens | whole number | max_tokens per request, 1 to 200,000. |
requests | whole number | Measured requests per concurrency level, 1 to 1,000,000. |
warmup_requests | whole number | Unmeasured requests before the first level, 0 to 100,000. |
seed | whole number | Seed of the synthetic prompts, 0 to 2147483647. |
requests, warmup_requests and seed are additions to the field list in ARCHITECTURE.md §4 so that a manifest can pin its whole default benchmark.
Rules that apply everywhere
- Values: text, numbers,
true/false,null, maps and lists. Lists hold only plain values (no maps or lists inside lists). At most 8 levels of nesting, 64 keys per map, 64 items per list. - Keys: start with a letter or
_, then letters, digits,_,.,-; at most 64 characters. - Text: at most 500 characters (Unicode code points); valid Unicode; no NUL characters.
- Numbers: finite, magnitude below 10^15, at most 6 digits after the decimal point. Numbers are IEEE doubles in every implementation; within these limits every language prints them the same way.
- Size: the canonical JSON of the manifest is at most 64 KB.
No secrets. Refused:
- keys that look like credentials — containing
token,secret,passw,api_key/apikey,access_key,private_key,credentialorbearer(the schema's owninput_tokens/output_tokensare exempt); - values that look like credentials — Hugging Face tokens (
hf_…),sk-…keys, Kovara keys (kvk_…,rbk_…), GitHub tokens (ghp_…,github_pat_…), AWS access key ids (AKIA…), Google API keys (AIza…), Slack tokens (xox…-), NVIDIA API keys (nvapi-…), PEM private keys, JSON Web Tokens,Bearer …values and URLs with a password (https://user:pass@…).
If a validator reports one of these and it was a real credential, rotate it.
- keys that look like credentials — containing
Extensions
Keys starting with x- are allowed at the top level and inside every section (x-team, runtime.x-launch-flags). They follow all the rules above, are kept in the published version and are part of its content hash. Kovara does not interpret them.
YAML reading
The CLI reads YAML with the YAML 1.2 core schema for unquoted values: true/false are booleans, null/~/empty are null, 123, 0o17, 0x1f are integers, 1.5, 1e3, .inf are floats, and everything else is text — so yes, no, on, off and dates such as 2024-01-01 stay text. Duplicate keys, aliases (*name) and multiple documents are refused as malformed YAML. A file that starts with { is read as JSON.
Quote anything that must be text but looks like a number: schema_version: "1", version: "0.11.0" (unquoted 0.11.0 is text, but 0.11 would be a number), a commit SHA made only of digits.
The web app does not parse YAML: it accepts JSON (kovara validate --json prints it) or builds manifests from forms.
YAML writing
kovara pull, kovara init and the web download write the same deterministic text (to_yaml in the CLI, toYaml in lib/workload-manifest/yaml.ts, checked byte for byte by scripts/check-workload-manifest.cjs):
- fields in the order of this specification, then other keys (extensions) by code point;
- block maps with two-space indentation; lists in flow style (
[1, 8, 32]);{}and[]for empty containers; - numbers in plain decimal form;
- text unquoted only when it starts with a letter or
_, contains only letters, digits and_ . / + -, and is not one ofy,yes,n,no,true,false,on,off,null(any case); otherwise double-quoted with escapes (\",\\,\t,\n,\r,\b,\f,\xNNfor other control characters,,,).
Both YAML 1.1 parsers (PyYAML safe_load) and YAML 1.2 parsers read this output back to the same values.
Canonical JSON, content hash, revision
The content hash of a manifest is the SHA-256 (lowercase hex) of its canonical JSON, the UTF-8 text produced by:
- objects: keys sorted by their UTF-8 bytes (which is Unicode code point order — not JavaScript's default UTF-16 order), no whitespace,
"key":value; - strings: escaped as JavaScript's
JSON.stringifydoes —\",\\,\b,\f,\n,\r,\t, other characters below U+0020 as\u00xx(lowercase hex); everything else, including/, U+007F and non-ASCII, raw; - numbers: plain decimal, no exponent, no trailing fractional zeros:
2.0→2,1.50→1.5,1e3→1000,-0.0→0,0.000001→0.000001(identical to Postgrestrim_scale(numeric)::text); true,false,null.
The revision is r + the first 12 hex digits of the content hash, e.g. r0b7cbeafb9f8. Comments, key order, quoting style and indentation in the YAML do not change the hash; any change to a value does.
Validation messages
Validators return every problem at once as {path, message}, where path is dotted (hardware.gpu_count, benchmark.concurrency[1]) and empty for the whole document, for example:
schema_version: schema_version must be the text "1", not the number 1. In YAML, quote it: schema_version: "1"
hardware.gpu_count: hardware.gpu_count must be a whole number, not the number 1.5
x-hf_token: This key looks like it holds a credential (token, secret, password or API key). …Warnings (the manifest is still valid): model.revision missing or not a commit SHA, runtime.image not pinned by digest, benchmark.profile unnamed without token lengths.