Skip to main content

ornotto

ornotto asks a local language model to decide, not to write. You describe a decision (pick one of these options, answer yes or no, place this on a rubric) and get back an answer with a probability for every alternative. Nothing is generated, so nothing has to be parsed, repaired or retried.

It gives three open-source engines one Python API:

  • dohnuts (dohnuts.cpp) runs models trained for this job (decider, kev, Dohnuts) and reads their answer at a trained slot. Its probabilities are temperature-calibrated.
  • pcdServer (pcdServer) runs any chat GGUF and scores only the tokens of the answers you allow. It needs no special model.
  • ollaya (ollaya) is a separate install that runs decision models from its own registry (kev, decider, laya, winnow and others) on ONNX Runtime, llama.cpp or MLX. Each model ships its own calibration.

The book at fontlab.org/ornotto explains how the engines work and which model to pick, and has the benchmarks: 258 methods over 67 queries in 30 languages, twelve chapters with diagrams, and a landing page that summarises the best results.

Install

uv add ornotto                      # or: pip install ornotto
uv add "ornotto[pydantic-ai]"       # with the pydantic-ai integration

Wheels bundle both engines for macOS 14+ on Apple silicon (Metal) and for Linux x86_64 and aarch64 (manylinux_2_28, CPU only: no CUDA or Vulkan). Windows x64 wheels carry dohnuts only: pcdServer does not build on Windows yet. Elsewhere pip installs the Python code without engines: clone with --recursive and run ./build.sh, or put dohnuts-cli and pcd_server on your PATH (or point ORNOTTO_DOHNUTS_BIN and ORNOTTO_PCD_BIN at them).

Models download from Hugging Face on first use into the usual Hugging Face cache. HF_HUB_CACHE moves it.

ornotto never bundles ollaya. Install it with curl -fsSL https://ollaya.dev/install.sh | OLLAYA_INSTALL_DIR=$HOME/.local sh; ornotto looks on PATH, then in ~/.local/bin, or at ORNOTTO_OLLAYA_BIN. ollaya pulls its models into its own store, OLLAYA_MODELS (default ~/.ollaya/models), not the Hugging Face cache.

Decide

import ornotto

ornotto.choose("Build a kern feature for A V W T", ["docs", "python", "fea"]).value
# 'fea'

answer = ornotto.check("Write me a script that renames glyphs", "Does the user want code?")
answer.value, answer.probability
# (True, 0.6)

Several questions about one state go in one call. The state can be text or any JSON value:

decision = ornotto.decide(
    {"message": "Why does this pair look too tight?", "selection": ["A", "V"]},
    {
        "area": ornotto.choice("Which area is this about?", {
            "kerning": "space between a specific pair of glyphs",
            "sidebearings": "space built into each glyph",
        }),
        "urgent": ornotto.yes_no("Is the user blocked?"),
        "skill": ornotto.score("How experienced is the user?", ["beginner", "intermediate", "expert"]),
    },
)
decision.area.value, decision.area.probabilities, decision.skill.value

Each Answer has value, probabilities, probability, confidence and calibrated. calibrated is true for dohnuts and ollaya, whose probabilities are calibrated, and false for pcdServer, whose probabilities are a raw softmax over the allowed tokens. Compare confidences only between answers of the same kind.

Pick a model and engine

fast = ornotto.Decider("decider-0.8b")                    # dohnuts, the default
same_weights = ornotto.Decider("decider-0.8b", engine="pcd")
careful = ornotto.Decider("qwen3.5-4b-hmm")               # a fine-tuned chat model; pcdServer only
mine = ornotto.Decider("~/models/my-model.gguf")          # any chat GGUF, on pcdServer
laya = ornotto.Decider("ollaya-laya-en")                   # a registered ollaya model
any_tag = ornotto.Decider("kev:4b", engine="ollaya")       # any ollaya tag, pulled on first use
remote = ornotto.Decider("decider", url="http://127.0.0.1:8298")   # an engine you started yourself

ornotto models lists the registered models:

