Myna
The decision engine that never re-reads. Typed choice / score / noul
decisions over growing observations — tickets, threads, page states,
documents — where the observation is scanned once and every later request
only pays for what changed.
Born from a year inside laya and kev: both are non-autoregressive System-1 decision models, and both re-encode the entire state on every request. Agent loops don't work that way — the state grows at the edge and the questions are short. Myna is an architecture built for that shape from the ground up.
Quickstart: install, fetch the weights, ask one question
pip install "git+https://github.com/aashish254/myna.git"
myna-weights # → ~/.cache/myna/antiprior_off_s0-weights/{model.pt,tokenizer.json}
from myna import Myna, fetch_weights
myna = Myna(fetch_weights()) # the best published arm: test macro 0.5339
out = myna.predict("My order never arrived and I want a refund.", {
"department": {"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": ["billing", "technical", "shipping", "returns", "other"]}})
print(out["answers"]["department"]["choice"])
That prints billing, at confidence 0.7899749875068665 with shipping the
weakest of the five at 0.00027509400388225913, over the Release bytes whose
sha256 is 30f0fa937e57e8ce8b21c58315dc7e800151a0c95e003dc0930e0f75d588f1e5 —
16,926,848 params at temperature 1.5. Measured 2026-10-01 on an M5 at latency_ms
7.84 (runs/quickstart_from_gh_download.log), 6.98 from a package installed off an
unpushed tree (runs/quickstart_from_git_install.log), and 7.93 from the install line above
pasted literally into a fresh venv, which resolved to fe11db6 and fetched its own weights
(runs/quickstart_from_public_install.log). Same choice, same float, three harnesses; only the
milliseconds move.
myna-weights takes --tag (every published arm is a tag: antiprior_off_s0-weights
is the 0.5339 control, antiprior_on_s0c-weights the 0.4785 one, v1b-checkpoint the
0.4893 Kaggle run), --dest DIR, --force, and --check, which prints whether the
cache hashes and exits 1 without writing anything. It needs no gh, no token and no
extra: it is stdlib urllib, it verifies each file against the digest GitHub publishes
for that asset, and it writes a SHA256SUMS beside them that shasum -a 256 -c reads.
Weights are deliberately not in the wheel — a pip install that downloaded a
particular training run would make every published macro a side effect of an install.
Prefer the tool you already have? The same bytes:
gh release download antiprior_off_s0-weights -D ~/.cache/myna/antiprior_off_s0-weights
Over HTTP, the server already ships (the extra is one install away:
pip install "myna[serve] @ git+https://github.com/aashish254/myna.git"):
myna-serve --ckpt ~/.cache/myna/antiprior_off_s0-weights --port 8080
curl -s localhost:8080/v1/predict -H 'content-type: application/json' \
-d '{"state":"My order never arrived and I want a refund.","questions":{"department":{"type":"choice","instructions":"Which team should handle this ticket?","criteria":["billing","technical","shipping","returns","other"]}}}'
/v1/predict takes {state, questions} and returns the same answers object the
engine prints; /v1/sessions keeps an observation so /ask and /append never
re-read the state.
Everything above runs today. What those weights are worth is the table below, and its headline is not good: the engine answers, and on the benchmark that decides G1 it does not answer well enough to ship.
Benchmarks, every number measured
Where this stands: the release gate, all seven rows
3 of 7 met, 3 not met, 1 open — the release gate is NOT clear.
Every verdict below is checked against a committed artifact — the registry
row beside it and the field read out of that artifact by
uv run python bench/gates.py --check — and the same words head the
status column of SPEC.md §2.2. The gate that decides whether
this is a product (G1) and the gate that decides what it can read (G4)
are the two that cannot be closed on this box.
| gate | verdict | what the committed artifact prints | what it does not claim |
|---|---|---|---|
| G1 — Accuracy beats the witness | not met | v1b-kaggle-3600b's committed report prints "pass": false for this gate: over the 16 cells both harnesses score, per-cell unweighted, myna 0.4893 · laya 0.6668 · majority floor 0.4331 — +0.056 over the floor against the +0.15 required and 0.211 short of 0.70. Its train.log is committed beside it: 3,600 updates in 20,225 s on a T4, and a dev curve whose last 1,000 updates still gain +0.0072 (0.4283 at step 2,500 → 0.4355 at 3,500). For contrast, the untrained control here measures 0.346 / 0.351 (runs/scratch_decision_v2_test.md). |
not met, measured — and the measurement is an artifact rather than a transcript. §9.30 refuses a figure whose witness is not committed, and until this tick the Kaggle run's metrics.json lived in a notebook output directory, so the gate had to sit at open; bench/reproduce.py's v1b-kaggle-macro, kaggle-dev-tail and kaggle-wall-clock rows re-derive every figure above from files in runs/. What the verdict does not settle is dose: that tail slope is why a longer run is not a free +0.21, and §9.38 writes the correction of the remembered "flat from step 2,250" claim. The void pre-385e06c checkpoint's 0.338 is deliberately not a row here — a not met may not rest on a void artifact (§9.23), so it stays in SPEC's prose as contrast and out of the evidence. |
| G2 — Latency claim survives equal-footing re-measurement | met | one M5 process, both engines fp32, Agent.system_one called directly, ladder capped inside laya's own 1024 window: 3.04×/3.21×/4.12×/3.60× end-to-end and 6.78×/4.67×/4.75×/3.65× ask-only at 1/5/10/50 questions, each cell the minimum of two committed runs. |
met as a ratio, which is all G2 asks. It carries no accuracy implication: the model that is faster is the one that still fails G1, and the millisecond columns behind these ratios are load-dependent on a box other sessions share (§9.23) — quote the ratio, never the milliseconds. |
| G3 — Deployment works for real | met | the exported artifact answers three typed decisions in real Chrome 153 with the same probabilities the torch engine prints — 6/6 parity checks, both isolation modes, desktop and mobile widths — and its bytes, cold-load and p50 are measured there with the box's load average printed beside them. | met as a mechanics gate, and the caveat is part of the verdict rather than a footnote: what ships is v0, which fails G1 and G5; the ≤ 20 MB int8 route is open on bytes and closed on agreement (§9.28); the Apple-silicon int8 route closes for the opposite reason — 17.40 MiB at 9.24e-03 and no latency win at all, so fp32 is the artifact on both paths (§9.29). |
| G4 — Long-context is correct, not just cheap | not met | v0's needle curve prints 0.188 at 128 tokens against a 0.167 uniform floor — at chance on the shortest rung — so the longer rows are the decay of nothing, and runs/needle_myna-v0.md prints G4: not measured by this run in place of a table a reader could quote. |
not met, and not met by a run that cannot answer the question. The 16k state is measured, fixed at 576 KiB and cheap (§9.22); the 16k decision needs the 4k truncated-backprop checkpoint, which is 7b / KAGGLE. Those are two different claims and only the first one is settled. |
| G5 — Useful confidence, with abstention | not met | the curve's own g5.pass is false: accuracy climbs 0.349 → 0.535 as coverage falls 1.00 → 0.20 over 448 rows / 568 questions, no rung reaches 0.95 (the max is 0.596, at 10% coverage), and G5's three named sources measure 0.000 / 0.025 / 0.150. |
not met on the level, met on the machinery, and the gate is the level. Myna(abstain_below=t) withholds the commitment with a measured reason and the fallback seam labels which engine committed — a confidence that ranks the answers of a model that cannot answer is routing, not the product G5 describes (§9.24). |
| G6 — No regression on what already worked | open | v0's measured test macro is 0.9523 — the macro of the nine === test === rows in runs/train-v0.log, recomputed from its rows because the log prints no overall line — so the level clears today. |
open rather than met, because the gate is written against the next checkpoint: it says "no regression", and there is no trained v1 artifact in this repo to regress. A draft of this row cited 0.951, a figure no artifact prints (§9.30), which is why the value here is computed from the log's own rows. |
| G7 — Reproducibility | met | make repro is green at this tick: 34 registry rows bind every published table cell to one command and one committed witness, and the 101 quoted figures are re-read out of those files rather than out of the prose. The python commands among those rows are checked against the tool behind them — its --help must still print every flag the row publishes (§9.43). The seeds live inside the printed commands (--seed 0, --seeds 0 1), not in sentences about them. |
met as a binding, not as a rebuild, and the gap is disclosed rather than absorbed: no target executes the registry end to end — 22 rows re-run on this box, 6 retrain a checkpoint, 2 need a laya checkout on PYTHONPATH, 3 need Google Chrome and 1 is KAGGLE. That is TODO 8b, which stays unticked for exactly this reason. |
The architecture
- Gated linear attention trunk. Each layer carries a fixed-size matrix
state
S ∈ R^{H×d_k×d_v}(96 KiB/layer at production width), not a KV cache that grows with context. Three execution forms — recurrent (streaming), chunked (training, linear memory), quadratic (reference) — are proven numerically identical, in float64, in CI. - Persistent state = the cache. Appending to the observation continues
the scan from the cached
S. Nothing below the append point is ever re-read, and the result is bit-exact versus a full pass — not approximate, not distilled, identical. The only thing retained per observation is the stack ofSmatrices: 576 KiB at production width, for a 128-token state or a 16,384-token one. - Questions are branches, not prompt tokens. Each question resumes every layer from the cached state-end, so a question reads the whole observation and — by architecture, not masking — nothing of any other question. Adding, removing, or reordering questions changes no other answer.
- No vocabulary readout anywhere. All three answer types share one pointer-probe head over option spans. No generation, nothing to parse, nothing to hallucinate.
| params | weights on disk | state memory | context | |
|---|---|---|---|---|
| myna-v0 | 14.45M · m | 55.13 MiB fp32 · m | 576 KiB, 128 or 16,384 tokens alike · m | 16,384 scanned and answered from · m |
| laya | 421.29M, read from their checkpoint · m | ~1.7 GB fp32 · p | holds none — re-encodes per request | 1,024 + 256 head, from their config.json · m |
| kev-4B | ~4B, published | ~8 GB fp32 · p | KV grows with the context · p | — |
Every cell above carries its own tag. m measured here, from a file in this
repo: myna's parameter count and artifact size are n_params and
sizes.mlx_fp32.file_bytes in runs/bench_mlx_int8.json, the 576 KiB is the
state_bytes_total: 589824 that runs/browser_g3.json prints on every one of its
four Chrome rows, and laya's two cells are the line runs/latency_matched.md
prints when it opens their snapshot. p projected — arithmetic from a
parameter count against a model we did not open, which is why it is not a
comparison cell. The decision accuracy at long context is a third thing again
and it is gated: the needle test puts v0 at 0.188 on a 128-token state against
a 0.167 floor, i.e. at chance before length even enters, and
runs/needle_myna-v0.md prints G4: NOT MEASURED here rather than a decay curve.
The context column is a cost statement, and the only 16k number this repo has
measured. Long-context correctness waits on the checkpoint trained for it
(SPEC.md §2.2 G4, §9.25).
Reproduce the params and weights columns — n_params, and both file_bytes — with the
MLX harness that owns the checkpoint. It is also the run whose --drift-rows 12 prints
the 576 KiB row at every state length from 128 to 16,384:
uv run python -m bench.bench_mlx --ckpt runs/myna-v0 --reps 20 --drift-rows 12 --out runs/bench_mlx_int8.md
and the context column with the needle run that the 0.188-against-0.167 pair came from
(runs/needle_myna-v0.md, and its G4: NOT MEASURED line):
uv run python bench/eval_needle.py --ckpt runs/myna-v0 --device cpu --n 16 --seed 0 --lengths 128 1024 4096 8192 16384
On the upstream corpus: three rows, including the one nobody likes
G1 is an accuracy gate on kev's frozen decision-v2 test split, and the
checkpoint that answers it trains on Kaggle. Three numbers on that split are
printable from this box today without training a model, and SPEC.md §8
requires all three rather than the flattering pair:
| row | test MACRO over the 16 (source, question) cells both sides score | footing |
|---|---|---|
| laya 421M — their engine, their suite, 40 rows/source | 0.667 · m | runs/laya_decision_v2_test.json, joined cell by cell |
| myna, untrained control — random init, the suite's own tokenizer, current code | seed 0 0.346 · seed 1 0.351 · mean 0.348 · m | runs/scratch_decision_v2_test.md |
| myna trained on this corpus (G1) | gated here — the Kaggle checkpoint has run and its verdict is TODO 3i's to publish; until its witness is a file in this repo, a figure for this row is a number with nothing behind it (§9.30) | kaggle/run.py, TODO 3i |
| majority-label floor, same 16 cells | 0.433 · m | runs/report_scratch_vs_laya.log |
| uniform-chance floor, same 16 cells | 0.332 · m | same |
majority-label clip of that control — max(model, floor) per cell, an oracle not a result |
0.436 · m | runs/report_scratch_vs_laya.json |
The last row is the one that must not be read as a score. myna.report now prints
clip = max(model, majority) beside each cell's own accuracy, and the control's
16-cell clip macro is 0.436 against its model macro of 0.346 — the +0.090
difference is "answering nothing, per cell, with this split's majority label", which
is an oracle statistic (the floor is measured on the rows being scored), it is
available to no model, and the report says so on the line that prints it
(runs/report_scratch_vs_laya.log: 13 of 16 cells answer below their own floor).
Printing it is reporting; acting on it would be a per-cell fallback rule — a
leaderboard decision, not a learning improvement, and SPEC §5 P9: 9c builds the
report and stays out of that decision.
The same reasoning is why the scope question is priced rather than argued. Two cells
(banking77/intent, mnli/relation) answer below their own floor and do not beat their own
permutation nulls, so a reader may reasonably ask whether G1 should be scored without them.
uv run python -m bench.scope_pricing answers it from the committed run: dropping them moves
the level 0.4893 → 0.5334 and the majority floor 0.4331 → 0.4666 with it, because the cells
myna wins are the ones a majority classifier wins too, so the margin G1 gates on moves
+0.0563 → +0.0668 — eleven-thousandths. Dropping their whole sources is the same 14 cells, and
even removing all five below-floor cells reaches 0.5526, still 0.147 short of 0.70. No scope
choice here passes G1 (runs/scope_pricing.json); whether those two cells belong in the claim
at all stays P9: 9d's call, and it is a decision about what the published number means. The
table is mutation-checked like everything else this repo publishes
(uv run python bench/mutation_scope_pricing.py, 24 mutants): its first pass caught 10 of them,
and the 13 survivors were all in the harness's main() — the guard's individual comparisons, how
the witness is assembled, and what the printed table puts under which column heading — not in the
arithmetic, where 8 of 8 died (SPEC §9.46).
The disclosure in the middle. The one checkpoint this repo ever trained on the
upstream corpus scores 0.338 on those same 16 cells
(runs/report_void_vs_laya.log, from
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/myna-v1-rich/metrics.json --laya runs/laya_decision_v2_test.json --out runs/report_void_vs_laya.json)
— below its own control, by 0.008, which is the
same order as the control's 0.005 seed spread, and void as evidence regardless
because it predates the 385e06c batching fix (§5 P1). So the honest sentence
about this repo as it stands is: no committed measurement of myna's
architecture on this corpus exists in either direction — the control that could
not have learned anything sits on the chance floor, and the checkpoint that could
is not here. What the control does prove is the one falsification that mattered —
a probe head or an answer head that leaked the label would sit above the 0.332
chance floor, and it sits on it. The third row waits on its artifact rather than
on a run: v1b-kaggle-3600b has trained and reported its own metrics, and TODO 3i
publishes that number the moment its witness is a file in this repo, because a
figure for a checkpoint this repo cannot open is a number with nothing behind it —
which is the mistake §9.30 exists to prevent.
Reproduce the control (1,440 rows scored on CPU; no gradient step, on any corpus):
uv run python bench/eval_scratch.py --split test --seeds 0 1 --out runs/scratch_decision_v2_test.md --metrics-out runs/scratch_metrics_test.json
and the join that puts it beside laya's per-cell numbers:
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/scratch_metrics_test.json --laya runs/laya_decision_v2_test.json --out runs/report_scratch_vs_laya.json
Two statistics that must never be subtracted from each other. The table is the
unweighted mean over the 16 cells both sides scored; laya's own harness prints a
row-weighted overall of 0.6319 over its 546 sampled answers, which is a different
statistic over a different sample — a cell counted by rows and a cell counted once
are not comparable, and arithmetic across the two is how §9.30's eight dead cells
were born. The join also merges the competitor's two contrastive/decision rows (one
qid asked as a 4-way choice over 24 answers and as a noul over 16) into that cell at
0.500 by rows; until §9.32 the report kept whichever row came last — 0.625 — which
had inflated every per-cell laya figure here by 0.008–0.009.
Measured on an Apple M5 (10-core, 32 GB)
One process, both engines resident, device=cpu, both fp32 — the same Python
str state and the same question dict handed to each engine. laya is called as
Agent.system_one on its published 55cf4c4 checkpoint, on laya's own
Stripe-payout ticket (87 myna / 57 laya tokens), alternating 3-option choice
and noul questions exactly like laya's own latency bench. p50 over 10 reps
after 3 warm-ups:
| questions | laya system_one |
myna end-to-end | ratio | myna, state already scanned | ratio |
|---|---|---|---|---|---|
| 1 | 100.7 ms | 33.1 ms | 3.04× | 14.9 ms | 6.78× |
| 5 | 272.0 ms | 74.9 ms | 3.63× | 55.5 ms | 4.90× |
| 10 | 506.3 ms | 120.8 ms | 4.19× | 98.2 ms | 5.15× |
| 50 | 3,589.5 ms | 870.0 ms | 4.13× | 787.1 ms | 4.56× |
Reproduce: PYTHONPATH="<laya checkout>" .venv/bin/python -m bench.bench_latency_matched → runs/latency_matched.md. Every cell in this table
is m — measured, on this box, by the command above, and the numbers after it
("14.5–17.4 ms", "−10 µs/token", "R² 0.74") are least-squares fits over the same
run's ladder, which is why they are quoted as fitted rather than as read. That
table is one run;
the claim shipped in SPEC.md §2.1 is the per-row minimum across the two
committed runs — 3.04× / 3.21× / 4.12× / 3.60× end-to-end and 6.78× / 4.67× /
4.75× / 3.65× streaming — because two runs of this command on this laptop differ
by up to 22% in absolute ms (other sessions share these cores), and the
conservative number is the only one that belongs in a README (SPEC §9.23).
Accuracy is not measured by this table and nothing here implies it.
What the shape of the cost says, fitted over a state ladder capped inside laya's
1024-token window: answering from a cached state costs 14.5–17.4 ms whether that
state is 66 or 1,060 tokens (fitted state slope −10 µs/token, i.e. zero) — the
fixed-size state, measured rather than drawn. The scan is not flat: within a
256-token chunk it is quadratic, so the sub-40 ms decision is a short-state result
(SPEC §9.22). laya's cost is state × questions — one sequence carrying the whole
state, once per question — which is why its additive fit lands at R² 0.7356 while
myna's end-to-end fit lands at 0.9915 (the ask-only fit is 0.9619, and the three
numbers are the harness's own fit block, not restatements of each other).
An earlier version of this section published 33×–239× speedups against a "~500 ms
laya floor". The floor was Router truncating the state at 1,024 tokens while
myna read all of it, and the speedup column was myna against itself. Withdrawn —
SPEC.md §9.20.
In a browser tab, measured
The exported artifact runs with no server and no build step. browser/index.html
re-implements the tokenizer in JavaScript from tokenizer.json alone, chains
state_step.onnx one call per 256-token chunk, runs the pointer head and the
abstention gate over the option spans from head.bin, and answers the same three
typed decisions the Python engine answers — checked against the Python engine by
browser/parity.mjs, the same module node browser/selftest.mjs fails a build on
(7 checks in node, 6 in the tab, which has no filesystem to compare bytes against).
Google Chrome 153, headless, cold cache, throwaway profile, 15 decisions per row
(bench/browser_g3.mjs → runs/browser_g3.json, and runs/browser_g3_int8.json
for the right-hand column). Every cell is m:
| fp32 — what ships | int8 — attempted | |
|---|---|---|
| artifact the page fetches | 59.32 MiB | 18.56 MiB |
| bytes on the wire, incl. the 13.58 MiB wasm runtime | 73.09 MiB | 32.33 MiB |
weights.bin requests |
1 (shared by both graphs) | 1 |
| cold load | 254–394 ms | 214–336 ms |
| p50 per decision, 10 threads / 1 thread | 436 / 742 ms | 413 / 757 ms |
| option probabilities vs the torch engine | 1.03e-06 | 6.48e-02 |
| Chrome's own parity checks | 6/6 | 4/6 |
Two things that table says and one it does not. It says the trunk really is transferred once — 93.7 MiB of duplicated fp32 weights in the first export (at its own 8 × 256-token shape, per SPEC §5 P6 6a), 59.32 MiB now, with Chrome's network log as the witness rather than a sum in a README. And it says the ≤ 20 MB int8 route is a byte win and an agreement loss: dynamic quint8 lands at 18.56 MiB and moves option probabilities 648× past the bound the fp32 artifact meets, for 5% latency on a box that was busier during the int8 run — so int8 ships nowhere (SPEC §9.28; §9.29 is the same verdict on the Apple path, for the opposite reason). What the table does not say is anything about accuracy: the checkpoint in that artifact is v0, which still fails the real-corpus gate below.
uv run python -m myna.onnx_export --ckpt runs/myna-v0 --out runs/onnx \
--scan-chunk 16 --n-chunks 4 --suite data/decision-v2-pilot # → runs/onnx_parity.json
uv run python -m myna.onnx_export --ckpt runs/myna-v0 --out runs/onnx-wide --questions 2 --q-len 1152 --scan-chunk 64 --report runs/onnx_parity_widest.json
uv run python bench/browser_expected.py # → browser/expected.json
npm ci && npm run selftest # 7 checks, node
npm run g3 # Chrome: bytes, cold, p50, shots
npm run g3 -- --artifact runs/onnx_int8 --out runs/browser_g3_int8.json # the int8 column
uv run python bench/quantize_int8.py # the int8 row above
The second export is the widest real request in the suite — 2 questions × 1,152 tokens
over a 16k state, 1,067 tokens after the state is folded in — and it is the parity cell
that the ≤ 1e-3 bound is actually measured on; the first line's --suite export is
what backs the probabilities row. The two npm run g3 lines are the same harness
pointed at the two artifacts, left and right column of the table above.
Absolute milliseconds from this laptop are a range, not a point, so the witness
files carry uptime's load average next to every number (SPEC §9.23), and the tab's
own memory reading (176 MiB) counts JS-managed heap only — it does not attribute the
54 MiB mounted into the wasm heap, which is why no total-tab memory figure is claimed.
On an Apple silicon Mac, measured
The same checkpoint serves natively through MLX: src/myna/mlx_model.py is a port of
the torch forward over identical weights, so there is no conversion step and no
re-export. One M5, both engines, same tensors, median of 20 reps, per-row minimum
of two committed runs (bench/bench_mlx.py → runs/bench_mlx_int8.md and
runs/bench_mlx_int8_run2.md, load average 4.55 and 3.94 at print). Every cell is
m, including the ratio ranges, which are per-row minima rather than best cases:
| engine | vs PyTorch on MPS, 128 → 16,384-token state | artifact on disk |
|---|---|---|
| MLX fp32 | 1.76–2.22× | 55.13 MiB |
| MLX int8, group 64 | 1.79–2.41× | 17.40 MiB |
int8 is 3.17× smaller and not faster — the sign of the int8 − fp32 difference flips
between the two runs on 4 of the 7 state lengths, and fp32's own run-to-run spread
reaches 15.2% on the widest row. bench/diag_mlx_int8_gem.py prices that at the kernel:
5 of the 6 linear shapes this model actually uses are slower quantised (0.43–1.06×
fp32/int8) even though int8 reads 3.6× fewer weight bytes, because the quantised pass
gets 10–37 GB/s of weight read against fp32's 55–257 on the same box. And it is not free
in agreement — over 20 real calibration.jsonl questions the port moves option
probabilities 1.20e-03 while quantisation moves them 9.24e-03 and changes one argmax, on
a question whose top two options sat 0.0008 apart. So fp32 is what ships on this path
too, and the int8 artifact stays in the tree for whoever needs the bytes for a reason
other than speed (SPEC §9.29).
uv run python -m bench.bench_mlx --ckpt runs/myna-v0 --reps 20 --drift-rows 12 --out runs/bench_mlx_int8.md
uv run python -m bench.bench_mlx --ckpt runs/myna-v0 --reps 3 --lengths 128 --drift-rows 12 --keep trunk.tok.weight .gate.weight --fp32-out runs/myna-v0-mlx-fp32-keepgate --int8-out runs/myna-v0-mlx-int8-keepgate --out runs/bench_mlx_int8_keepgate.md
uv run python bench/diag_mlx_int8_gem.py --iters 200 --trials 7 --out runs/mlx_int8_gem_probe.md
The first line is both ratio tables (runs/bench_mlx_int8.md and its _run2 twin are
two runs of it, and runs/bench_mlx_int8.json is the n_params / file_bytes witness
the architecture table quotes). The second keeps trunk.tok.weight and .gate.weight
in fp32 — the one row of the six that flips sign when they are, which is why it is run
separately rather than folded into the first. The third prices the claim at the kernel.
Trained v0 — accuracy on held-out data
9,000 steps on the synthetic typed-decisions corpus, batch 32, MPS, one M5 — the
log's own elapsed counter reads 15,693 s (4.4 h) at step 8999 — and dev and
test use noun pools the trainer never showed the model. Every cell below is m,
copied from runs/train-v0.log's final === test === block; the overall figures
are the macro over those nine rows, which is also the only way to average them
honestly from what the log prints:
test 0.952 · dev 0.960 (the dev-mid probe line prints 0.9682 at steps 7500, 8000 and 8500 —
on a prefix subset of that same dev split, not on all of it — and an earlier draft of this section
quoted that as the dev overall next to a test figure of 0.951 that no committed artifact prints —
SPEC.md §9.30).
| workflow / question | type | test acc | Brier | ECE |
|---|---|---|---|---|
| incident/page_oncall | noul | 0.946 | 0.046 | 0.026 |
| incident/severity | score | 0.958 | — | 0.019 |
| incident/team | choice | 0.960 | — | 0.020 |
| moderation/action | score | 0.974 | — | 0.012 |
| moderation/category | choice | 0.948 | — | 0.027 |
| moderation/repeat_appeal | noul | 0.946 | 0.043 | 0.037 |
| support/churn_risk | noul | 0.963 | 0.028 | 0.016 |
| support/department | choice | 0.947 | — | 0.029 |
| support/urgency | score | 0.928 | — | 0.042 |
Generalization is real, not memorization: the dev→test gap is 0.76 of a point
(0.9599 − 0.9523) on fully disjoint nouns. Test ECE runs 0.0115–0.0415 with the
single refit temperature (3.0) that runs/train-v0.log prints. A short RLCD
pass — 800 steps maximizing the Brier proper score under a KL leash to the
supervised model — then halved it: mean ECE 0.0191 → 0.0076, mean Brier 0.0258
→ 0.0232, dev accuracy 0.9626 → 0.9657 (runs/rlcd_v0.log, every one of the 9
questions improved or held). Note what that dev accuracy is: rlcd.py
regenerates the dev split at 600 rows per workflow, so 0.9626 is a different
dev draw than the 0.9599 above — the published claim is the before/after pair
inside one draw, not either level. The policy here is the
reported distribution, so the score is optimized differentiably, no REINFORCE
(PLAN.md decision 10 has the post-mortem of trying it the generative way).
These two are the only tables here whose reproduction is a training run, so
bench/reproduce.py --run refuses them and runs/train-v0.log / runs/rlcd_v0.log are
the witnesses instead:
uv run python -m myna.train --steps 9000 --batch 32 --device mps --out runs/myna-v0
uv run python -m myna.rlcd --ckpt runs/myna-v0 --steps 800 --out runs/myna-v0-rlcd
Knowing when not to answer — risk / coverage
A System-1 decider that never says "not me" is not a fast model, it is an unauditable one. So the engine can be built with a floor under it, and the measurement that matters is the trade the floor buys:
from myna.engine import Myna
myna = Myna("runs/myna-v0", abstain_below=0.693) # the floor the curve below picks
out = myna.observe(
"China Invokes Deng to Send Tough Taiwan Message BEIJING (Reuters) - China invoked late "
"leader Deng Xiaoping on Saturday in its campaign to recover Taiwan, lauding his proposal "
"to recover the island by peaceful means, but warning against any move toward independence."
).ask({"topic": {"type": "choice", "instructions": "What is the topic of this article?",
"criteria": {"world": "world news", "sports": "games, athletes, teams",
"business": "companies, markets, economy",
"scitech": "research, gadgets, software, space"}}}})
a = out["answers"]["topic"]
The gold label for this row of calibration.jsonl is world, at 0.001. The engine was
about to answer sports, and the floor took the decision away:
a["choice"] -> None out["policy"] -> {'abstain_below': 0.693, 'abstained': ['topic']}
a["abstain"] -> True
a["reason"] -> 'sports at p=0.602, 0.341 ahead of the runner-up, under the 0.693 floor'
a["probabilities"] -> {'world': 0.001, 'sports': 0.602, 'business': 0.136, 'scitech': 0.261}
probabilities rides along because the answer was withheld: it is what a fallback re-ranks
and what the curve below is sorted by. The withheld field is None on purpose — a caller
that ignores abstain gets an error at the seam instead of a confident wrong label.
Refusing costs the decision, not the numbers: noul keeps reporting raw
p(Yes) (the Brier score needs it), every answer keeps its confidence and its
runner-up margin, and ask() echoes the floor in force plus which questions it
withdrew. myna.serve --abstain-below 0.693 puts the same policy behind HTTP, and
/v1/health names it.
The curve below is every question in calibration.jsonl (448 rows, 568 questions),
ranked by the probability of the side committed to — every cell m, from
runs/risk_coverage.md. floor is the value you would
hand the engine, and the run re-runs the engine at that floor to check it abstains
on exactly the rows the curve withheld — 227 against 227 at the 0.693 floor.
| coverage | questions answered | accuracy | risk | floor |
|---|---|---|---|---|
| 1.00 | 568 | 0.349 | 0.651 | 0.074 |
| 0.90 | 511 | 0.376 | 0.624 | 0.444 |
| 0.80 | 454 | 0.374 | 0.626 | 0.547 |
| 0.70 | 398 | 0.392 | 0.608 | 0.622 |
| 0.60 | 341 | 0.413 | 0.587 | 0.693 |
| 0.50 | 284 | 0.426 | 0.574 | 0.772 |
| 0.40 | 227 | 0.445 | 0.555 | 0.851 |
| 0.30 | 170 | 0.465 | 0.535 | 0.935 |
| 0.20 | 114 | 0.535 | 0.465 | 0.974 |
| 0.10 | 57 | 0.596 | 0.404 | 0.994 |
Read the shape, not the level. Accuracy climbs +0.247 from full coverage to the
top decile, so this model's confidence ranks its own answers — and it does that on
v0, a checkpoint trained on synthetic data that scores near the guessing floor on
this corpus (the agnews row above is one of them: world at 0.001 against a withheld
sports at 0.602). That is the harness proving it measures something; it is not
the gate. G5 wants ≥ 0.95 accuracy at ≥ 60% coverage on banking77 + dbpedia14 + trec,
and on v0 no rung reaches 0.95 (max 0.596, at 10% coverage) with those three sources
at 0.000 / 0.025 / 0.150 — so the pass is gated on the real-trained checkpoint
(SPEC.md §2.2, §9.24).
bench/risk_coverage.py prints it, tests/test_risk_coverage.py checks it against
a synthetic oracle whose answer the test knows in advance, and
bench/mutation_p5.py (49 mutations, all caught) checks that each published figure
moves when — and only when — the claim behind it stops holding. Reproduce:
uv run python -m bench.risk_coverage --ckpt runs/myna-v0 --out runs/risk_coverage.md
Every table, and the command that made it
SPEC §9.30 buried eight headline cells that had been copied out of an artifact into
prose and then copied again. This is the repair pointed the other way: one line per
published table, the command that regenerates it, and the file its numbers came from.
bench/reproduce.py holds the pairs, and either of
make repro # or:
uv run python bench/reproduce.py --check # every row, or it says which one drifted
proves, for each row, that its witness is still committed, still contains the figure the
docs quote from it, and that the prose around the table — not this index, which is this
file's own registry echoed back and is cut out before matching — still prints the
command. The fourth assertion asks the tool rather than the file: every row whose command
names a python target gets --help from it, and each flag the row publishes has to be in
the answer, because a renamed flag leaves the other three untouched (§9.43).
The registry's size is counted out of ROWS by bench/gates.py and read back out of
SPEC §2.2 — the gate table's one hand-copied row — so make gates reds on a stale count
instead of printing it (§9.44).
A third command guards something no row covers: make secrets
(bench/scan_secrets.py) walks every blob in the object database, reachable or not,
against nine credential shapes, and prints the blobs it scanned beside the blobs the
database reports so a mismatch exits non-zero. That denominator is the whole design —
the first version of this check was a one-off command that mis-parsed its own input,
walked nothing, and published "845 blobs, 0 matches" into SPEC (§5 P0).
A finding is then classified, because two findings are not the same fact: one in a blob
reachable from a ref is in the set a push transfers and fails the command; one in an
unreachable blob — an amended commit's leftover, or the tool's own earlier drafts — is
printed as local debt and does not. tests/test_scan_secrets.py runs all three arms of
that rule against a throwaway repo, and .github/workflows/ci.yml runs make secrets
on every push — so the sentence "no credential is in this repository's history" is a
command CI executes rather than a claim this file remembers.
tests/test_reproduce.py (33 tests) pins the block below to the registry, so a
row cannot appear here without being added there, and
bench/mutation_reproduce.py breaks the checker one promise
at a time — a witness that stopped existing, a figure the artifact no longer prints, an
index that counts as its own evidence, a flag check that stopped asking — and requires the
tests to go red: 29/29 all caught, in runs/mutation_reproduce.log. Rows tagged
external-* need something this repo does not carry (a laya checkout, Google Chrome);
the gated-kaggle row has no witness on purpose — that figure does not exist yet,
and no local run may invent it. make repro-show ROW=ID prints what a row would do and
make repro-run ROW=ID does it, overwriting a committed witness the README quotes —
hence the two-step.
The release-gate table at the top of this file is deliberately not a row here. Its command is
make gates # or:
uv run python bench/gates.py --check # every verdict, or it says which one lost its witness
and what that proves is different in kind: bench/gates.py imports the registry above
rather than restating it, so a verdict cannot be greener than the artifact under it, and it
re-reads one named field from a committed JSON file per gate (g5.pass,
baseline.readable, inference_only, meta.myna_dtype). But the table itself is
generated from the same declarations it would check, and §9.31 says what a checker
generates is not evidence — so the evidence is SPEC.md §2.2's status column and
the files it cites, which is exactly what --check reads.
# README architecture table · SPEC §2.1 (params, weights on disk) [here]
uv run python -m bench.bench_mlx --ckpt runs/myna-v0 --reps 20 --drift-rows 12 --out runs/bench_mlx_int8.md
# README 'Measured on an Apple M5' · SPEC §2.1 G2 [external-laya]
PYTHONPATH="<laya checkout>" .venv/bin/python -m bench.bench_latency_matched
# README 'Trained v0' · SPEC §2.1 G6 [retrain]
uv run python -m myna.train --steps 9000 --batch 32 --device mps --out runs/myna-v0
# PLAN v1 · TODO 4 (RLCD scoring pass) [retrain]
uv run python -m myna.rlcd --ckpt runs/myna-v0 --steps 800 --out runs/myna-v0-rlcd
# README abstention table · SPEC §2.2 G5 [here]
uv run python -m bench.risk_coverage --ckpt runs/myna-v0 --out runs/risk_coverage.md
# SPEC §2.2 G4 · README context column [here]
uv run python bench/eval_needle.py --ckpt runs/myna-v0 --device cpu --n 16 --seed 0 --lengths 128 1024 4096 8192 16384
# SPEC §2.1 G1 competitor row · README real-corpus table [external-laya]
uv run python bench/eval_laya_real.py --split test --n-per-source 40 --seed 0 --out runs/laya_decision_v2_test.json
# SPEC §2.2 G3 (torch↔onnxruntime) · README browser section [here]
uv run python -m myna.onnx_export --ckpt runs/myna-v0 --out runs/onnx --scan-chunk 16 --n-chunks 4 --suite data/decision-v2-pilot
# SPEC §5 P6 6b (the 1,067-token request) [here]
uv run python -m myna.onnx_export --ckpt runs/myna-v0 --out runs/onnx-wide --questions 2 --q-len 1152 --scan-chunk 64 --report runs/onnx_parity_widest.json
# SPEC §5 P6 (the node gate that backs every browser cell) [external-chrome]
npm run selftest
# README in-tab table, left column · SPEC §2.2 G3 [external-chrome]
npm run g3
# README in-tab table, right column [external-chrome]
npm run g3 -- --artifact runs/onnx_int8 --out runs/browser_g3_int8.json
# SPEC §5 P6 (the int8 attempt, and the node gate going red) [here]
uv run python bench/quantize_int8.py
# README 'On an Apple silicon Mac' · SPEC §5 P6 6c [here]
uv run python -m bench.bench_mlx --ckpt runs/myna-v0 --reps 20 --drift-rows 12 --out runs/bench_mlx_int8.md
# SPEC §5 P6 6c (the one row that flips when the gates are kept fp32) [here]
uv run python -m bench.bench_mlx --ckpt runs/myna-v0 --reps 3 --lengths 128 --drift-rows 12 --keep trunk.tok.weight .gate.weight --fp32-out runs/myna-v0-mlx-fp32-keepgate --int8-out runs/myna-v0-mlx-int8-keepgate --out runs/bench_mlx_int8_keepgate.md
# README 'and it prices that at the kernel' · SPEC §5 P6 6c [here]
uv run python bench/diag_mlx_int8_gem.py --iters 200 --trials 7 --out runs/mlx_int8_gem_probe.md
# SPEC §4.2 (the majority-label floors the accuracy gates are set against) [here]
uv run pytest tests/test_report.py -q
# SPEC §5 P3 3c (the Kaggle image is python 3.12/3.11, this box is 3.13) [here]
uv run python bench/check_python311.py
# README upstream table, control row · SPEC §8's disclosure [here]
uv run python bench/eval_scratch.py --split test --seeds 0 1 --out runs/scratch_decision_v2_test.md --metrics-out runs/scratch_metrics_test.json
# README upstream table, the gap line · SPEC §8 [here]
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/scratch_metrics_test.json --laya runs/laya_decision_v2_test.json --out runs/report_scratch_vs_laya.json
# README upstream table's disclosure row · SPEC §5 P1, §9.23 [here]
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/myna-v1-rich/metrics.json --laya runs/laya_decision_v2_test.json --out runs/report_void_vs_laya.json
# SPEC §2.1 G1 myna row · TODO 3i [gated-kaggle]
# no local command: the run this row named is now committed as `v1b-kaggle-macro`, so G1's verdict no longer rests here.
# SPEC §5 P9 9e (the `ce` vs `emd` verdict) · §9.35, §9.36 [retrain]
python bench/ordinal_ab.py --ab-dir /tmp/ab2
# SPEC §2.1 G1 myna row · §5 P8 · KAGGLE_LAUNCH_INSTRUCTIONS.md [here]
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/v1b_kaggle_3600b.metrics.json --laya runs/laya_decision_v2_test.json --out runs/v1b_kaggle_3600b.report.json
# SPEC §5 P10 10c (the fresh `off` control) · §9.53(iii) [here]
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/antiprior_off_s0.metrics.json --laya runs/laya_decision_v2_test.json --out runs/antiprior_off_s0.report.json
# SPEC §5 P10 10c (Tier 0 over the `off` control) · §9.55(iii) [retrain]
uv run python bench/diag_question_ablation.py --run-dir runs/antiprior_off_s0 --metrics runs/antiprior_off_s0.metrics.json --out runs/tier0_antiprior_off_s0.json
# SPEC §5 P10 10c (the `on` arm, dose-matched) · §9.56 [here]
uv run python -m myna.report --suite data/decision-v2-pilot --split test --metrics runs/antiprior_on_s0c.metrics.json --laya runs/laya_decision_v2_test.json --out runs/antiprior_on_s0c.report.json
# SPEC §5 P10 10c (Tier 0 over the `on` arm) · §9.56 [retrain]
uv run python bench/diag_question_ablation.py --run-dir runs/antiprior_on_s0c --metrics runs/antiprior_on_s0c.metrics.json --out runs/tier0_antiprior_on_s0c.json
# SPEC §9.41 (the dbpedia14 dev→test delta) · TODO 9h [here]
uv run python -m myna.report --split dev --metrics runs/v1b_kaggle_3600b.metrics.json --out runs/v1b_kaggle_3600b.dev.report.json > runs/v1b_kaggle_3600b.dev.report.log
# SPEC §5 P9 9d · TODO 9d [here]
uv run python -m bench.scope_pricing
# KAGGLE_LAUNCH_INSTRUCTIONS.md cost table · kaggle/campaign.py [here]
grep -E "^step" runs/v1b_kaggle_3600b.train.log | tail -1
# SPEC §2.1 G1 · §9.38 · KAGGLE_LAUNCH_INSTRUCTIONS.md dose lane [here]
grep -E "dev-mid acc" runs/v1b_kaggle_3600b.train.log | uniq | tail -5
# SPEC §5 P10 · `campaign.py`'s P10 pair · TODO 10 [here]
uv run python bench/anti_prior_audit.py --compare --out runs/anti_prior_audit.json
# SPEC §5 P10 9l (§9.48) — Tier 0 input ablation [retrain]
uv run python bench/diag_question_ablation.py --run-dir runs/v1b-kaggle-3600b --out runs/diag_question_ablation.json
Quickstart
uv sync --extra browser
uv run python -m myna.train # 4,000 steps by default; v0's 9,000 took 15,693 s on an M5
uv run pytest # equivalence + isolation proofs
--extra browser (onnx, onnxruntime, onnxscript) is part of the test install, not an optional
add-on: the registry asks bench/quantize_int8.py --help for its int8-quantize row and that module
imports onnx at module scope, so a plain uv sync leaves 5 tests red — the row, gate G7's proof
that every published row binds to a live command, and the four test_gates.py arms that read it.
Those 5 are what the runner printed on its first real collection pass (13 failed, 520 passed, 20
skipped, §9.52); the next run installed the extra and went green — 545 passed, 8 skipped, 0 failed
in 568.96 s, the first green this repository has recorded, and CI installs --extra browser for the
same reason this file does.
And a fresh clone is still missing the weights, which costs another 5. --extra browser alone
does not make uv run pytest green: five tests load runs/myna-v0/model.pt, runs/* is ignored, and
.gitignore keeps 57 MB of *.pt out of git — fetch the two Release assets first (the block under
"Weights are not in the tree", or gh release download v0-checkpoint --dir runs/myna-v0) and both
classes go away.
The 11 MLX tests are the remaining skip: uv run pytest -rs names them, and --extra mlx includes
them on Apple silicon only. Off macOS that extra is a trap worth naming, because PyPI's Linux
mlx wheel installs the bindings without libmlx.so, so the package is present and unloadable at
once (uv run python -c "import mlx.core" is the one command that shows it). The four MLX registry
rows report the same limit as a note rather than a failure. Both facts are CI-measured and in §9.51,
which is the entry for the tick this repo's Actions tab had never once gone green.
Weights are not in the tree — .gitignore keeps *.pt out, and G7's witness is the committed
JSON, not a binary. The v0 checkpoint that ~20 registry rows read as --ckpt runs/myna-v0 is
therefore a Release asset, with its
sha256 and the re-run that ties it to runs/risk_coverage.json on the page. The V1-B weights behind
the 0.4893 macro are also there now — v1b-checkpoint,
recovered on 2026-10-01 from the Kaggle notebook's Output archive after §9.50 had recorded them as
destroyed with /private/tmp/kgwork. The page carries the three hashes that tie those bytes to the
committed Tier 0 witness (model 0e73a8f07edc, tokenizer 0252c24627ee, and a metrics.json that
cmps byte-identical to runs/v1b_kaggle_3600b.metrics.json), which is why "recovered" is a
measurement here and not a hope. §9.53 is the entry. The off control of the P10 pair ships the same
way — antiprior_off_s0-weights,
model.pt 67,733,781 B at sha256 30f0fa937e57e8ce…, downloaded back and re-hashed on 2026-10-01 —
and its title says 3,314 of 3,600 updates, because the tag previously claimed the requested dose as
if it were the achieved one (§9.54). That checkpoint is now ablated too, and what it says is the thing
the accuracy work needed before spending another GPU hour: the control's +0.0445 over V1-B (exactly
0.0445463372) arrives with less prompt-selectivity, not more — its blank-instruction macro rises
further (+0.0974) than its
as-scored one (+0.0445), and a fourth cell (agnews/is_business) has become a constant emitter that
prints its own 0.698 floor. §9.55(iii) is the entry and tier0-off-control is the row. The stopped
on arm's snapshots are labelled by step, because size does not identify them: the
antiprior_on_s0-wip asset is the ≤step-250 bytes (203,229,625 B, sha256 8ed38b240bd843bf…,
downloaded and re-hashed), while this box's runs/antiprior_on_s0/model_last.pt is the step-500 save —
same byte count, sha256 3b45dd6a4ae65e77… — and it is gitignored, so §9.55(ii) keeps that as an open
durability gap rather than a covered one. The on arm has since run to completion on this box:
continued from that step-500 save through the canonical launcher with --resume, ending at
"last_step": 3314 of "steps_requested": 3315 with "stopped": null, so the pair is one machine and
one dose and the residue left is the re-seeded data RNG a continuation cannot avoid. The coverage
asymmetry this README used to report — 176,003 paraphrase draws against the control's 207,411 over the
same 17,112 sets, "15.2% fewer per update" — was a denominator, not a dose: the continued process ran
2,814 updates, so the two arms reach 62.55 and 62.57 draws per executed update, and a CPU replay of
both drawers lands on both counters exactly (§9.58). The judgement §9.56(vii) fixed before the run
printed: test macro 0.4784612299508689 against 0.5338735348381732, −0.0554 from the flag alone, and
0 constant emitters where the control has 4, so the shortcut is gone and nothing readable arrived in
its place. antiprior-on-macro and tier0-on-arm are the two rows; the weights are on the
antiprior_on_s0c-weights Release and re-hash identical to the trained files.
mkdir -p runs/myna-v0
curl -L -o runs/myna-v0/model.pt https://github.com/aashish254/myna/releases/download/v0-checkpoint/model.pt
curl -L -o runs/myna-v0/tokenizer.json https://github.com/aashish254/myna/releases/download/v0-checkpoint/tokenizer.json
make repro # 34/34 rows hold here, with weights to read
The V1-B weights go where bench/diag_question_ablation.py already looks by default, so Tier 0
re-runs on the recovered checkpoint with no flag at all:
mkdir -p runs/v1b-kaggle-3600b
curl -L -o runs/v1b-kaggle-3600b/model.pt https://github.com/aashish254/myna/releases/download/v1b-checkpoint/model.pt
curl -L -o runs/v1b-kaggle-3600b/tokenizer.json https://github.com/aashish254/myna/releases/download/v1b-checkpoint/tokenizer.json
uv run python bench/diag_question_ablation.py # guard green: 716 committed keys, macro 0.4893271976
from myna import Myna
myna = Myna("runs/myna-v0")
obs = myna.observe("Hi, we were billed twice for March. Please refund the duplicate today or we will cancel our plan.")
answers = obs.ask({
"department": {"type": "choice", "instructions": "Which department should handle this?",
"criteria": {"billing": "invoices, payments, refunds",
"technical": "bugs, outages, system errors"}},
"urgency": {"type": "score", "instructions": "How urgent is this?",
"criteria": ["not urgent", "soon", "blocking", "drop everything"]},
"churn_risk": {"type": "noul", "instructions": "Does the writer threaten to cancel?"},
})["answers"]
# the thread continues — only the new message is encoded
obs2 = obs.append("And the second charge is still showing on my card this morning.")
# the observation itself is portable: 576 KiB of state, resume it tomorrow
obs2.save_state("session.pt") # ... myna.restore("session.pt")
Serve API
uv run --extra serve python -m myna.serve --ckpt runs/myna-v0 --port 8080
# --abstain-below 0.693 runs the server with a floor under its answers; /v1/health
# reports the value in force, so a deployment cannot forget its own policy
POST /v1/predict |
{state, questions} → answers (laya Router.predict contract) |
POST /v1/sessions |
{state} → session_id — the state is scanned once, here |
POST /v1/sessions/{id}/append |
{text} → only the delta is scanned |
POST /v1/sessions/{id}/ask |
{questions} → answers; flat cost as the session grows |
DELETE /v1/sessions/{id} |
release the 576 KiB and be done |
What v0 is and isn't
v0 trains and evaluates on a synthetic typed-decisions corpus (three
workflows: ticket triage, incident triage, content moderation) with disjoint
train/dev/test vocabulary. It proves the architecture learns and streams;
it does not yet claim parity with laya/kev on their frozen suites —
SPEC.md's gates split that into what is settled and what is not.
Speed is measured on one box against laya's own engine and clears its gate
(G2, §2.2); accuracy on the real kev decision-v2 corpus (G1) and the
usable-confidence gate (G5, the section above) both wait on a checkpoint
trained on that corpus, which runs on Kaggle rather than here. The full
next-generation research design — free-text questions, state algebra (merge/branch/diff
of cached states), O(window) mid-state edits, unbounded two-timescale memory,
expected-utility decisions, 15 MB on-device model — is specified with
acceptance criteria in V2_SPEC.md.
The uncertainty has somewhere to go: myna.fallback holds a Decider seam and a
registry, so Fallback(myna, laya) re-asks the secondary only the questions the
fast engine refused, and every answer carries the name of the engine that committed
to it. No single model is load-bearing on that path, and the routing is auditable one
decision at a time rather than reconstructed from logs.
Honest limits: word-order-heavy tasks can be partly solved bag-of-cues style; mid-state edits (not appends) fall back to a full re-encode — linear in the state's length asymptotically, but with a quadratic factor inside each 256-token chunk, so short states are cheaper than long ones (§9.22); the question branch's fixed cost (10.1 ms fitted, 14.5–17.4 ms to answer one question regardless of state length) is kernel work, not architecture work; and abstention is a routing feature, not an accuracy one — it can decline to answer, it cannot answer a question the checkpoint never learned (§9.24).
Apache-2.0 · research log in PLAN.md · questions to aashish254
Metadata
Release files for myna-engine 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| myna_engine-0.1.0.tar.gz | 131.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| myna_engine-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 253.3 kB
Release files / myna_engine-0.1.0.tar.gz
| Download URL | myna_engine-0.1.0.tar.gz |
|---|---|
| Size | 131.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
db42620f37e613fd186e04eae0739932392938f377a8c1e253f27f636ac7841f
|
|
BLAKE2b-256 checksum How to use checksums |
64f4bad4b437330424ae51e6735f56be0366aef5dfc5a274818ef24bf1cff8e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / myna_engine-0.1.0-py3-none-any.whl
| Download URL | myna_engine-0.1.0-py3-none-any.whl |
|---|---|
| Size | 121.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
247fb104789cbc6f3c26ad4df8d4a608ec53c78bc39c979c83f25bc7dd634ff3
|
|
BLAKE2b-256 checksum How to use checksums |
a1558b0837ee9ee1cf6de0238e38993ea552579a14cafc21c06b237ef58f2681
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|