wai-quantum-writer: WAI Quantum writes a program for a task, on its own model #646
Loading…
Reference in a new issue
No description provided.
Delete branch "wai/quantum-writer"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The loop the job service will use to write a program for a task, on WAI's own model and runtime. Stacked on #645 (
check_exercise): this branch carries #645's commit, and its watcher starts once #645 merges.What it does
A task is an exercise in the course's form (
wai.quantum.exercise/1): what the program must do (an intent, or one for each case), its constraints, its job, and a starter program. The loop runs in rounds:It stops at the first program that passes, or when the rounds run out (four repairs by default).
The code model (
src/coder.rs): North Mini Code 1.0 (Apache-2.0) on the pure-Rust runtime (jouleclaw-loader-gguf).The model's identity: a manifest of its files (
wai.quantum.model/1,wai-model.json), with each file's name, size and BLAKE3 digest. The model is named by that manifest's digest.openloads exactly the files the manifest lists and checks their sizes;open_verified(and--verify) reads every byte first. The identity says which ("checked").The transcript (
wai.quantum.write/1) records:Timing is returned beside the transcript rather than inside it, since a replay does not reproduce time.
The command line:
wai-quantum-writer write --model DIR TASK.json,course --model DIR --out DIR(the measurement below),manifest DIR,verify DIR.The engine:
course::task_ofshows any exercise the way a lesson shows its own. Its title and task can be one string or one per locale.course::exercise(id)returns a lesson's exercise document.lesson()now builds its fields through the same helpers.Why on our own model
WAI Quantum is its own stack. A program that WAI writes for a task should not depend on another company's model or agent. The runtime is ours and its decode is greedy, so a written program can be replayed byte for byte from the model's digest, on a host with the same arithmetic.
What measuring it on the course showed
I ran an earlier version of this loop as a harness over the course's 21 exercises, on the build host's CPU.
Review fixes (2026-11-06)
A review of the write loop found these in the loop and the model wrapper. Fixed here:
task_digestis taken over), and holds it toengine::course::exercise_form(#645) before the model is asked anything. Before, the same task with its keys in another order, or a bound written0.0, was two prompts and two checks under one digest. A task whose job asked for a repair, or whose bound held vacuously, went to the model.check_memory_bytes, the memory each check's run is planned in. The command line and the service planned in different memory, and the transcripts did not say so.arch,osandmatmul, which is NEON fused multiply-add on aarch64 and a scalar loop elsewhere. A test holds the named kernel to the one that runs. The claim in the crate's docs, its description and the changelog is now "the same bytes in the same arithmetic".openbinds the files the manifest lists, in its order. It refuses a manifest that lists none, and a directory that holds a model file the manifest does not list or lacks one it lists. Before, a stray shard that sorted first was bound in place of the listed one, under the old digest. The identity says how far the files were checked:"sizes", or"blake3"withopen_verified/--verify.answer_ofdrops<|START_TEXT|>and cuts at<|END_TEXT|>.Second review (2026-11-06)
max_new(201 tokens of 200).MIN_ANSWER); otherwise the reply ends with no answer.the_cap_keeps_within_max_new_and_closes_a_reopened_thinkingandan_empty_answer_is_no_programboth failed onfbbee6fd9for the reasons they state."checked": "blake3"held only at check time.open_verifiednow opens each file once, hashes it through the mapping the model reads (map_checked), and parses from that same mapping. It reads the config and the tokenizer from the bytes it hashed. The docs say what holds after mapping: a file renamed over is not seen; a file written in place can change what the mapping reads. Testa_shard_is_checked_through_the_mapping_the_model_reads.--check-memory-bytesgives the command line the check's memory a service planned in. Testevery_recorded_option_can_be_given, which failed onfbbee6fd9because the flag was ignored.libm: glibc's version, or macOS's release and build). Testthe_maths_library_is_named.matmul_bf16_autofor one row and three, and the batch.The "no answer" replies: a thinking cap
Run 2 (21 lessons, 3,000 tokens, greedy) passed 11 at once, 15 after one repair and 18 after two, then stayed flat. The three it never passed (
phase-estimation,bit-flip-code,toffoli) all ended in no answer. 23 of its 43 attempts spent the whole budget thinking.A reply still thinking at its cap now has
<|END_THINKING|><|START_TEXT|>fed for it, and decodes its answer in the tokens left. The cap isOptions::thinking_cap: two thirds ofmax_new(2,000 of 3,000), or--think-max N. The transcript records the cap (decoding.think_max) and, for each attempt,thinking_closed.courseprints how many replies gave no program and how many had their thinking closed. The decode loop is a function over a small trait, so a test drives it with a model that thinks for ever. It is not yet measured on the real model; how to measure it:wai-quantum-writer course --model DIR --out run3-off --think-max 3000. A cap equal tomax_newnever closes, so this is run 2's setting on this code. Re-run it rather than compare with run 2: canonical tasks can order a lesson's prompt differently.wai-quantum-writer course --model DIR --out run3-cap, at the default cap of 2,000. Optionally run 1,500 and 2,500 as well.results.jsonl, compare per lesson:answered,thinking_closed,thinking_tokensandpassedfor each attempt, andtiming.Tests
tests/write.rsruns the loop with a scripted model, so no checkpoint is needed. It covers:63fb4a39cfor the reason it states: one task, two prompts; a task with{"t_count": 0}was written for.src/coder.rs(new, with the model compiled in, no checkpoint):63fb4a39c: the markers were in the answer, and an unlisted shard was taken.coder, 1 in the command line, 11 intests/write.rs), 11 without. Clippy with-D warningsis clean in both configurations.engine::course: 15 pass, includingany_exercise_is_shown_as_a_lesson_shows_its_own(every lesson, both locales, and a case named with one string).wai-quantum-docs.ymlcounts the course's fifteen tests and requires each by name.wai/changes/2026-11-05-quantum-writer-writes-program-for-task.md, beside #645's.wai/CHANGELOG.mdis main's.wai-quantum-cli.ymlnow runs the writer's tests and clippy. It also runs when the writer orjouleclaw-loader-ggufchanges.Not covered here: a run of
CodeModelagainst the real checkpoint (57 GB), with or without the cap; the steps above measure it.🤖 Generated with Claude Code
The loop the job service will write programs with, as a crate of its own. The task is an exercise in the course's form (wai.quantum.exercise/1). A model writes a program, and the engine checks it against the task exactly as it checks a lesson's answer (engine::course::check_exercise). What went wrong goes back to the model in the engine's own words, until a program passes or the rounds run out. - The code model (src/coder.rs): North Mini Code 1.0 (Apache-2.0) on the pure-Rust runtime (jouleclaw-loader-gguf). - Decoding is greedy, so the same model files, task and engine give the same programs. - The conversation stays in the model's cache: a repair round feeds only the new turn, where the measurement harness re-read the whole conversation each round. - The loop takes the answer after the model's thinking, so thinking text is never checked as a program, and a reply that never leaves its thinking is asked again. - The model's identity: the digest of a manifest of its files (wai.quantum.model/1, wai-model.json). open() checks each file's size against it, and verify reads every byte. - The transcript (wai.quantum.write/1): the task, model and prompt (version, system and first-turn digests), the decoding, the engine's version, and each attempt (program, digest, the engine's check, tokens). Timing is returned beside it, since a replay does not reproduce time. - The engine: - course::task_of shows any exercise as a lesson shows its own, its title and task given as one string or one per locale; - course::exercise gives a lesson's exercise document; - lesson() now builds its fields through the same helpers, and a test holds every lesson's fields in both locales equal to task_of's. - The command line: wai-quantum-writer write | course | manifest | verify. - CI: wai-quantum-cli.yml tests the writer with a scripted model, with and without the model compiled in, plus clippy. The workflow now also runs for changes to the writer and to the loader crate it depends on. Tests (all run on the build machines): - wai-quantum-writer: 7 pass, default features and without them; clippy -D warnings is clean. - engine::course: 11 pass. - scripts/check-lockfiles.sh: 61 lockfiles, all current. Stacked on #645 (check_exercise). Co-Authored-By: Claude Opus 5.5 <[email protected]>The workflow pins how many course tests pass and names each one, since a module filter is satisfied by any one test. This branch adds any_exercise_is_shown_as_a_lesson_shows_its_own to engine::course, and the pin it now inherits from the branch below counts ten. So the step failed here with all eleven passing ("test result: ok. 11 passed" in the job's log, then "the course's tests did not all pass"). The count is eleven and the new name is the last in the list, the same lines the branch stacked above this one already holds, so the two merge as one change. Co-Authored-By: Claude Fable 5.1 <[email protected]>The loop, as reviewed: - A task is read in its canonical form (RFC 8785, what task_digest is taken over) and held to engine::course::exercise_form before the model is asked anything. The same task with its keys in another order, or a bound written 0.0, was two prompts and two checks under one digest; a job asking for a repair, a vacuous bound or an unknown constraint went to the model. - The transcript records check_memory_bytes, the memory each check's run is planned in, which decides what a check runs: the command line and the service used different caps and their transcripts did not say so. - The replay claim is scoped: the model's identity names the arithmetic it ran in (arch, os, and the BF16 matmul kernel: NEON fused multiply-add on aarch64, a scalar loop elsewhere), and a test holds the named kernel to the one that runs. The same files, task, options and engine give the same bytes in that arithmetic. The model wrapper: - CodeModel::open loads exactly the files wai-model.json lists, in its order, and refuses a manifest that lists none and a directory holding a model file it does not list or lacking one it lists (a stray shard sorting first was bound in place of the listed one). It checks sizes, and says so in the identity ("checked": "sizes"); open_verified (and --verify) reads every byte first and says "blake3". - answer_of drops <|START_TEXT|> and cuts at <|END_TEXT|>, which landed in the program. And the measured "no answer" (run 2: 23 of 43 attempts spent the whole budget thinking): a reply still thinking at its cap (Options::thinking_cap, two thirds of max_new unless --think-max says) has <|END_THINKING|><|START_TEXT|> fed for it and decodes its answer in the tokens left. The transcript records the cap and, per attempt, thinking_closed; `course` prints how many replies gave no program and how many were closed. The decode loop is a function over a small trait, so a test drives it with a model that thinks for ever. Each loop test failed on63fb4a39cfor the reason it states (a malformed task was written for; one task, two prompts), and the coder tests for theirs (the markers were in the answer; an unlisted shard was taken). The course's case names given as one string are shown by it. Co-Authored-By: Claude Opus 5.5 <[email protected]>The course words these bounds as "at most n" (constraint.t_count, two_qubit, estimate), and the check read a min too: {"t_count": {"min": 2}} showed the model "at most ? T and T† gates" while it failed every program with fewer than two. The reader now takes {"max": n} alone for them (engine::course::at_most), and exercise.v1.json says so ($defs/at_most), with the T count's description saying how T gates are counted. No lesson uses a min there. Found sweeping the review's family of claims the check makes that the code does not keep. Co-Authored-By: Claude Opus 5.5 <[email protected]>The second review found finding 1 closed only for undeclared inputs. A program that declared `input uint err;` and never used it passed the bit-flip lesson at proof, and one that branched on the classical err passed the syndrome lesson. A new constraint, `inject`, names the input whose value is the qubit an error flips. The cases' programs may then differ in that one gate alone, an x on the qubit the case names, every other gate, measurement and step the same (course::injected). So the error is in the quantum state, the program cannot read it classically, and what finds and corrects it is the program's quantum operations, the same in every case. A program that never applies the error, reads it elsewhere, or flips another qubit fails, saying which. A second, `qubits` ({"max": n}), closes the last way round: an answer kept on a qubit no error reaches. bit-flip-code and syndrome set both; their solutions and wrong answers keep their results. The exercise schema names both, and a case set an injection cannot be made from is refused. Test an_injected_error_is_corrected_from_the_qubits_and_never_read, failing onba59c776efor the reason it states (the declared, unused input passed bit-flip-code). Course tests 13 -> 14 in wai-quantum-docs.yml. Co-Authored-By: Claude Opus 5.5 <[email protected]>