name engines family GB licence
decider-0.8b dohnuts, pcd dedicated 0.8 apache-2.0
decider-2b dohnuts, pcd dedicated 2.0 apache-2.0
kev-0.8b dohnuts dedicated 0.8 apache-2.0
dohnuts-0.8b dohnuts dedicated 0.8 cc-by-nc-sa-4.0
qwen3.5-0.8b, qwen3.5-2b, qwen3.5-4b pcd vanilla 0.8, 1.4, 3.0 apache-2.0
qwen3.5-4b-hmm pcd fine-tuned 2.7 apache-2.0
jevk5-4b, openjev-35b-a3b pcd fine-tuned 2.7, 21.2 apache-2.0
ollaya-kev-0.8b, ollaya-decider-0.8b, ollaya-laya-en, ollaya-laya-multilingual, ollaya-nli-modernbert-large, ollaya-gliclass-large, ollaya-von-1.1, ollaya-decision-eos ollaya dedicated 0.7–1.8 apache-2.0
ollaya-winnow-12b, ollaya-clm-8b ollaya dedicated 12.7, 16.5 apache-2.0

openjev-35b-a3b needs about 23 GB of memory, so run it with nothing else loaded. The book also benchmarks models that need their own runtimes: rune, NeoHorse-Jev, Jev-Omni's decision head, lev, leo, imajev, jeb, CLM, semif, and GLiNER2.5-Decide, Julia-1, von and other encoders on MLX, ONNX Runtime, Core ML and Core AI. ornotto does not run those. Of the 258 methods in the book, rune-26b-a4b (Q3_K_M) matches the hosted jev on translated text, 65/67, and NeoHorse-Jev-4B scores 64/67 in both modes.

The engine starts on the first question, on a free loopback port, and stops when Python exits. Every Decider in a process that names the same model, engine and device shares one engine process. ornotto.shutdown() stops them all now.

An ollaya engine is a private ollaya serve on its own port. It holds one model, pulls the tag if the store lacks it, and loads it before the first question. It never touches a daemon you run on ollaya's default port. A state longer than the model's context raises DecisionError instead of being cut; ollaya-laya-en reads only 512 tokens.

Typed decisions

A pydantic model becomes one question per field: a Literal or Enum of strings is a choice, bool is yes or no, and a float with ge=0, le=1 is the probability of yes. The field description is the question.

from typing import Literal
from pydantic import BaseModel, Field

class Triage(BaseModel):
    task: Literal["docs", "python", "fea"] = Field(description="What does the user want?")
    wants_code: bool = Field(description="Does the user want code they can run?")

ornotto.extract(Triage, "Write a Python script that renames every .sc glyph")
# Triage(task='python', wants_code=True)

A typed function with only a docstring becomes a decision, in the style of magentic and promptic. Its docstring is the question, and several arguments become one JSON state:

@ornotto.decision
def route(request: str) -> Literal["docs", "python", "fea"]:
    """Which kind of help does this FontLab user want?"""

route("How do I open the Glyph window?")   # 'docs'

ornotto.classify(text, labels) follows marvin's classify. These are idioms, not integrations: marvin 3 does not currently import against pydantic-ai 2.

pydantic-ai

pydantic-ai's TypeSafeModel turns an agent's output_type into System One questions, the protocol dohnuts speaks. ornotto.pydantic_ai.model() points it at a local engine. For pcdServer, an in-process transport translates each request.

from pydantic_ai import Agent
from ornotto.pydantic_ai import model

agent = Agent(model("decider-0.8b"), output_type=Triage, instructions="Classify a FontLab user's request.")
agent.run_sync("Build a kern feature for A V W T").output

Rubrics (int fields with a description per level), lists of options and nested models work as pydantic-ai documents them for TypeSafeModel.

Command line

ornotto models
ornotto pull decider-0.8b
ornotto choose "Build a kern feature" docs python fea
ornotto check "Rename all .sc glyphs" "Does the user want code?"
ornotto serve qwen3.5-4b-hmm --engine=pcd
ornotto pull ollaya-kev-0.8b                                     # into OLLAYA_MODELS
ornotto choose "Build a kern feature" docs python fea --model=kev:4b --engine=ollaya

Develop

git clone --recursive https://github.com/fontlaborg/ornotto
cd ornotto
./build.sh                # lint, unit tests, sdist and a wheel with both engines compiled in
./docs.sh                 # build the book with the shared FontLab documentation theme
ENGINES=1 ./test.sh       # also the real-engine tests and examples/, one engine at a time (ollaya if installed)
./publish.sh              # tag the next version, let CI build every wheel, upload with uv publish

