Skip to content
MrJev

Laya for Node

Runs Laya, an open Jev-compatible System One model, from Node.js and TypeScript through ONNX Runtime.

View on GitHub →

Hands-on review

Runs the open Laya model through ONNX from TypeScript, with the official request shape. Answers arrive in under a second on a plain CPU.

Good for

  • A typed decision model with no API, no key and no per-call cost
  • Two runtime dependencies and the same request shape as the official SDK
  • Air-gapped use, once the bundle is on disk and you pass `modelDir`

Watch out for

  • A 1.6 GB download, and the default revision is `main` with no content check
  • The offline-with-a-warm-cache bug is fixed in 0.1.2 — take that version
  • It is a client for someone else's model; the weights are Convai's

Tested Sep 21, 2026 at eebf3e1 · re · Node 24 in Docker on 4 CPU cores, no GPU; its tests, the bundled example, and offline runs with the network disabled, before and after the fix

How we reviewed this: we installed it in Docker on Node 24 with four CPU cores and no GPU, ran its tests and the bundled example, measured a cold and a warm run, then cut the network to see what still works. Nothing here talks to TypeSafe, so none of our usual restrictions on publishing numbers apply — the timings below are ours, for Laya.

What it is

Laya is an open System One decision model from Convai Innovations: a ModernBERT encoder with a decision head, trained to answer typed questions about a state in one pass. @receptron/laya runs it from Node through ONNX Runtime, so there is no Python, no PyTorch and no server.

The request shape is the official one:

const result = await laya.systemOne(state, {
  department: { type: "choice", instructions: "Which team should handle this ticket?",
                criteria: { billing: "payments, refunds, invoices", support: "product help and bugs", … } },
  urgency:    { type: "score",  instructions: "How urgent is this ticket?",
                criteria: ["not urgent", "somewhat urgent", "urgent", "critical"] },
  churn_risk: { type: "noul",   instructions: "Is the customer likely to cancel or dispute?" },
});
result.answers.department.choice;   // "billing"
result.answers.urgency.score;       // 1.39
result.answers.churn_risk.noul;     // 0.099

Choice, score and noul all come back with probabilities, plus an rl_agent.act_probability field the checkpoint carries. Code written against the TypeSafe SDK reads the same way.

On our box — four cores, fp32, no GPU — a warm call on the sample ticket took 729 ms. That is our measurement of Laya on modest hardware, not a benchmark of anything else.

What it costs to install

The bundle is a 3.8 MB graph and a 1.69 GB weights file, pulled from receptron/laya-onnx on Hugging Face into a local cache on first use. After that it is local: no key, no request, no per-call price. Two runtime dependencies, onnxruntime-node and the Hugging Face tokenizer.

The export itself is in the repository — export/export_onnx.py turns the upstream checkpoint into the ONNX bundle and prints the largest logit difference against PyTorch, which the README puts at about 1e-5. Being able to rebuild the artefact you are asked to trust is not something we find often.

Two things to know about the download

The default revision is main, and nothing checks the contents. ensureBundle builds a Hugging Face URL from repo and revision, defaulting to the branch tip, and its freshness check compares the local file size to the remote one. A re-upload of the same size would not be noticed, and a changed main is picked up silently the next time the cache is cold. Both options exist — pass revision to pin it — and after watching Von lose its classification head on main mid-review, we would pin it.

Cached was not the same as offline — fixed in 0.1.2. With the bundle already on disk and the network down, the default path used to fail:

[TypeError: fetch failed] { [cause]: Error: getaddrinfo EAI_AGAIN huggingface.co }

The freshness HEAD ran before the cache was used, and its failure was fatal rather than a reason to fall back to the files sitting right there. We reported it with a one-line fix, and that is what went in (cfac045): the HEAD is now .catch(() => null), an unknown size is treated the way a non-OK response already was, and there is a test for the offline case.

We re-ran the same scenario on the published 0.1.2 — the same populated cache, a container with no network at all, and the default Laya.load({}) rather than the modelDir escape hatch:

load 3713 ms, answer 452 ms
{"model":"laya","answers":{"department":{"choice":"billing",…},"urgent":{"noul":0.0956,…}}}

A file that is genuinely missing still has to be downloaded and still reports the failure, which is the right half to keep.

Engineering

Created 19 September, fourteen commits, MIT, published as @receptron/laya 0.1.2, CI green. Ten tests, nine passing and one skipped. The weights stay Convai’s, under Apache-2.0, and the README says so in the licensing section rather than leaving it implied.

There is no telemetry and nothing to configure: the only network access in the package is the model download, and HF_TOKEN is read if you point it at a private repo.

Verdict

If you want typed decisions without an account, this is the least ceremonious way to get them in a Node codebase: two dependencies, one method, the same shapes your SDK code already uses. Budget the 1.6 GB and a warm-up, and expect a few hundred milliseconds per call on a CPU rather than the tens of milliseconds a hosted service quotes.

Pin revision, take 0.1.2 or later, and a warm cache now survives losing the network.

For other local options see SemIf, Von and the rest of the open models category.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 21, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Open Models & Reproductions

Laya

★ 21k▲ 6.4k

NandhaKishorM/laya

Non-autoregressive decision engine over 100+ languages: three checkpoints and a router that detects the script and dispatches per request. Its benchmarks end with a limits section naming the datasets it does not generalise to and the headline figure that came from a training split.

PythonReviewed

kev

★ 6.3k▲ 5.9k

jaredpalmer/kev

Jev-style decision models from 0.5B to 8B, built as LoRA adapters on Qwen and served behind a Jev-compatible /v1/systemone API.

PythonReviewed

SemIf

★ 4.1k▲ 2.3k

TheoLeeCJ/SemIf-OpenJev

Jev-style decisions from a frozen 4B model on a single RTX 3090, with a browser demo. Formerly OpenJev.

PythonReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.