The hub

JuL is model-agnostic. Your code asks typed questions through one API; behind it you plug the model, the runtime and the provider that fit your constraints, and change them without touching that code.

                         ┌──────────────────────────────────────────┐
  Python  ─ TypeSafeClient│                                          │
  shell   ─ jul ask / run │   one API: Choice · Noul · Score         │
  any lang ─ jul serve    │   one answer shape: Jev's System One     │
  Lambda  ─ Bundle        │                                          │
                         └───────────────────┬──────────────────────┘
                                             │
           ┌──────────────┬──────────────────┼──────────────────┬──────────────────┐
           ▼              ▼                  ▼                  ▼                  ▼
     embedding LLMs   decision models     encoders         embeddings APIs   System One servers
     WeMM, F2LLM,     jul-decision-*,     e5, BERT,        Ollama, OpenAI,   Jev, Clef (Cloudflare),
     Qwen3-Emb,       minicpm5-2b-        XLM-R            Mistral, Voyage,  Nimble (Ollama), Kev,
     Harrier, any     decision, CLM-8B                     any OpenAI-       another jul serve
     HF decoder                                            compatible        (escalation tiers)
           │              │                  │                  │                  │
           └──────── mlx · torch · onnx ─────┘                api              HTTP

Five families, four backends, one call. The rest of this page says what each brings and how to pick.

Models you run

Anything on the Hugging Face Hub, or in a local directory, that JuL can load on one of its backends. jul models add fits it once on the development sets (layer, center, temperature) and from then on --model <name> uses it everywhere: Python, CLI, server, bundles.

