Sentiment Analysis demo box
Model: DistilBERT SST-2 (ONNX INT8)
Sentiment analysis reads a piece of text and judges the opinion in it. This model is binary: it says whether a sentence reads as positive or negative, and how confident it is about that.
This demo takes DistilBERT, fine-tuned on SST-2 and quantised to INT8 in ONNX form, and ships it as a signed, self-contained box.
$ scrollcase run .scrollcase/.../*.release.json \
-- "This product is surprisingly easy to use."
Sentiment: POSITIVE
Confidence: 99.9%You define what the box should contain, Scrollcase handles it:
No Python environment to prepare, no model to download at run time, no container. The model and everything it needs are inside the box, which runs it in its own environment.
Try the demo
Build it yourself
The demo repository is almost empty on purpose: you package the model yourself, and its README is the walkthrough. Install the CLI, initialise the workspace, create the scroll, declare the model's pinned files, lock, commit, sign and build. About fifteen minutes, most of it spent waiting on the build.
Open in GitHub Codespaces
What the demo shows
A signed release, verified before execution. The release document commits to the archive by size and SHA-256, and the archive's filename is its content hash. verify checks the signature against a public key you obtain independently of the download.
The model travels inside the box. The ONNX weights, the tokenizer and the config are declared as assets pinned to an immutable upstream commit, each with its size and SHA-256. The build fetches them once and fails if a byte moved. With weights: embed they are packed into the archive, so the box installs and runs air-gapped.
Defence in depth against a stray download. The scroll declares HF_HUB_OFFLINE=1, TRANSFORMERS_OFFLINE=1 and TOKENIZERS_PARALLELISM=false, and those values are signed into the release and override the host environment. This matters in practice: tokenizers pulls huggingface_hub in transitively, so a downloader is present in the environment — the guarantee comes from the entrypoint importing no client and from the signed environment, not from absence.
A locked, audited environment. lock resolves the four conda dependencies into a pixi.lock, and the build installs only from it — nothing is resolved while building. audit derives the licence inventory from that same lock, and the build recomputes it and fails on any difference, so a dependency whose licence changed is caught when it changes rather than at the end of a multi-gigabyte build.
A self-test at two levels. selfTest.imports is the part schema v2 signs, which is why verify --self-test can repeat it later with the box's own interpreter. files and the optional pythonCode block stay builder-only: add pythonCode and the build runs real predictions and refuses to sign a box that answers wrong. Proof of real inference for a box you downloaded is what run gives you.
More than one way to call it. The CLI; the run-box.ts and run_box.py that ship beside the downloaded box; and, in a workspace of your own, the Node, Python and Rust consumer templates init writes under consumer-templates/. Whichever starts it, the two lines above are the whole of stdout — progress, diagnostics and failures go to stderr — so … > verdict.txt is a file with the verdict in it and nothing else.
What you write
Three CPU targets that package the same model agree about everything except the target itself, so the demo is a split scroll:
examples/sentiment-demo/
scroll.json # everything the three targets share
shared/ # entrypoint, model notice, Apache-2.0 text
linux-x86_64-cpu/scroll.json # extends + target + its licence audit path
macos-aarch64-cpu/scroll.json
windows-x86_64-cpu/scroll.jsonThe base carries the identity, the three model files as commit-pinned assets with their sizes and SHA-256, the offline environment, the self-test and the licence files to carry into the box. Each target file is nine lines. A change to the model is one edit, not three.
Beside every target sits its own pixi.toml — four conda dependencies: python, onnxruntime, tokenizers, numpy — because the solved environment is what genuinely differs. lock and audit then write pixi.lock and conda-licenses.json next to it.
No hash is typed by hand anywhere. scrollcase add asset fetches each model file once and records the size and SHA-256 it found; the notices and the entrypoint are pinned, and scrollcase refresh moves those digests after a reviewed change.
The scroll declares weights: embed, a 2 GB RAM floor, and execution as a python-script.
Measured
The box has been built, self-tested, verified and run on all three CPU targets — Linux, macOS and Windows — and a rebuild produces a byte-identical archive. On Apple Silicon the archive is about 192 MiB, almost all of it model. It answers the sentence above POSITIVE at 99.9% confidence, and This was a frustrating and disappointing experience. NEGATIVE at 100.0%.
Scope and limitations
This is a demonstration of packaging, not a sentiment product. The model reads short English sentences and answers POSITIVE or NEGATIVE; input beyond 128 tokens is truncated. Its model card documents biases inherited from the training data, including predictions that differ systematically for sentences mentioning underrepresented populations, and INT8 quantisation does not remove them. Do not use it for decisions about people.
The original checkpoint is Apache-2.0; the community conversion is attributed separately, and every box ships the full licence text next to the model notice.