Run Promote Desk from your own code
Both metered lanes are available over HTTP, and each returns one JSON object. The division of labour: the page reads the evidence, computes every drift delta with its 95% half-width and budget verdict, the arena win rate with its Wilson interval, every stage's status, the verdict in gate order with one remediation, and the export format the map gives - all free, in promokit.js. The run writes the report or the plan; the page then holds the reply to its own gate (promoreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"promote-desk"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, run, evals, facts, deploy, gate) is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
One app, one model, two lanes - task picks the lane. An unknown or missing task is answered as gate. The export lane is meant to run only after a PROMOTE; the page will not send it otherwise.
| task | Stage | Returns |
|---|---|---|
gate | decide | The promotion report: verdict (PROMOTE, REJECT or RERUN), uncertain, four stages (stage, status, evidence), evidence, remediation (one fix, empty for PROMOTE) and caveats. |
export | ship | The export plan: format, packaging, runtime, why, steps (step, command), smoke_test, watch_for (signature, cause) and note. |
The input
| Field | Required | What |
|---|---|---|
task | yes | gate or export. If it is missing or neither, do gate and set "task": "gate". |
run | yes | the checkpoint and base model names, the target-task metric, the deployment (production or local), the forgetting-ladder rungs already tried, and whether replay rows reuse the drift suite's phrasing. |
evals | yes | the drift table as the page read it: benchmark, baseline, checkpoint, delta, n, half-width, seed rerun, budget verdict. |
facts | yes | every stage's status line, the expected verdict and what decided it, the page's remediation, and every finding tagged [fix], [check] or [info]. |
deploy | yes | for export: hardware, workload, packaging, model size, the page's format and packaging pick with the allowed alternatives, the runtime, the smoke-test gate and approximate sizes. none for gate. |
gate | yes | for export, the gate's result; none for gate. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
- Four stages, each gating the next: 1 data quality (dedup, zero goldens leaked into training, label noise), 2 held-out plus frozen capability-drift suite against the baseline, 3 paired arena checkpoint vs base on the same prompts, 4 canary (5-10% stratified rollout with auto-rollback) for production only - a local-only deployment stops at stage 3, and that is the correct stopping point.
- Drift budget, in percentage points (absolute accuracy difference, never relative percent): <=1 pt noise; 2-5 pt rerun with seed variation before deciding; >5 pt HARD FAIL with no exception for task gains. The worst benchmark governs. RERUN is not a verdict: a rerun must land back at <=1 pt to pass, anything else resolves to HARD FAIL.
- Report the 95% half-width with every drift verdict. When a margin is smaller than its half-width, the verdict is REJECT (uncertain), resolved with a larger suite or a same-config seed repeat. n=200 is a pragmatic floor (about +/-6 pts at 70%); the strict size for a 2.5 pt half-width is about 1,300.
- Arena: ties count half a win; the checkpoint must reach 50% + margin with the 95% interval excluding 50%. A holdout win that loses the arena does not ship - a real signal, not a discrepancy to explain away.
- Catastrophic forgetting: about 43% knowledge loss unmanaged, about 10% with basic management, about 3% with replay + EWC; a 10-30% general-data replay mix is the standard mitigation. Escalation ladder, in order: 1 swap the replay-mix fraction (swap rows, do not add them - adding confounds fraction with optimizer steps), 2 lower the learning rate, 3 fewer epochs, 4 a smaller LoRA rank. Guidance from one before/after pair is a hypothesis.
- Replay rows that copy the drift suite's instruction phrasing make that score an upper bound.
- The verdict is PROMOTE or REJECT with the evidence that decided it and exactly one remediation for a REJECT - one, not a menu. REJECT is a result, not an error. No auto-retraining.
- Export starts only from PROMOTE. Format map: FP8 by default on Hopper-class and newer; AWQ INT4 for GPUs that predate FP8 (GPTQ is superseded); GGUF Q4_K_M built from an imatrix for edge/llama.cpp; NVFP4 for Blackwell at scale only and never on GB10 (about 32% slower than FP8 there); on GB10, FP8 via vLLM nightly or GGUF via llama.cpp. Long-context, code and math never go to INT4 - stay on FP8 or W8A8, and validate with the task evals, not broad benchmarks. Merged vs LoRA-only is a separate axis: merged is portable and larger; LoRA-only is small but must load the exact base revision.
- The smoke test is mandatory for every export: load the artifact in its real target runtime (vLLM for FP8/AWQ, llama.cpp for GGUF), run 3-5 golden prompts with greedy decoding (temperature 0) and a fixed seed persisted between the pre- and post-export runs, and compare: a lossless export must byte-match, a lossy one must agree on task-grader verdicts. Failure signatures: a chat-template mismatch shows as garbled or run-on output; a wrongly quantized lm_head shows as fluent but off-template or nonsensical output. Re-run on any quant-method or runtime version bump. GGUF needs llama.cpp built with its default configuration, the repo's gguf-py and sentencepiece installed, convert_hf_to_gguf.py to f16, llama-imatrix on a domain calibration corpus, then llama-quantize with --imatrix; smoke-test raw prompts with llama-completion, not llama-cli.
House rules
- Use only the input and the rules above. Never invent a score, an item count, a benchmark, a GPU, a library version or a date the input does not give.
- The page's numbers and statuses are exact - copy them; never recompute them differently. Every number in a sentence must appear in the input.
- Never use backticks, never refer to this input's fields by their key names ("the facts field", "see deploy") in a sentence, and never copy this prompt's narrative wording into a reply - technical terms with no other correct wording (greedy decoding, temperature 0, fixed seed, task-grader verdict agreement, imatrix) are fine, and so are ordinary words like run or gate.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
gate
verdict- copy the expected verdict from the facts:PROMOTE,REJECTorRERUN(RERUN only when stage 2 still waits on a seed rerun - the report cannot end yet).uncertain- true only when the expected verdict in the facts carries the word uncertain; otherwise false - if the numbers look tighter than that, say so in a caveat instead.stages- exactly four entries, stages 1 to 4 in order:stage(the number),status(exactly one ofPASS,FAIL,HARD FAIL,RERUN,UNCERTAIN,NOT RUN,NOT APPLICABLE- the token whose meaning matches the stage line, not its exact wording: "RERUN pending" is RERUN, "REJECT (uncertain)" is UNCERTAIN, "NOT RUN (production target)" is NOT RUN),evidence(one or two sentences, at most 45 words, with the numbers that matter).evidence- one paragraph, at most 70 words: for REJECT or RERUN, cite the specific stage and number that decided it; for PROMOTE, say every stage cleared and cite the tightest numbers.remediation- for REJECT or RERUN, exactly one fix, at most 35 words, following the page's remediation unless it contradicts the rules;""for PROMOTE.caveats- at most 4 short strings: sample-size limits, instruction-familiar scores, single-run lever attribution, anything the person should know before trusting the verdict.[]when none.
{
"task": "gate",
"verdict": "PROMOTE | REJECT | RERUN",
"uncertain": false,
"stages": [ { "stage": 1, "status": "PASS", "evidence": "one or two sentences" } ],
"evidence": "one paragraph",
"remediation": "one fix, or empty",
"caveats": [ "a caveat" ]
}
The page holds it to its own gate: the verdict and the uncertain flag must match, each stage status must be the one the page computed, a PROMOTE carries no remediation and a REJECT or RERUN exactly one, and every number in a sentence must be in the input.
export
format- the page's format pick, written as its key (fp8,w8a8,awq-int4,gguf-q4_k_m,gguf-q8_0,nvfp4orbf16-merged); another key only if it is one of the allowed alternatives and you say why.packaging-mergedorlora-only, following the page's packaging pick.runtime- the runtime the artifact is served and smoke-tested in.why- one or two sentences, at most 45 words: the hardware and workload rule that picks this format.steps- 3 to 6 entries in order, eachstep(one short sentence) andcommand(the command or code for it, with placeholders such as CHECKPOINT_DIR, MERGED_DIR, OUT_DIR - never invent paths or model names).smoke_test- 3 to 5 short strings: the smoke-test procedure for this format and runtime, ending with the gate (byte match or grader agreement).watch_for- 2 to 4 entries:signature(what the failure looks like) andcause.note- one sentence, at most 30 words: what to run first.
{
"task": "export",
"format": "fp8",
"packaging": "merged",
"runtime": "vLLM",
"why": "one or two sentences",
"steps": [ { "step": "one sentence", "command": "the command" } ],
"smoke_test": [ "a step" ],
"watch_for": [ { "signature": "what it looks like", "cause": "why" } ],
"note": "one sentence"
}
The page holds it to the format map: the format must be its pick or an allowed alternative, never INT4 for long-context, code or math, never NVFP4 on GB10; the runtime must fit the format; GGUF Q4_K_M must build an imatrix; the smoke test must pin greedy decoding with a fixed seed and use the right gate - byte match for a lossless export, task-grader agreement for a lossy one.
Worked examples - every lane, every sample
These are the page's four free samples. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped as the validating model wrote it; the last line is the page's own check of it. The export lane runs only for the two promoted samples.
A code model headed to production - gate
The gate says PROMOTE - every stage cleared.
Input:
{
"task": "gate",
"run": "Checkpoint: support-coder-r16-ckpt-2400; base model: Qwen2.5-Coder 7B Instruct\nTarget task: ticket-to-patch pass@1 41.0 -> 53.5 (+12.5 pts)\nDeployment: production (canary passed)\nForgetting-ladder rungs already tried: none\nReplay rows reuse the drift suite's instruction phrasing: no",
"evals": "benchmark | baseline | checkpoint | delta (pts) | n | 95% half-width (pts) | seed rerun | budget verdict\nmmlu-subset | 66.4 | 66.1 | -0.3 | 1400 | 2.5 | - | noise\ngsm8k-subset | 79.2 | 78.8 | -0.4 | 1319 | 2.2 | - | noise\nifeval | 72.0 | 72.5 | +0.5 | 541 | 3.8 | - | noise\nhumaneval-plus | 61.6 | 62.2 | +0.6 | 164 | 7.4 | - | noise\ndomain-adjacent | 58.0 | 57.4 | -0.6 | 1300 | 2.7 | - | noise",
"facts": "Stage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 37 rows removed; label noise pass\nStage 2 capability drift: PASS\nStage 3 paired arena (judge): PASS - 171 wins, 58 ties, 71 losses of 300; win rate 66.7% (ties count half), 95% Wilson CI 61.2-71.8%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: PASS\nExpected verdict: PROMOTE - every stage cleared\nFindings (3):\n[info] dedup (stage 1): 37 duplicate rows removed before training.\n[check] small_n (stage 2): humaneval-plus has n=164, under the 200-item pragmatic floor.\n[info] floor_n (stage 2): Suites at the 200-item floor resolve about +/-6 pts at 70%; the strict size for a 2.5 pt half-width is about 1,291 items.",
"deploy": "none",
"gate": "none"
}
Reply:
{
"task": "gate",
"verdict": "PROMOTE",
"uncertain": false,
"stages": [
{
"stage": 1,
"status": "PASS",
"evidence": "Zero overlapping golden IDs leaked into training, 37 duplicate rows were removed, and the label-noise check passed."
},
{
"stage": 2,
"status": "PASS",
"evidence": "All five benchmarks moved at most 0.6 pts, within the 1-point noise band; the largest was domain-adjacent at -0.6 pts (n=1300, +/-2.7 pts). humaneval-plus was +0.6 pts but at n=164, under the 200-item floor (+/-7.4 pts)."
},
{
"stage": 3,
"status": "PASS",
"evidence": "171 wins, 58 ties, 71 losses of 300 (ties count half) gave a 66.7% win rate; the 95% Wilson interval 61.2-71.8% clears the 55.0% threshold and excludes 50%."
},
{
"stage": 4,
"status": "PASS",
"evidence": "The production canary rollout passed."
}
],
"evidence": "All four gating stages passed: stage 1 found zero golden leakage, stage 2's worst drift was domain-adjacent at -0.6 pts, within the 1-point noise band, stage 3's arena win rate was 66.7% (95% CI 61.2-71.8%, excluding 50%), and stage 4's production canary passed, so the checkpoint promotes.",
"remediation": "",
"caveats": [
"humaneval-plus was evaluated at n=164, under the 200-item pragmatic floor (+/-7.4 pts)."
]
}
The page's check: kept - nothing to flag
A code model headed to production - export
The gate says PROMOTE - every stage cleared. The format map picks fp8.
Input:
{
"task": "export",
"run": "Checkpoint: support-coder-r16-ckpt-2400; base model: Qwen2.5-Coder 7B Instruct\nTarget task: ticket-to-patch pass@1 41.0 -> 53.5 (+12.5 pts)\nDeployment: production (canary passed)\nForgetting-ladder rungs already tried: none\nReplay rows reuse the drift suite's instruction phrasing: no",
"evals": "benchmark | baseline | checkpoint | delta (pts) | n | 95% half-width (pts) | seed rerun | budget verdict\nmmlu-subset | 66.4 | 66.1 | -0.3 | 1400 | 2.5 | - | noise\ngsm8k-subset | 79.2 | 78.8 | -0.4 | 1319 | 2.2 | - | noise\nifeval | 72.0 | 72.5 | +0.5 | 541 | 3.8 | - | noise\nhumaneval-plus | 61.6 | 62.2 | +0.6 | 164 | 7.4 | - | noise\ndomain-adjacent | 58.0 | 57.4 | -0.6 | 1300 | 2.7 | - | noise",
"facts": "Stage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 37 rows removed; label noise pass\nStage 2 capability drift: PASS\nStage 3 paired arena (judge): PASS - 171 wins, 58 ties, 71 losses of 300; win rate 66.7% (ties count half), 95% Wilson CI 61.2-71.8%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: PASS\nExpected verdict: PROMOTE - every stage cleared\nFindings (3):\n[info] dedup (stage 1): 37 duplicate rows removed before training.\n[check] small_n (stage 2): humaneval-plus has n=164, under the 200-item pragmatic floor.\n[info] floor_n (stage 2): Suites at the 200-item floor resolve about +/-6 pts at 70%; the strict size for a 2.5 pt half-width is about 1,291 items.",
"deploy": "Hardware: Hopper datacenter GPU (H100, H200)\nWorkload: Code\nPackaging asked: Decide for me\nModel size: 7.6B parameters\nPage's format pick: FP8 (W8A8-FP8) (fp8) - FP8 is the default on Hopper-class GPUs: near-bf16 quality at about half the memory.\nAlternatives allowed: W8A8 INT8 (w8a8)\nPackaging pick: merged - Merged folds the adapter into the base: a larger artifact, no base-model dependency at serve time.\nRuntime for the smoke test: vLLM\nSmoke-test gate: task-grader verdict agreement with the pre-export outputs (byte match is expected to fail on a lossy export)\nApproximate artifact sizes: FP8 (W8A8-FP8) ~7.7 GB; W8A8 INT8 ~7.7 GB; Merged bf16 safetensors ~15.2 GB",
"gate": "Verdict: PROMOTE - every stage cleared\nStage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 37 rows removed; label noise pass\nStage 2 capability drift: PASS\nStage 3 paired arena (judge): PASS - 171 wins, 58 ties, 71 losses of 300; win rate 66.7% (ties count half), 95% Wilson CI 61.2-71.8%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: PASS"
}
Reply:
{
"task": "export",
"format": "fp8",
"packaging": "merged",
"runtime": "vLLM",
"why": "FP8 is the default pick for Hopper-class GPUs, and the code workload must stay on FP8 or W8A8 rather than INT4, so FP8 fits both the hardware and the workload here.",
"steps": [
{
"step": "Merge the LoRA adapter into the base model to produce a standalone merged checkpoint.",
"command": "python merge_adapter.py --base_model BASE_MODEL_DIR --adapter CHECKPOINT_DIR --out MERGED_DIR"
},
{
"step": "Quantize the merged weights to FP8 for Hopper-class serving.",
"command": "python quantize_fp8.py --model MERGED_DIR --out OUT_DIR --dtype fp8"
},
{
"step": "Serve the FP8 checkpoint with vLLM.",
"command": "vllm serve OUT_DIR --quantization fp8"
},
{
"step": "Smoke-test the exported checkpoint by comparing its outputs with the pre-export outputs.",
"command": "python smoke_test.py --model OUT_DIR --prompts GOLDEN_PROMPTS_FILE --seed SEED --temperature 0"
}
],
"smoke_test": [
"Load the FP8 checkpoint in vLLM, the real target runtime.",
"Replay the same 3 to 5 reference prompts used before export with deterministic decoding at temperature 0, keeping the same fixed seed as before export.",
"Compare the outputs against the pre-export outputs.",
"Because FP8 is a lossy export, accept task-grader verdict agreement with the pre-export outputs rather than a byte match."
],
"watch_for": [
{
"signature": "Output comes out scrambled or does not break into separate turns the way it should.",
"cause": "Mismatch between the chat template used at export time and the one used to serve the model."
},
{
"signature": "Output reads fluently but ignores the expected response structure or makes no sense.",
"cause": "The lm_head layer was quantized incorrectly."
}
],
"note": "Merge the LoRA adapter into the base model first, before quantizing to FP8."
}
The page's check: kept - nothing to flag
A 3B recipe model for laptops - gate
The gate says PROMOTE - every stage cleared.
Input:
{
"task": "gate",
"run": "Checkpoint: recipes-lora-ckpt-900; base model: Llama 3.2 3B Instruct\nTarget task: recipe-format exact match 52.0 -> 81.0 (+29.0 pts)\nDeployment: local only\nForgetting-ladder rungs already tried: none\nReplay rows reuse the drift suite's instruction phrasing: no",
"evals": "benchmark | baseline | checkpoint | delta (pts) | n | 95% half-width (pts) | seed rerun | budget verdict\nmmlu-subset | 60.2 | 59.8 | -0.4 | 500 | 4.3 | - | noise\ngsm8k-subset | 72.1 | 68.9 | -3.2 | 500 | 4.0 | 71.5 (delta -0.6) | pass after rerun\nifeval | 68.4 | 69.0 | +0.6 | 541 | 3.9 | - | noise\ndomain-adjacent | 64.0 | 63.6 | -0.4 | 300 | 5.4 | - | noise",
"facts": "Stage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 4 rows removed; label noise pass\nStage 2 capability drift: PASS\nStage 3 paired arena (deterministic): PASS - 30 wins, 40 ties, 10 losses of 80; win rate 62.5% (ties count half), 95% Wilson CI 51.5-72.3%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: NOT APPLICABLE - local-only deployment stops at stage 3\nExpected verdict: PROMOTE - every stage cleared\nFindings (3):\n[info] dedup (stage 1): 4 duplicate rows removed before training.\n[info] floor_n (stage 2): Suites at the 200-item floor resolve about +/-6 pts at 70%; the strict size for a 2.5 pt half-width is about 1,291 items.\n[info] arena_det_small (stage 3): The deterministic arena covers 80 goldens - dogfood scale; read the CI width as evidence quality.",
"deploy": "none",
"gate": "none"
}
Reply:
{
"task": "gate",
"verdict": "PROMOTE",
"uncertain": false,
"stages": [
{
"stage": 1,
"status": "PASS",
"evidence": "Zero overlapping golden IDs leaked into training, 4 duplicate rows were removed, and the label-noise check passed."
},
{
"stage": 2,
"status": "PASS",
"evidence": "gsm8k-subset dropped 3.2 pts initially (+/-4.0 pts, n=500), in the 2-5 pt rerun band; the seed-variation rerun returned 71.5 (delta -0.6 pts), within the 1-point noise band, so it passes. The other three benchmarks moved at most 0.6 pts, also noise."
},
{
"stage": 3,
"status": "PASS",
"evidence": "30 wins, 40 ties, 10 losses of 80 (deterministic arena, ties count half) gave a 62.5% win rate; the 95% Wilson interval 51.5-72.3% clears the 55.0% threshold and excludes 50%, though 80 goldens is dogfood scale."
},
{
"stage": 4,
"status": "NOT APPLICABLE",
"evidence": "This is a local-only deployment, which correctly stops at stage 3; no canary is run."
}
],
"evidence": "All three applicable stages passed: stage 1 found zero golden leakage, stage 2's gsm8k-subset rerun landed at a -0.6-point delta after an initial 3.2-point drop, within the 1-point noise band, and stage 3's deterministic arena reached a 62.5% win rate (95% CI 51.5-72.3%, excluding 50%). Stage 4 does not apply to this local-only deployment, so promotion is correct.",
"remediation": "",
"caveats": [
"The deterministic arena covers only 80 goldens, dogfood scale; read the 51.5-72.3% CI width as evidence quality.",
"The gsm8k-subset pass relies on a single seed-variation rerun, not a repeated average."
]
}
The page's check: kept - nothing to flag
A 3B recipe model for laptops - export
The gate says PROMOTE - every stage cleared. The format map picks gguf-q4_k_m.
Input:
{
"task": "export",
"run": "Checkpoint: recipes-lora-ckpt-900; base model: Llama 3.2 3B Instruct\nTarget task: recipe-format exact match 52.0 -> 81.0 (+29.0 pts)\nDeployment: local only\nForgetting-ladder rungs already tried: none\nReplay rows reuse the drift suite's instruction phrasing: no",
"evals": "benchmark | baseline | checkpoint | delta (pts) | n | 95% half-width (pts) | seed rerun | budget verdict\nmmlu-subset | 60.2 | 59.8 | -0.4 | 500 | 4.3 | - | noise\ngsm8k-subset | 72.1 | 68.9 | -3.2 | 500 | 4.0 | 71.5 (delta -0.6) | pass after rerun\nifeval | 68.4 | 69.0 | +0.6 | 541 | 3.9 | - | noise\ndomain-adjacent | 64.0 | 63.6 | -0.4 | 300 | 5.4 | - | noise",
"facts": "Stage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 4 rows removed; label noise pass\nStage 2 capability drift: PASS\nStage 3 paired arena (deterministic): PASS - 30 wins, 40 ties, 10 losses of 80; win rate 62.5% (ties count half), 95% Wilson CI 51.5-72.3%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: NOT APPLICABLE - local-only deployment stops at stage 3\nExpected verdict: PROMOTE - every stage cleared\nFindings (3):\n[info] dedup (stage 1): 4 duplicate rows removed before training.\n[info] floor_n (stage 2): Suites at the 200-item floor resolve about +/-6 pts at 70%; the strict size for a 2.5 pt half-width is about 1,291 items.\n[info] arena_det_small (stage 3): The deterministic arena covers 80 goldens - dogfood scale; read the CI width as evidence quality.",
"deploy": "Hardware: Laptop, desktop or CPU - llama.cpp\nWorkload: Generic chat\nPackaging asked: Decide for me\nModel size: 3.2B parameters\nPage's format pick: GGUF Q4_K_M + imatrix (gguf-q4_k_m) - Edge/llama.cpp serving: GGUF Q4_K_M built from an imatrix optimizes for footprint.\nAlternatives allowed: GGUF Q8_0 (gguf-q8_0)\nPackaging pick: merged - Merged folds the adapter into the base: a larger artifact, no base-model dependency at serve time.\nRuntime for the smoke test: llama.cpp\nSmoke-test gate: task-grader verdict agreement with the pre-export outputs (byte match is expected to fail on a lossy export)\nApproximate artifact sizes: GGUF Q4_K_M + imatrix ~1.9 GB; GGUF Q8_0 ~3.4 GB; Merged bf16 safetensors ~6.4 GB",
"gate": "Verdict: PROMOTE - every stage cleared\nStage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 4 rows removed; label noise pass\nStage 2 capability drift: PASS\nStage 3 paired arena (deterministic): PASS - 30 wins, 40 ties, 10 losses of 80; win rate 62.5% (ties count half), 95% Wilson CI 51.5-72.3%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: NOT APPLICABLE - local-only deployment stops at stage 3"
}
Reply:
{
"task": "export",
"format": "gguf-q4_k_m",
"packaging": "merged",
"runtime": "llama.cpp",
"why": "GGUF Q4_K_M built from an imatrix is the edge and llama.cpp pick for laptop, desktop or CPU hardware, and the generic chat workload has no restriction against this quantization level.",
"steps": [
{
"step": "Merge the LoRA adapter into the base model to produce a standalone merged checkpoint.",
"command": "python merge_adapter.py --base_model BASE_MODEL_DIR --adapter CHECKPOINT_DIR --out MERGED_DIR"
},
{
"step": "Build llama.cpp with its default configuration and install the repo's gguf-py and sentencepiece.",
"command": "cmake -B build && cmake --build build --config Release && pip install -e gguf-py && pip install sentencepiece"
},
{
"step": "Convert the merged checkpoint to f16 GGUF.",
"command": "python convert_hf_to_gguf.py MERGED_DIR --outtype f16 --outfile OUT_DIR/model-f16.gguf"
},
{
"step": "Generate an importance matrix from a domain calibration corpus.",
"command": "./llama-imatrix -m OUT_DIR/model-f16.gguf -f CALIBRATION_CORPUS -o OUT_DIR/imatrix.dat"
},
{
"step": "Quantize to Q4_K_M using the imatrix.",
"command": "./llama-quantize --imatrix OUT_DIR/imatrix.dat OUT_DIR/model-f16.gguf OUT_DIR/model-Q4_K_M.gguf Q4_K_M"
}
],
"smoke_test": [
"Load the Q4_K_M GGUF checkpoint in llama.cpp, the real target runtime.",
"Replay the same 3 to 5 reference prompts used before export with deterministic decoding at temperature 0, keeping the same fixed seed as before export, using llama-completion rather than llama-cli.",
"Compare the outputs against the pre-export outputs.",
"Because this export is lossy, accept task-grader verdict agreement with the pre-export outputs rather than a byte match."
],
"watch_for": [
{
"signature": "Output comes out scrambled or does not break into separate turns the way it should.",
"cause": "Mismatch between the chat template used at export time and the one used to serve the model."
},
{
"signature": "Output reads fluently but ignores the expected response structure or makes no sense.",
"cause": "The lm_head layer was quantized incorrectly."
}
],
"note": "Merge the LoRA adapter into the base model first, then build llama.cpp before converting and quantizing to GGUF."
}
The page's check: kept - nothing to flag
Task up 9 pts, domain down 11.5 - gate
The gate says REJECT - decided by stage 2 HARD FAIL on domain-adjacent.
Input:
{
"task": "gate",
"run": "Checkpoint: legal-qa-r64-ckpt-3000; base model: Llama 3.1 8B Instruct\nTarget task: clause-extraction F1 70.2 -> 79.1 (+8.9 pts)\nDeployment: local only\nForgetting-ladder rungs already tried: none\nReplay rows reuse the drift suite's instruction phrasing: no",
"evals": "benchmark | baseline | checkpoint | delta (pts) | n | 95% half-width (pts) | seed rerun | budget verdict\nmmlu-subset | 68.2 | 67.6 | -0.6 | 1400 | 2.4 | - | noise\ngsm8k-subset | 81.0 | 80.4 | -0.6 | 1319 | 2.1 | - | noise\nifeval | 74.1 | 73.8 | -0.3 | 541 | 3.7 | - | noise\ndomain-adjacent | 64.0 | 52.5 | -11.5 | 1300 | 2.7 | - | HARD FAIL",
"facts": "Stage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 0 rows removed; label noise pass\nStage 2 capability drift: HARD FAIL\nStage 3 paired arena (judge): PASS - 118 wins, 30 ties, 52 losses of 200; win rate 66.5% (ties count half), 95% Wilson CI 59.7-72.7%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: NOT APPLICABLE - local-only deployment stops at stage 3\nExpected verdict: REJECT - decided by stage 2 HARD FAIL on domain-adjacent\nTop remediation (the page's pick): Rung 1 of the forgetting ladder: swap the replay-mix fraction (swap rows, do not add them; start toward 20%).\nFindings (3):\n[fix] hard_fail (stage 2): domain-adjacent dropped 11.5 pts (+/-2.7 pts at n=1300) - over the 5 pt budget: HARD FAIL, no exception for task gains.\n[info] floor_n (stage 2): Suites at the 200-item floor resolve about +/-6 pts at 70%; the strict size for a 2.5 pt half-width is about 1,291 items.\n[info] task_gain (stage 2): The target task gained 8.9 pts - task improvement never buys back a drift-budget breach.",
"deploy": "none",
"gate": "none"
}
Reply:
{
"task": "gate",
"verdict": "REJECT",
"uncertain": false,
"stages": [
{
"stage": 1,
"status": "PASS",
"evidence": "Data quality passed: 0 overlapping golden IDs, 0 rows removed in dedup, and label noise checks passed."
},
{
"stage": 2,
"status": "HARD FAIL",
"evidence": "Domain-adjacent dropped 11.5 pts (n=1300, +/-2.7 pts half-width), over the 5 pt budget: HARD FAIL, no exception for the 8.9 pt task gain."
},
{
"stage": 3,
"status": "PASS",
"evidence": "Paired arena: 118 wins, 30 ties, 52 losses of 200; win rate 66.5% (ties count half), 95% Wilson CI 59.7-72.7%, above the 55.0% threshold excluding 50%."
},
{
"stage": 4,
"status": "NOT APPLICABLE",
"evidence": "Local-only deployment stops at stage 3, so the canary rollout does not apply."
}
],
"evidence": "Stage 2 capability drift is a HARD FAIL: domain-adjacent dropped 11.5 pts (+/-2.7 pts half-width, n=1300), over the 5 pt budget with no exception for the checkpoint's 8.9 pt task gain. Stage 1 passed and stage 3 passed with a 66.5% win rate, but stage 2 governs and REJECT is the result; stage 4 does not apply to this local-only deployment.",
"remediation": "Apply rung 1 of the forgetting ladder: swap the replay-mix fraction by swapping rows, not adding them, moving toward 20%.",
"caveats": [
"The 8.9 pt task gain does not offset the stage 2 drift-budget breach.",
"Suites near the 200-item floor resolve only about +/-6 pts; a 2.5 pt half-width needs about 1,291 items.",
"After applying rung 1, a before/after comparison is a single-run hypothesis, not proof of fix."
]
}
The page's check: kept - nothing to flag
GSM8K down 3.1, no rerun yet - gate
The gate says RERUN - decided by stage 2 needs a seed-variation rerun on gsm8k-subset.
Input:
{
"task": "gate",
"run": "Checkpoint: triage-r32-ckpt-1500; base model: Mistral 7B Instruct v0.3\nTarget task: triage accuracy 74.0 -> 83.0 (+9.0 pts)\nDeployment: local only\nForgetting-ladder rungs already tried: none\nReplay rows reuse the drift suite's instruction phrasing: no",
"evals": "benchmark | baseline | checkpoint | delta (pts) | n | 95% half-width (pts) | seed rerun | budget verdict\nmmlu-subset | 62.5 | 62.0 | -0.5 | 1400 | 2.5 | - | noise\ngsm8k-subset | 52.1 | 49.0 | -3.1 | 1319 | 2.7 | - | rerun-seed\nifeval | 60.3 | 60.9 | +0.6 | 541 | 4.1 | - | noise\ndomain-adjacent | 71.0 | 70.6 | -0.4 | 1300 | 2.5 | - | noise",
"facts": "Stage 1 data quality: PASS - goldens leakage 0 overlapping IDs; dedup 9 rows removed; label noise pass\nStage 2 capability drift: RERUN pending\nStage 3 paired arena (judge): PASS - 126 wins, 34 ties, 40 losses of 200; win rate 71.5% (ties count half), 95% Wilson CI 64.9-77.3%, threshold 55.0% with the CI excluding 50%\nStage 4 canary: NOT APPLICABLE - local-only deployment stops at stage 3\nExpected verdict: RERUN - decided by stage 2 needs a seed-variation rerun on gsm8k-subset\nTop remediation (the page's pick): Rerun gsm8k-subset with seed variation and enter the rerun score.\nFindings (3):\n[info] dedup (stage 1): 9 duplicate rows removed before training.\n[fix] rerun_pending (stage 2): gsm8k-subset dropped 3.1 pts (+/-2.7 pts at n=1319) - in the 2-5 pt band: rerun with seed variation before deciding; only <=1 pt on the rerun passes.\n[info] floor_n (stage 2): Suites at the 200-item floor resolve about +/-6 pts at 70%; the strict size for a 2.5 pt half-width is about 1,291 items.",
"deploy": "none",
"gate": "none"
}
Reply:
{
"task": "gate",
"verdict": "RERUN",
"uncertain": false,
"stages": [
{
"stage": 1,
"status": "PASS",
"evidence": "Zero overlapping golden IDs leaked into training, 9 duplicate rows were removed, and the label-noise check passed."
},
{
"stage": 2,
"status": "RERUN",
"evidence": "gsm8k-subset dropped 3.1 pts (+/-2.7 pts, n=1319), in the 2-5 point band that requires a seed-variation rerun before deciding; only a rerun landing at <=1 pt will pass. The other three benchmarks moved at most 0.6 pts, within noise."
},
{
"stage": 3,
"status": "PASS",
"evidence": "126 wins, 34 ties, 40 losses of 200 (ties count half) gave a 71.5% win rate; the 95% Wilson interval 64.9-77.3% clears the 55.0% threshold and excludes 50%."
},
{
"stage": 4,
"status": "NOT APPLICABLE",
"evidence": "This is a local-only deployment, which correctly stops at stage 3; no canary is run."
}
],
"evidence": "Stage 2 decided the verdict: gsm8k-subset dropped 3.1 pts (+/-2.7 pts, n=1319), landing in the 2-5 point band that requires a seed-variation rerun before the checkpoint can be judged. Stage 1 passed and stage 3's arena reached 71.5% (95% CI 64.9-77.3%), but the report cannot end until the gsm8k-subset rerun is entered.",
"remediation": "Rerun gsm8k-subset with seed variation and enter the rerun score.",
"caveats": [
"The gsm8k-subset rerun must land at <=1 pt to pass; anything else resolves to hard fail, not another rerun."
]
}
The page's check: kept - nothing to flag
The drift-table CSV for the third sample
benchmark,baseline,checkpoint,delta_pts,n,half_width_pts,rerun,verdict
mmlu-subset,68.2,67.6,-0.6,1400,2.4,,pass
gsm8k-subset,81.0,80.4,-0.6,1319,2.1,,pass
ifeval,74.1,73.8,-0.3,541,3.7,,pass
domain-adjacent,64.0,52.5,-11.5,1300,2.7,,hard
Build the input yourself
Load the engine in node (global.window = global; require("./promokit.js"); require("./promoreply.js")), then:
const K = PromoKit, R = PromoReply;
const f = K.form({ name: "ckpt-2400", base: "the base model", evals: fs.readFileSync("drift.csv", "utf8"), taskline: "task 61.0 -> 69.0",
leak: "0", noise: "pass", mode: "judge", wins: "130", ties: "20", losses: "50", margin: "5", target: "local", rung: "0",
hw: "hopper", work: "chat", pack: "auto", params: "8" });
// f.rows: [{name, base, ck, n, rerun, j: {drop, hw, band, status}}] f.s1..f.s4: {status} f.verdict: PROMOTE | REJECT | RERUN f.uncertain
// f.remedy: the one fix f.exp: {format, alt, pack, sizes, runtime, gate} f.findings: [{code, level, stage, text}]
K.driftCSV(f) // benchmark, baseline, checkpoint, delta, n, half-width, rerun, verdict
const b = K.buildInput(f, "gate"); // b.ok is false, with b.note, until a drift row is read (export: until PROMOTE)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "gate");
// R.check(reply, b) // {status: kept or flagged, notes}
// R.report("ckpt-2400", b, reply) // promotion-report.md
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://promote-desk.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://promote-desk.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// promote-desk.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://promote-desk.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://promote-desk.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// PromoteDesk.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class PromoteDesk {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://promote-desk.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://promote-desk.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://promote-desk.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://promote-desk.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"promote-desk"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "promote-desk"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "promote-desk" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "promote-desk"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "promote-desk"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "promote-desk" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "promote-desk"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "promote-desk" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "gate", "run": "Checkpoint: ...", "evals": "benchmark | baseline | checkpoint | ...", "facts": "Stage 1 data quality: PASS ...", "deploy": "none", "gate": "none"}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "gate", "run": "Checkpoint: ...", "evals": "benchmark | baseline | checkpoint | ...", "facts": "Stage 1 data quality: PASS ...", "deploy": "none", "gate": "none"}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "gate" or "export", run, evals, facts, deploy, gate} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses promote-desk:<task>:<inputHash>:a<attempt> - the lane is part of the key, so a report and an export plan of the same checkpoint never collide - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A gate reply carries verdict, stages and remediation; an export plan format, steps and smoke_test; the code below prints either.
KEY="promote-desk:gate:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.verdict // .format, .evidence // .why, ((.stages // .steps)[] | "\(.stage // .step): \(.status // .command)")' reply.json
jq -r '.remediation // .note // empty' reply.json # the one fix (gate) or what to run first (export)
import hashlib, time
key = "promote-desk:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("verdict"))
print(reply.get("evidence") or reply.get("why", ""))
for s in reply.get("stages") or reply.get("steps") or []:
print(s.get("stage", s.get("step")), "-", s.get("status", s.get("command")))
if reply.get("remediation"):
print("one fix:", reply["remediation"]) # REJECT or RERUN only
import { createHash } from "node:crypto";
const key = `promote-desk:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.verdict);
console.log(reply.evidence ?? reply.why);
for (const s of reply.stages || reply.steps || []) console.log(s.stage ?? s.step, "-", s.status ?? s.command);
if (reply.remediation) console.log("one fix:", reply.remediation); // REJECT or RERUN only
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "promote-desk:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
items, _ := reply["stages"].([]any) // an export plan lists "steps" instead
fmt.Println(j["charged_credits"], j["truncated"], reply["verdict"])
fmt.Println(reply["evidence"], reply["why"])
for _, x := range items {
c := x.(map[string]any)
fmt.Println(c["stage"], "-", c["status"])
}
if fix, ok := reply["remediation"].(string); ok && fix != "" {
fmt.Println("one fix:", fix) // REJECT or RERUN only
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "promote-desk:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.get("verdict"));
System.out.println(reply.path("evidence").asText(reply.path("why").asText()));
for (JsonNode s : reply.has("stages") ? reply.path("stages") : reply.path("steps")) System.out.println(s.path("stage").asText(s.path("step").asText()) + " - " + s.path("status").asText(s.path("command").asText()));
if (!reply.path("remediation").asText().isEmpty()) System.out.println("one fix: " + reply.get("remediation").asText()); // REJECT or RERUN only
require "digest"
key = "promote-desk:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["verdict"]].inspect
puts reply["evidence"] || reply["why"]
(reply["stages"] || reply["steps"] || []).each { |s| puts "#{s["stage"] || s["step"]} - #{s["status"] || s["command"]}" }
puts "one fix: #{reply["remediation"]}" unless reply["remediation"].to_s.empty? # REJECT or RERUN only
$key = "promote-desk:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", ($reply["verdict"] ?? ""), "\n";
echo $reply["evidence"] ?? $reply["why"] ?? "", "\n";
foreach ($reply["stages"] ?? $reply["steps"] ?? [] as $s) echo $s["stage"] ?? $s["step"], " - ", $s["status"] ?? $s["command"], "\n";
if (!empty($reply["remediation"])) echo "one fix: ", $reply["remediation"], "\n"; // REJECT or RERUN only
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"promote-desk:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply!["verdict"]}");
Console.WriteLine($"{reply["evidence"] ?? reply["why"]}");
foreach (var s in (reply["stages"] ?? reply["steps"]) as JsonArray ?? new JsonArray()) Console.WriteLine($"{s!["stage"] ?? s["step"]} - {s["status"] ?? s["command"]}");
if (reply["remediation"] is JsonNode fix && fix.GetValue<string>() != "") Console.WriteLine("one fix: " + fix); // REJECT or RERUN only
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: promote-desk:gate:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "gate", "run": "Checkpoint: ...", "evals": "benchmark | baseline | checkpoint | ...", "facts": "Stage 1 data quality: PASS ...", "deploy": "none", "gate": "none"}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "promote-desk:gate:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `promote-desk:gate:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "promote-desk:gate:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "promote-desk:gate:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "promote-desk:gate:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: promote-desk:gate:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"promote-desk:gate:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);