Family What it is Examples Best for
Embedding LLMs decoders fine-tuned for embeddings, read at a middle layer wemm-4b-4bit (built in), wemm-9b, f2llm-1.7b, qwen3-embedding-0.6b, harrier-0.6b sorting one text into options, zero-shot
Any decoder a general LLM read the same way minicpm5-2b (built in), bitnet-2b, any instruct model trying a model you already use
Decision models trained to answer typed questions, bring their own format in a decision.json jul-decision-minicpm5-2b, Kev checkpoints, Strands Decider questions that read two things together
Letter-readout models LLMs trained to answer with an option letter after their own prompt, read as their runtime reads them JevK5, plumb-4b, Quyet, spark-s1, Decision 3.0 open decision models published with their own runtime
Laya a decision model on its own runtime (laya package): JuL hands it the questions laya, laya:multilingual (Laya) Laya's answers through JuL's API, as a tier or next to the others
Cross models (adapters) LoRA adapters that read the question and the text together, on the same weights jul-decision-wemm-4b (the default), jul-decision-wemm-4b-4bit yes/no and choices (the default), scores (-4bit), one model in memory
Encoders small bidirectional models, read as their sentence embedding e5-small, jul-decision-e5-small milliseconds, CPU only, Lambda
Contrastive heads projection heads on a frozen backbone clm-8b (CLM's heads on Qwen3-8B), your own with --train-heads reusing heads trained elsewhere
jul models add harrier-0.6b --repo majentik/harrier-oss-v1-0.6b-MLX-4bit     # an embedding LLM
jul models add my-model --repo org/Some-Instruct-3B                          # any decoder
jul models add jul-decision-minicpm5-2b --repo usejul/jul-decision-minicpm5-2b-mlx-4bit   # a decision model
jul models add clm-8b --repo Contrastive-LM/CLM-v0.1-8B                       # contrastive heads
jul models add jevk5 --repo alibiserikbay/JevK5                              # a letter-readout model
jul models                                                                   # what you have

What jul models add recognizes by itself: a repo with a decision.json is a decision model (a "method": "letters" one, or the runtime config of JevK5, Quyet or spark-s1, is a letter-readout model), one with a CLM config.json or a contrastive.json gets contrastive heads, one carrying a cross/ folder gets its cross model attached. Everything else is fitted as a vector preset. A Strands Decider archive is converted first (scripts/convert_strands_decider.py); Laya needs no add (--model laya). The 18 models we measured, with their scores: Models.

Backends

The same preset name can be fitted on several backends; the backend decides where it runs.

Backend Install Runs on Reads Use it for
mlx pip install "jul[mlx]" Apple Silicon every reading a Mac, the default there
torch pip install "jul[torch]" CUDA, CPU, MPS every reading Linux, Windows, GPUs, the default elsewhere
onnx pip install "jul[onnx]" CPU, no torch vector, cross (encoders) deploying: Lambda, containers, 225 MB packages
api nothing extra wherever the API is vector only no weights at all, a model you already serve

Pick one with backend= in Python, --backend on the CLI, or JUL_BACKEND for everything. Never picked by default: onnx (it reads a model exported for it) and api (it calls a server). Details: Installation.

Vectors are tied to their weights. MLX presets are 4-bit, PyTorch loads bf16: the same model reads slightly different vectors on each (cosine ~0.95). Contexts, heads and bundles are therefore keyed per backend: a head trained on MLX is never applied to PyTorch vectors. Tune and pack on the backend you deploy on.

Embeddings APIs

When the model already runs somewhere (an Ollama on the box, a hosted provider), JuL reads its embeddings over HTTP instead of loading weights. jul models add fits the center and temperature as for a local model.

--repo Endpoint Key
ollama:MODEL $OLLAMA_HOST or http://localhost:11434 none
openai:MODEL https://api.openai.com/v1 OPENAI_API_KEY
mistral:MODEL https://api.mistral.ai/v1 MISTRAL_API_KEY
voyage:MODEL https://api.voyageai.com/v1 VOYAGE_API_KEY
http(s)://host/v1#MODEL any OpenAI-compatible /embeddings none
jul models add qwen3-emb --repo ollama:qwen3-embedding:0.6b --backend api
jul ask choice "Which team?" -o billing -o technical --state "I was charged twice" --model qwen3-emb --backend api

Trade-offs: one layer (the API's output), no prefix cache, no logits, and with a hosted provider the state leaves the machine. See Models › Embeddings APIs.

System One servers

JuL speaks Jev's HTTP protocol both ways.

As a server, jul serve exposes any of the models above at POST /v1/systemone: any Jev client, SDK or curl, talks to it by changing its base URL. See Serving over HTTP.

As a client, SystemOneHTTP calls any server that speaks it, and Escalation chains them: answer locally, and send only the questions answered below a confidence bar to the next tier.

Tier --escalate-to Key
Jev (TypeSafe) typesafe TYPESAFE_API_KEY
Nimble on Ollama ollama, ollama:MODEL none
Clef on Cloudflare Workers AI cloudflare, cloudflare:clef CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID
Kev, another jul serve, any server its URL --escalate-key-env VAR

The same targets go in jul bench --models (a URL as URL#model, its key in --remote-key-env), to measure them against JuL's models on your own rows before choosing: see Bench on your own data.

from jul import TypeSafeClient, Escalation, SystemOneHTTP

client = Escalation(tiers=[("local", TypeSafeClient()),
                           ("ollama", SystemOneHTTP("http://localhost:11434", model="nimble"))],
                    min_confidence=0.8)
r = client.system_one(state="…", questions={…})
r.escalation["team"]     # {"tried": ["local"], "tier": "local", "confidence": 0.93, "met_bar": True}

See Serving › Escalation and Python API › Escalation.

Choosing

Start from your constraint, not from the model.

Your constraint Start with Why
best accuracy on a laptop, no labels the default, jul-decision-wemm-4b 0.857 zero-shot on Jev's benchmark (Jev 0.753), 55 ms, 2.6 GB on MLX
speed, many options, batch jobs f2llm-1.7b or harrier-0.6b 24 ms / 1 GB, 13 ms / 0.31 GB; tune them with labels
CPU only, serverless jul-decision-e5-small on onnx + autotune 4 ms per text, 90 MB, $0.59 per million in Lambda
yes/no about how two things relate a preset with a cross model (the default on PyTorch) the vectors alone answer those near chance
no GPU, no weights, a model already served an embeddings API nothing to download
local first, but some questions are hard Escalation to Jev, Clef or Nimble only the unsure ones leave
a model you already trust jul models add one command, measured on dev sets

Then measure on a few hundred of your own texts: the benchmark tells you where to start, your data tells you where to stop. Benchmarks has every number and how it was measured.

Edit this page · JuL, Just use Less · Apache-2.0 · Not affiliated with TypeSafe AI or Jev.