The engines are git submodules in engines/. The version comes from git tags via hatch-vcs.

The book sources live in src_docs/; GitHub Pages publishes the generated docs/. Typography, themes, navigation, search and keyboard shortcuts come from the shared i.fontlab.com/fltheme26/ assets. Local CSS and JavaScript handle benchmark tables.

Licence

ornotto is Apache-2.0. The wheels contain dohnuts.cpp (Apache-2.0), pcdServer, llama.cpp, cpp-httplib and nlohmann/json (MIT). Their notices are in licenses/. ollaya (Apache-2.0) is installed separately. Model weights keep their own licences, listed above.

Shared FontLab theme integration

This repository is part of the FontLab theme 2026 rollout: ornotto documentation with fontlab editorial chrome. THEME.md documents its source/output boundaries, configuration, publication route, control ownership, shared visual changes and verification. Use the public setup guide and MaterialX starter for new sites.

Metadata

Release files for ornotto 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ornotto 0.1.2
File Size Uploaded Provenance Actions
ornotto-0.1.2.tar.gz 43.5 kB Without provenance Details for ornotto-0.1.2.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for ornotto 0.1.2
File Actions
ornotto-0.1.2-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for ornotto-0.1.2-py3-none-win_amd64.whl
ornotto-0.1.2-py3-none-manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64 Details for ornotto-0.1.2-py3-none-manylinux_2_28_x86_64.whl
ornotto-0.1.2-py3-none-manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.28+ ARM64 Details for ornotto-0.1.2-py3-none-manylinux_2_28_aarch64.whl
ornotto-0.1.2-py3-none-macosx_14_0_arm64.whl Python 3 none macOS 14.0+ ARM64 Details for ornotto-0.1.2-py3-none-macosx_14_0_arm64.whl

Total release size: 19.5 MB

Release files / ornotto-0.1.2.tar.gz

Download URL ornotto-0.1.2.tar.gz
Size 43.5 kB
Tags Source
SHA-256 checksum
How to use checksums
883b5106d3c1aa86cefd923460aa5047eada363fa29645a531ee59f8acf16156
BLAKE2b-256 checksum
How to use checksums
8b24c0ff5b3f804ba6b0070368b4a55202abf7318b5d76a787f85f8c2ef7ef86
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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 / ornotto-0.1.2-py3-none-win_amd64.whl

Download URL ornotto-0.1.2-py3-none-win_amd64.whl
Size 2.3 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
c638dace35e911668c2f9627c291d1977b304fd7d4de7b71b77a772bd9368096
BLAKE2b-256 checksum
How to use checksums
d431cdf09e071773927ee501c1c7243df2f2f8606b8d614429904317ffdde47a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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 / ornotto-0.1.2-py3-none-manylinux_2_28_x86_64.whl

Download URL ornotto-0.1.2-py3-none-manylinux_2_28_x86_64.whl
Size 6.0 MB
Tags Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
e987331470363f644ffea344ee20c4961b941687c55d258717cb57eeb85637e3
BLAKE2b-256 checksum
How to use checksums
157d87fcae1978187166c21fe215093a38436610e6f6822f8949af856c3aa5c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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 / ornotto-0.1.2-py3-none-manylinux_2_28_aarch64.whl

Download URL ornotto-0.1.2-py3-none-manylinux_2_28_aarch64.whl
Size 5.7 MB
Tags Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
a47ad392cda68bb3831373af6497892f30f6376d3b1d70970cd3fb43d9857a95
BLAKE2b-256 checksum
How to use checksums
e09571880fda5d9256692a516433b95dadf77faa7795295c1fe5d9c7d8caec25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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 / ornotto-0.1.2-py3-none-macosx_14_0_arm64.whl

Download URL ornotto-0.1.2-py3-none-macosx_14_0_arm64.whl
Size 5.5 MB
Tags Python 3 macOS 14.0+ ARM64
SHA-256 checksum
How to use checksums
87cf1d2d662844d3352d3c3b1e2e2bbefe77723432aa924f7f9745d7a392103d
BLAKE2b-256 checksum
How to use checksums
ddd853f44e6a27ed69e849b30692bc9dc525854e1debd8642ecbf8bd98740b55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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}

Security for ornotto 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page