System requirements

What a machine needs to run vibey and its krypton clients, and how each figure is known. The tables on this page are generated: scripts/minimum_specs.py writes them from the committed evidence record, docs/architecture/evidence/minimum-specs.json, and the text between the markers is never edited by hand. The prose around them is.

How these figures are kept current

The requirements are rolling. A weekly workflow (.github/workflows/minimum-specs.yml) runs python scripts/minimum_specs.py measure on the project's self-hosted macOS runner, and measures the Linux matrix (below) on GitHub-hosted runners of each architecture. It re-derives the requirements, re-renders this page and the research paper's tables, and opens a pull request with the result. Nothing reaches develop except through that pull request and the normal merge train. The configuration it runs from (contexts, models, thresholds, assumptions and the idle rule) is declared in scripts/minimum_specs.toml.

Every figure in the record carries its value and unit, the command or method that produced it, when (UTC), on which host, and under which conditions. Each has one of five statuses, and the tables say which:

Status Meaning
measured A command on the named host produced it.
declared Read from the repository: a constant in the code, a floor in a manifest, or a stated assumption.
derived Arithmetic on other figures. The formula and every input are in the record, and check recomputes it.
stale The weekly measurement could not run. The last good value is kept and shown as stale since the date it was last measured, with the reason. It is never reused silently.
skipped It could not run and there has never been a good value. No number is shown.

A few figures come from a one-off pass that the weekly probes do not repeat, such as the offline network matrix and the real DESIGN call. They are shown with their own date.

The model measurements need a quiet host. They run only when the one-minute load is at or below the declared ceiling, no other client has sent the Ollama server an inference request within the quiet window, and the declared busy check is silent. The run waits a bounded time. If the host is still busy after that, the model figures are recorded as skipped, and the previous values stay in the record, marked stale.

Scope of the evidence. Every measured figure in the hardware, software, network and client tables comes from one machine, named in each table: an Apple M5 Mac with 24 GiB of unified memory. The seed values were measured on 2026-09-29 and 2026-09-30 by three passes (software, network and hardware). The Linux tables come from each distribution's container, named in each table. Anything about other hardware is either derived or listed under Not verified.

Hardware

The sovereign default is one host running vibey, PostgreSQL and Ollama serving gpt-oss:20b. Memory is the binding constraint. vibey asks the model for two context sizes. The conductor (DESIGN and DECOMPOSE) sizes its window to the prompt, up to 8,192 tokens by default. The gptossloop runner (BUILD) assumes 32,768 tokens. Through /v1, though, Ollama loads the model at its own server default, which on a default install is the model's maximum of 131,072. The minimum is sized for the runner's window. The recommended size is sized for the model's maximum, plus room for other applications.

Measured on Mac17,2 · Apple M5 · 24 GiB · macOS 26.6.2 (25G83), aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS; figures dated 2026-09-29 to 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Requirement Minimum Recommended Basis
Memory, Apple Silicon (unified) 24 GB (stale since 2026-09-30: derived from stale input(s): ram.minimum_need_gib) (needs 17.79 GiB (stale since 2026-09-30: derived from stale input(s): ram.model_process.ctx32768, ram.vibey_side_gib)) 32 GB (stale since 2026-09-30: derived from stale input(s): ram.recommended_need_gib) (needs 24.42 GiB (stale since 2026-09-30: derived from stale input(s): ram.model_process.ctx131072, ram.vibey_side_gib)) derived, stale
Memory, 16 GB Mac insufficient (stale since 2026-09-30: derived from stale input(s): ram.design_only_need_gib): DESIGN alone needs 17.15 GiB (stale since 2026-09-30: derived from stale input(s): ram.model_process.ctx8192, ram.vibey_side_gib); the GPU part is -78.0 MiB (stale since 2026-09-30: derived from stale input(s): bench.gpt-oss:20b.ctx8192.device_mib, gpu.metal_limit_16gb_mib) over its limit at 8,192 - derived, stale
GPU memory for gpt-oss:20b 12,339 MiB (stale since 2026-09-30: no accounting line in the log) at 8,192; 12,974 MiB (stale since 2026-09-30: no accounting line in the log) at 32,768 discrete GPU: 16 GB (stale since 2026-09-30: derived from stale input(s): bench.gpt-oss:20b.ctx32768.device_mib) card (not verified on CUDA) measured, stale; derived, stale
Model throughput at 24k depth 10 tok/s generation, 100 tok/s prompt (worst BUILD turn 512 s of 900 s) 25 / 500 tok/s (worst turn 143 s); measured here 18.1 tok/s / 379 tok/s derived; measured
CPU only (no GPU) DESIGN call 462 s (fits: yes); worst BUILD turn 1,422 s (fits: no) use a GPU; CPU only measured 2.8 tok/s generation, 44.5 tok/s prompt derived; measured
Free disk 20 GB (stale since 2026-09-30: derived from stale input(s): disk.minimum_need_gb) (needs 17.8 GB (stale since 2026-09-30: derived from stale input(s): disk.ollama_app_bytes)) 50 GB (stale since 2026-09-30: derived from stale input(s): disk.recommended_need_gb) (needs 45.2 GB (stale since 2026-09-30: derived from stale input(s): disk.minimum_need_gb)) derived, stale
PostgreSQL memory summed RSS peak 57.5 MiB (default shared_buffers suffices) - measured
vibey processes CLI 75.2 MiB (stale since 2026-09-30: no scratch database); worker 276 MiB (stale since 2026-09-30: no scratch database); hub idle 102 MiB (stale since 2026-09-30: no scratch database) krypton launcher + hub idle 138 MiB (stale since 2026-09-30: no scratch database) measured, stale

What the rows mean in practice:

  • A 16 GB Mac cannot run the sovereign default. Even DESIGN alone, at the conductor's 8,192-token ceiling, needs more than 16 GiB. The part that must sit on the GPU is also over the working-set limit a 16 GB Mac would have. This verdict is derived, not measured on 16 GB hardware.
  • On 24 GB, the model runs, but the measured host swapped. Free memory fell to a few tens of MiB during DESIGN calls. The measured host was also running a container workload, so a quieter 24 GB machine should have more margin; that was not measured.
  • CPU only is enough for the DESIGN interview, and not for BUILD. The throughput rows are held against vibey's own per-request timeout (VIBEY_OLLAMA_TIMEOUT, 900 s by default). A worst-case BUILD turn at CPU-only rates takes longer than that. Use a GPU.
  • ollama ps is not the memory to plan with. It understates the loaded model by 1.2 to 4.2 GiB. The memory figures here come from llama-server's own accounting in the Ollama log.

Software

Measured on Mac17,2 · Apple M5 · 24 GiB · macOS 26.6.2 (25G83), aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS; figures dated 2026-09-29 to 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Requirement Minimum Recommended Basis
Python 3.12 (declared >=3.12) works on 3.12, 3.13, 3.14 derived; declared
PostgreSQL 14 (checked on connect) measured on 18.4 (stale since 2026-09-29: $VIBEY_SPECS_PG_ADMIN_URL is not set): 20 (stale since 2026-09-29: $VIBEY_SPECS_PG_ADMIN_URL is not set) migrations applied declared; measured, stale
Ollama with gpt-oss:20b (sovereign default) required unless a paid engine is set up; measured on 0.35.0 - measured
git 2.26 - declared
  • Python. The floor is the oldest candidate interpreter that installs the engine and runs vibey --version. An older interpreter is refused by the resolver, because the package declares requires-python. Python 3.15 does not install yet: its release candidate has no pydantic-core wheel.
  • PostgreSQL is required, and vibey never guesses a database: set VIBEY_PG_URL. vibey checks the server's major version on connect. For vibey doctor to pass cleanly, also set up scram-sha-256 authentication and split owner and application roles (ADR-0055, ADR-0061).
  • Ollama (or another OpenAI-compatible server, such as llama-server or vLLM) is required for the sovereign default. Without it you need a paid engine and its credentials. No paid engine CLI or key is needed for the sovereign path.
  • Optional: uv is the recommended installer, and pip or pipx should also work (not tested). RabbitMQ is used only when a [bus] is configured. Docker or Podman is used for container isolation and image builds. Helm, kubectl and minikube are for Kubernetes. az is for the Azure deployment stages. gh is used only by the maintainer tooling (vibey-gh).

Network

Measured on Mac17,2 · Apple M5 · 24 GiB · macOS 26.6.2 (25G83), aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS; figures dated 2026-09-29 to 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Requirement Minimum Recommended Basis
Install download, vibey-engine 236.4 MB with [hub] 238.0 MB; krypton-app 238.0 MB measured
Installed size, vibey-engine 701.9 MB krypton-app 709.5 MB measured
Model download, gpt-oss:20b 13.79 GB qwen3:14b 9.28 GB (opt-in) measured
First install on the wire 14.03 GB - derived
Internet at runtime (sovereign path) none - measured

Bandwidth matters once, at install time, and the model is almost all of it. At runtime the sovereign path uses loopback only: PostgreSQL on 5432 and Ollama on 11434, plus the hub on 8765 when a krypton client connects. No telemetry, analytics, update check or version check is made at runtime. The engine SDKs for the paid engines ship in the base package, so a sovereign-only install downloads them too.

Host allowlist

From the network pass of 2026-09-29. Observed means seen on the wire or by the tracer. Code means read from the code or configuration only.

Install time

Host Port Purpose Needed for Observed / code
pypi.org 443 Package index Installing vibey-engine and krypton-app observed
files.pythonhosted.org 443 Wheels Same observed
releases.astral.sh 443 uv-managed CPython Only when no Python 3.12 or newer is present observed
registry.ollama.ai 443 Model manifests, redirects to the blob store ollama pull gpt-oss:20b (and qwen3:14b) observed
Cloudflare R2 (*.r2.cloudflarestorage.com) 443 Model blob bytes Same observed
registry.npmjs.org 443 npm tarballs Building the VS Code extension or the React Native app from source observed
huggingface.co 443 GGUF weights Only gptossloop/qwenloop model install (llama-server backend) code
gitlab.gnome.org, Flathub 443 json-glib wrap, GNOME runtime Building the GTK desktop client code

Runtime, sovereign default path

Host Port Purpose Needed for Observed / code
localhost or a unix socket 5432 PostgreSQL: queue, ledger, LISTEN/NOTIFY Every database command observed
127.0.0.1 11434 Ollama /api/chat (conductor) and /v1/* (gptossloop) Sovereign DESIGN, DECOMPOSE and BUILD observed
127.0.0.1 8765 The hub, vibey serve krypton clients observed

Runtime, optional features

Host Port Purpose Needed for Observed / code
api.github.com, github.com 443 (22 for ssh) GitHub API through gh; git push The triaged-delivery bridge, claudeloop issue import, vibey-gh observed / code
Anthropic (api.anthropic.com) 443 claudeloop sessions through the bundled CLI claudeloop observed
api2.cursor.sh, api.cursor.com 443 The Cursor SDK bridge cursorloop (its doctor is live) observed
api.openai.com (or a configured base URL) 443 The codex CLI codexloop code
generativelanguage.googleapis.com, aiplatform.googleapis.com 443 The Antigravity SDK agyloop code
Azure Resource Manager, through az 443 Deployment stages ④–⑥ Only vibey worker --azure az code
The Kubernetes API 443 or 6443 vibey operator Only the operator extra code
Webhook URLs (global addresses only) 443 or 80 Gate notifications, HMAC-signed Only when [notifications] webhooks are set code
LAN clients to the hub; mDNS 8765 TCP (TLS), 5353 UDP Device access and _vibey._tcp advertisement Only on a declared LAN ([hub] lan = true and --host) observed / code

Offline capability

With every outbound connection dead and only local PostgreSQL and Ollama up, these all worked: vibey --version, migrate, doctor, new, work on the sovereign gptossloop provider (against a model-free stub), answer, worker --once, status, projects, gates, engines, cost, budget, serve, hub pair, and the krypton launcher. What needs the internet: installing packages, pulling models, the paid engines, the triaged-delivery bridge, webhooks, --azure az, the operator and the opt-in integrations. vibey doctor offline differs in one verdict: cursorloop's live authentication check fails.

krypton clients

Measured on aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS; figures dated 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Requirement Minimum Recommended Basis
krypton-app (launcher) Python 3.12; pulls vibey-engine[hub] - derived
VS Code extension VS Code ^1.90.0; Node >=20 to build Node 24 declared
React Native app (mobile, web) Node >=20.19.4 Node 24 declared
Desktop (C, GTK 4) meson >= 1.1.0; glib >= 2.74; json-glib >= 1.6 GUI: gtk4 >= 4.12, libadwaita >= 1.4 declared
  • krypton-app (the krypton launcher) starts vibey serve, so it has vibey's own requirements and depends on vibey-engine[hub]. It needs PostgreSQL and VIBEY_PG_URL like vibey does.
  • The VS Code extension runs inside VS Code 1.90 or newer. Building it from source needs Node and npm. It talks to the hub only when vibey.hubUrl is set; otherwise it polls Ollama on loopback every 30 s, about 0.8 KB per refresh. It is published to Open VSX as the-vibey-project.krypton (0.2.0, first published with 3.3.0), and every release attaches its .vsix.
  • The React Native app (mobile and web) needs Node to build. The release builds the Android package with Gradle and Java 17 on Ubuntu, and the iOS package with EAS on a macOS runner with Xcode (scripts/release_binaries.toml). The iOS package of the release-binaries dry run 36976031188 (krypton-ios-0.1.0.ipa) declares iOS 16.4 as its minimum (MinimumOSVersion) and is signed for the App Store, so it reaches a phone through TestFlight or the App Store; no device install has been recorded.
  • The desktop client is C17 on GTK 4 and libadwaita, built with meson. The core builds without the GUI libraries. libsoup-3.0 (hub transport) and Avahi or dns_sd (discovery) are optional. Without discovery, you pair by typing the address and code.
  • On Linux, the release's tarball needs the distribution's GTK 4, libadwaita, libsoup 3, json-glib and Avahi; the Flatpak needs only Flatpak and fetches the GNOME runtime from Flathub, which carries no Avahi, so it cannot discover hubs by itself.
  • On macOS, from 3.4.0, the release's .dmg holds a self-contained krypton.app for Apple silicon that carries its own GTK 4 runtime, so it needs no Homebrew. It runs on macOS 15 Sequoia or newer: the app built by the dry run 36976031188 declares LSMinimumSystemVersion 15.0, the macOS of the runner it is built on. It discovers hubs over Bonjour (dns_sd) and asks once for local-network permission. It is ad-hoc signed and not notarised until the Developer ID secrets are set, so the first launch needs a confirmation (Downloads). Intel Macs are not supported: Homebrew no longer bottles the GTK 4 stack for them.

Linux: Ubuntu, Arch and Fedora on x86_64 and arm64

The Linux requirements are a matrix. Each supported distribution, on each architecture, is one cell, and each cell is measured inside that distribution's own container image by python scripts/minimum_specs.py cell --distro <d> --arch <a>. The distributions, the architectures, the images, the package sets and where each distribution's support is claimed are all declared in [minimum_specs.linux] in scripts/minimum_specs.toml, not in this page or the workflow: today Ubuntu 24.04 LTS and Arch Linux (the CI desktop job builds on both), Ubuntu 26.04 LTS (planned, #1116) and Fedora (named by the operator; a COPR channel is in ADR-0019), each on x86_64 and arm64.

What a cell measures:

  • The floors the distribution offers. The repository version of the kernel, Python, PostgreSQL and the desktop client's libraries, read from package metadata without installing them, and the glibc of the image. Each is compared with the floor vibey declares: PostgreSQL against the major version vibey checks on connect, Python against the oldest interpreter the engine runs on (if the distribution's is older, uv fetches one), glibc against the newest glibc any installed wheel's manylinux tag requires, and the libraries against clients/desktop/meson.build.
  • The closures. What each package set adds to the pristine image, sized by the package manager's own installed-size records: the base vibey needs (Python, git, certificates, and curl to fetch uv), PostgreSQL's server, and the desktop client's GTK 4 runtime. Recommended and weak dependencies are left out.
  • A cold install of vibey-engine and krypton-app with uv, from wheels built once on the host, with its venv, cache and download sizes.

In CI (.github/workflows/minimum-specs.yml) every cell runs on a native GitHub-hosted runner of its architecture, ubuntu-24.04 or ubuntu-24.04-arm, and the publish job folds the cells in with merge --complete: a cell whose job handed nothing over is marked stale, with that reason. Run on the other architecture, a cell is emulated (QEMU): its sizes and versions stand, because bytes are bytes, but its timings are refused, because they would measure the emulator. A cell with no image is recorded as skipped with the reason: there is no official Arch Linux image for arm64. A container's kernel, cores and memory are its runner's, so they are recorded with the host, not as the distribution's.

Measured on aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS, archlinux:latest (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073941, fedora:latest (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073938, fedora:latest (linux/arm64, native) on GitHub-hosted GitHub Actions 1000073936, ubuntu:24.04 (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073942, ubuntu:24.04 (linux/arm64, native) on GitHub-hosted GitHub Actions 1000073935, ubuntu:26.04 (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073939, ubuntu:26.04 (linux/arm64, native) on GitHub-hosted GitHub Actions 1000073937; figures dated 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Distribution Arch Run OS release glibc Kernel (packaged) Python (packaged) PostgreSQL (packaged) Desktop libraries (packaged)
Ubuntu 24.04 LTS x86_64 native Ubuntu 24.04.5 LTS 2.39 (wheels need 2.34: yes) 6.8.0 3.12.3 (>= 3.12: yes) 16 (>= 14: yes) gtk4 4.14.5, libadwaita 1.5.0 (meet floors: yes)
Ubuntu 24.04 LTS aarch64 native Ubuntu 24.04.5 LTS 2.39 (wheels need 2.34: yes) 6.8.0 3.12.3 (>= 3.12: yes) 16 (>= 14: yes) gtk4 4.14.5, libadwaita 1.5.0 (meet floors: yes)
Ubuntu 26.04 LTS x86_64 native Ubuntu 26.04.1 LTS 2.43 (wheels need 2.34: yes) 7.0.0 3.14.3 (>= 3.12: yes) 18 (>= 14: yes) gtk4 4.22.4, libadwaita 1.9.1 (meet floors: yes)
Ubuntu 26.04 LTS aarch64 native Ubuntu 26.04.1 LTS 2.43 (wheels need 2.34: yes) 7.0.0 3.14.3 (>= 3.12: yes) 18 (>= 14: yes) gtk4 4.22.4, libadwaita 1.9.1 (meet floors: yes)
Arch Linux x86_64 native Arch Linux 2.44 (wheels need 2.34: yes) 7.2.8 3.14.7 (>= 3.12: yes) 18.6 (>= 14: yes) gtk4 4.22.5, libadwaita 1.9.4 (meet floors: yes)
Arch Linux aarch64 not run: no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch - - - - - -
Fedora (current release) x86_64 native Fedora Linux 44 (Container Image) 2.43 (wheels need 2.34: yes) 7.2.8 3.14.7 (>= 3.12: yes) 18.6 (>= 14: yes) gtk4 4.22.5, libadwaita 1.9.4 (meet floors: yes)
Fedora (current release) aarch64 native Fedora Linux 44 (Container Image) 2.43 (wheels need 2.34: yes) 7.2.8 3.14.7 (>= 3.12: yes) 18.6 (>= 14: yes) gtk4 4.22.5, libadwaita 1.9.4 (meet floors: yes)

The requirements per cell. Memory is the same in every cell: the model dominates, and its weights and KV cache are the same bytes on any Linux. Cores depend on the architecture. Disk is each cell's own. A cell reading not measured names its reason in What is stale right now.

Measured on Mac17,2 · Apple M5 · 24 GiB · macOS 26.6.2 (25G83), aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS, archlinux:latest (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073941, fedora:latest (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073938, fedora:latest (linux/arm64, native) on GitHub-hosted GitHub Actions 1000073936, ubuntu:24.04 (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073942, ubuntu:24.04 (linux/arm64, native) on GitHub-hosted GitHub Actions 1000073935, ubuntu:26.04 (linux/amd64, native) on GitHub-hosted GitHub Actions 1000073939, ubuntu:26.04 (linux/arm64, native) on GitHub-hosted GitHub Actions 1000073937; figures dated 2026-09-29 to 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Distribution Arch Memory min / rec Cores min / rec Disk min / rec Basis
Ubuntu 24.04 LTS x86_64 24 GB (stale) / 32 GB (stale) not measured / not measured 20 GB / 50 GB derived, stale; not measured; derived
Ubuntu 24.04 LTS aarch64 24 GB (stale) / 32 GB (stale) unreachable / 4 20 GB / 50 GB derived, stale; derived
Ubuntu 26.04 LTS x86_64 24 GB (stale) / 32 GB (stale) not measured / not measured 20 GB / 50 GB derived, stale; not measured; derived
Ubuntu 26.04 LTS aarch64 24 GB (stale) / 32 GB (stale) unreachable / 4 20 GB / 50 GB derived, stale; derived
Arch Linux x86_64 24 GB (stale) / 32 GB (stale) not measured / not measured 20 GB / 50 GB derived, stale; not measured; derived
Arch Linux aarch64 24 GB (stale) / 32 GB (stale) unreachable / 4 - derived, stale; derived; not measured
Fedora (current release) x86_64 24 GB (stale) / 32 GB (stale) not measured / not measured 20 GB / 50 GB derived, stale; not measured; derived
Fedora (current release) aarch64 24 GB (stale) / 32 GB (stale) unreachable / 4 20 GB / 50 GB derived, stale; derived

How the Linux requirements are derived

Each requirement is fitted to measured points and solved in closed form (scripts/requirements_math.py), so check refits it from the record and gets the same digits. The model behind each one is the one its mechanism predicts:

  • Memory is a line in the context window. The weights are a constant and the KV cache and compute buffers grow in proportion to the window, so the model's memory is fitted by least squares as M(c) = M₀ + k·c over the measured sweep, and R² and the largest residual are kept so that a bend would show. On Linux without a discrete GPU the whole model sits in system RAM, so the requirement is h·M(c) at the runner's window, plus vibey, PostgreSQL and the OS allowance, with h a declared headroom factor. The same line, solved for c, gives the largest context a given memory size holds.
  • Cores follow Amdahl's law. CPU generation on n threads is fitted as T(n) = T₁ / ((1 − p) + p/n) through its reciprocal, which is linear in 1/n. Its marginal gain dT/dn = T₁·p / ((1 − p)·n + p)² falls monotonically, so the recommended core count is where it drops to a declared threshold θ, n = (√(T₁·p/θ) − p)/(1 − p), and the minimum is the fewest cores reaching the minimum generation rate F, n ≥ p / (T₁/F − (1 − p)), which has no answer when F is at or above the asymptote T₁/(1 − p). The sweep is measured, idle-gated, by the weekly host run (CPU only, one thread count at a time); an architecture no host has swept says not measured*.
  • The ledger's disk is an integral. The ledger is append-only, so with b bytes per job (measured: the database after one project and one job, minus the empty one) and a job rate r(t) = r₀ + r₁·t, the bytes after H days are ∫₀ᴴ b·(r₀ + r₁·t) dt. The rate and the horizon are declared in [minimum_specs.growth].

Measured on Mac17,2 · Apple M5 · 24 GiB · macOS 26.6.2 (25G83), aarch64 · unknown chip · 23 GiB · Ubuntu 24.04.4 LTS; figures dated 2026-09-30 to 2026-10-02 (UTC). Source: docs/architecture/evidence/minimum-specs.json, regenerated by scripts/minimum_specs.py; do not edit inside these markers.

Memory against context: least squares over the measured sweep

    M(c) = M₀ + k·c
    M₀ = 13,241 MiB (stale)   k = 27.874 (stale) KiB/token   R² = 0.99998 (stale)   max |residual| = 9.9 MiB (stale)

Linux memory: h = 1.1 (declared headroom), the model held in system RAM

    RAM(c) = h·M(c)/1024 + vibey side + OS headroom
    minimum     = RAM(32,768) = 19.18 GiB (stale) → 24 GB (stale)
    recommended = RAM(131,072) + apps headroom (4 GiB) = 26.06 GiB (stale) → 32 GB (stale)
    c_max(16 GB) = ((S·10⁹/2²⁰ − (side + OS)·1024)/h − M₀)/(k/1024) = 0 (stale) tokens; holds 32,768: no (stale)

CPU throughput against cores: Amdahl's law, per architecture

    T(n) = T₁ / ((1 − p) + p/n)       fitted as 1/T = (1 − p)/T₁ + (p/T₁)·(1/n)
    dT/dn = T₁·p / ((1 − p)·n + p)²
    knee:  dT/dn = θ  ⇒  n* = (√(T₁·p/θ) − p)/(1 − p),  θ = 0.25 tok/s per core
    floor: T(n) ≥ F  ⇔  n ≥ p/(T₁/F − (1 − p)),  F = 10 tok/s; none if F ≥ T₁/(1 − p)
    x86_64: T₁ = not measured, p = not measured, R² = not measured, T∞ = not measured; knee not measured, minimum not measured
    aarch64: T₁ = 1.2 tok/s, p = 0.6865, R² = 0.1871, T∞ = 4.0 tok/s; knee 4, minimum unreachable

Ledger growth: append-only, so the disk it needs is the integral of its rate

    D(H) = ∫₀ᴴ b·(r₀ + r₁·t) dt = b·(r₀·H + r₁·H²/2)
    b = not measured per job, r₀ = 50 jobs/day, r₁ = 0 jobs/day², H = 365 days  ⇒  D = not measured

What is stale right now

Record generated 2026-10-02T13:55:54.709Z.

Figure Status Since Reason
bench.gpt-oss:20b.ctx131072.device_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx131072.host_compute_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx131072.host_model_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx131072.kv_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx32768.device_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx32768.host_compute_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx32768.host_model_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx32768.kv_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx4096.device_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx4096.host_compute_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx4096.host_model_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx4096.kv_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx8192.device_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx8192.host_compute_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx8192.host_model_mib stale 2026-09-30 no accounting line in the log
bench.gpt-oss:20b.ctx8192.kv_mib stale 2026-09-30 no accounting line in the log
disk.ledger_growth_gb skipped - missing input(s): postgres.bytes_per_job
disk.minimum_gb stale 2026-09-30 derived from stale input(s): disk.minimum_need_gb
disk.minimum_need_gb stale 2026-09-30 derived from stale input(s): disk.ollama_app_bytes
disk.ollama_app_bytes stale 2026-09-30 /Applications/Ollama.app is absent on this host
disk.recommended_gb stale 2026-09-30 derived from stale input(s): disk.recommended_need_gb
disk.recommended_need_gb stale 2026-09-30 derived from stale input(s): disk.minimum_need_gb
fit.cpu.x86_64.asymptote_tok_s skipped - a fit needing 3 of its 6 measured points has 0; missing bench.gpt-oss:20b.cpu.x86_64.threads1.gen_tok_s and 5 more
fit.cpu.x86_64.knee_cores skipped - a fit needing 3 of its 6 measured points has 0; missing bench.gpt-oss:20b.cpu.x86_64.threads1.gen_tok_s and 5 more
fit.cpu.x86_64.minimum_cores skipped - a fit needing 3 of its 6 measured points has 0; missing bench.gpt-oss:20b.cpu.x86_64.threads1.gen_tok_s and 5 more
fit.cpu.x86_64.p skipped - a fit needing 3 of its 6 measured points has 0; missing bench.gpt-oss:20b.cpu.x86_64.threads1.gen_tok_s and 5 more
fit.cpu.x86_64.r2 skipped - a fit needing 3 of its 6 measured points has 0; missing bench.gpt-oss:20b.cpu.x86_64.threads1.gen_tok_s and 5 more
fit.cpu.x86_64.t1_tok_s skipped - a fit needing 3 of its 6 measured points has 0; missing bench.gpt-oss:20b.cpu.x86_64.threads1.gen_tok_s and 5 more
fit.memory.k_kib_per_token stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx4096.device_mib, bench.gpt-oss:20b.ctx4096.host_model_mib, bench.gpt-oss:20b.ctx4096.host_compute_mib, bench.gpt-oss:20b.ctx8192.device_mib, bench.gpt-oss:20b.ctx8192.host_model_mib, bench.gpt-oss:20b.ctx8192.host_compute_mib, bench.gpt-oss:20b.ctx32768.device_mib, bench.gpt-oss:20b.ctx32768.host_model_mib, bench.gpt-oss:20b.ctx32768.host_compute_mib, bench.gpt-oss:20b.ctx131072.device_mib, bench.gpt-oss:20b.ctx131072.host_model_mib, bench.gpt-oss:20b.ctx131072.host_compute_mib
fit.memory.m0_mib stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx4096.device_mib, bench.gpt-oss:20b.ctx4096.host_model_mib, bench.gpt-oss:20b.ctx4096.host_compute_mib, bench.gpt-oss:20b.ctx8192.device_mib, bench.gpt-oss:20b.ctx8192.host_model_mib, bench.gpt-oss:20b.ctx8192.host_compute_mib, bench.gpt-oss:20b.ctx32768.device_mib, bench.gpt-oss:20b.ctx32768.host_model_mib, bench.gpt-oss:20b.ctx32768.host_compute_mib, bench.gpt-oss:20b.ctx131072.device_mib, bench.gpt-oss:20b.ctx131072.host_model_mib, bench.gpt-oss:20b.ctx131072.host_compute_mib
fit.memory.max_residual_mib stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx4096.device_mib, bench.gpt-oss:20b.ctx4096.host_model_mib, bench.gpt-oss:20b.ctx4096.host_compute_mib, bench.gpt-oss:20b.ctx8192.device_mib, bench.gpt-oss:20b.ctx8192.host_model_mib, bench.gpt-oss:20b.ctx8192.host_compute_mib, bench.gpt-oss:20b.ctx32768.device_mib, bench.gpt-oss:20b.ctx32768.host_model_mib, bench.gpt-oss:20b.ctx32768.host_compute_mib, bench.gpt-oss:20b.ctx131072.device_mib, bench.gpt-oss:20b.ctx131072.host_model_mib, bench.gpt-oss:20b.ctx131072.host_compute_mib
fit.memory.r2 stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx4096.device_mib, bench.gpt-oss:20b.ctx4096.host_model_mib, bench.gpt-oss:20b.ctx4096.host_compute_mib, bench.gpt-oss:20b.ctx8192.device_mib, bench.gpt-oss:20b.ctx8192.host_model_mib, bench.gpt-oss:20b.ctx8192.host_compute_mib, bench.gpt-oss:20b.ctx32768.device_mib, bench.gpt-oss:20b.ctx32768.host_model_mib, bench.gpt-oss:20b.ctx32768.host_compute_mib, bench.gpt-oss:20b.ctx131072.device_mib, bench.gpt-oss:20b.ctx131072.host_model_mib, bench.gpt-oss:20b.ctx131072.host_compute_mib
gpu.discrete_vram_gb stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx32768.device_mib
gpu.metal_limit_16gb_mib stale 2026-09-30 derived from stale input(s): gpu.working_set_limit_mib
gpu.over_16gb.ctx32768_mib stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx32768.device_mib, gpu.metal_limit_16gb_mib
gpu.over_16gb.ctx8192_mib stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx8192.device_mib, gpu.metal_limit_16gb_mib
gpu.working_set_limit_mib stale 2026-09-30 no working-set line in the log
linux.arch.aarch64.disk.minimum_gb skipped - missing input(s): linux.arch.aarch64.disk.minimum_need_gb
linux.arch.aarch64.disk.minimum_need_gb skipped - missing input(s): linux.arch.aarch64.pkg.base.bytes, linux.arch.aarch64.pkg.postgres.bytes, linux.arch.aarch64.install.vibey-engine.venv_bytes, linux.arch.aarch64.install.vibey-engine.uv_cache_bytes
linux.arch.aarch64.disk.recommended_gb skipped - missing input(s): linux.arch.aarch64.disk.recommended_need_gb
linux.arch.aarch64.disk.recommended_need_gb skipped - missing input(s): linux.arch.aarch64.disk.minimum_need_gb, linux.arch.aarch64.pkg.desktop.bytes, linux.arch.aarch64.install.krypton-app.venv_bytes
linux.arch.aarch64.disk.with_ledger_gb skipped - missing input(s): linux.arch.aarch64.disk.minimum_need_gb, disk.ledger_growth_gb
linux.arch.aarch64.floor.desktop skipped - missing input(s): linux.arch.aarch64.packaged.glib, linux.arch.aarch64.packaged.json_glib, linux.arch.aarch64.packaged.gtk4, linux.arch.aarch64.packaged.libadwaita
linux.arch.aarch64.floor.glibc skipped - missing input(s): linux.arch.aarch64.glibc, linux.arch.aarch64.install.glibc_floor
linux.arch.aarch64.floor.postgres skipped - missing input(s): linux.arch.aarch64.packaged.postgres
linux.arch.aarch64.floor.python skipped - missing input(s): linux.arch.aarch64.packaged.python
linux.arch.aarch64.glibc skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.glibc_floor skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.krypton-app.cold_s skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.krypton-app.download_bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.krypton-app.packages skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.krypton-app.uv_cache_bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.krypton-app.venv_bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.vibey-engine.cold_s skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.vibey-engine.download_bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.vibey-engine.packages skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.vibey-engine.uv_cache_bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.install.vibey-engine.venv_bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.os_release skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.glib skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.gtk4 skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.json_glib skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.kernel skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.libadwaita skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.postgres skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.packaged.python skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.pkg.base.bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.pkg.base.count skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.pkg.desktop.bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.pkg.desktop.count skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.pkg.postgres.bytes skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.aarch64.pkg.postgres.count skipped - no official Arch Linux image for arm64: archlinux:latest is amd64 only, and Arch Linux ARM is a separate project, not Arch
linux.arch.x86_64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
linux.fedora.aarch64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
linux.fedora.x86_64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
linux.ram.max_context_at_probe stale 2026-09-30 derived from stale input(s): fit.memory.m0_mib, fit.memory.k_kib_per_token, ram.vibey_side_gib
linux.ram.minimum_gb stale 2026-09-30 derived from stale input(s): linux.ram.minimum_need_gib
linux.ram.minimum_need_gib stale 2026-09-30 derived from stale input(s): fit.memory.m0_mib, fit.memory.k_kib_per_token, ram.vibey_side_gib
linux.ram.probe_fits_runner stale 2026-09-30 derived from stale input(s): linux.ram.max_context_at_probe
linux.ram.recommended_gb stale 2026-09-30 derived from stale input(s): linux.ram.recommended_need_gib
linux.ram.recommended_need_gib stale 2026-09-30 derived from stale input(s): fit.memory.m0_mib, fit.memory.k_kib_per_token, ram.vibey_side_gib
linux.ubuntu-24.04.aarch64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
linux.ubuntu-24.04.x86_64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
linux.ubuntu-26.04.aarch64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
linux.ubuntu-26.04.x86_64.disk.with_ledger_gb skipped - missing input(s): disk.ledger_growth_gb
postgres.after_one_job_bytes skipped - worker --once did not run on a scratch database
postgres.bytes_per_job skipped - missing input(s): postgres.after_one_job_bytes
postgres.empty_db_bytes stale 2026-09-30 $VIBEY_SPECS_PG_ADMIN_URL is not set
postgres.migrations_applied stale 2026-09-29 $VIBEY_SPECS_PG_ADMIN_URL is not set
postgres.server_version stale 2026-09-29 $VIBEY_SPECS_PG_ADMIN_URL is not set
process.cli.new.max_rss_mib stale 2026-09-30 no scratch database
process.cli.new.wall_s stale 2026-09-30 no scratch database
process.cli.status.max_rss_mib stale 2026-09-30 no scratch database
process.cli.status.wall_s stale 2026-09-30 no scratch database
process.cli.version.max_rss_mib stale 2026-09-30 no scratch database
process.cli.version.wall_s stale 2026-09-30 no scratch database
process.cli.worker_once.max_rss_mib stale 2026-09-30 no scratch database
process.cli.worker_once.wall_s stale 2026-09-30 no scratch database
process.krypton.idle_rss_mib stale 2026-09-30 no scratch database
process.krypton.startup_s stale 2026-09-30 no scratch database
process.serve.idle_rss_mib stale 2026-09-30 no scratch database
process.serve.startup_s stale 2026-09-30 no scratch database
ram.16gb_verdict stale 2026-09-30 derived from stale input(s): ram.design_only_need_gib
ram.design_only_need_gib stale 2026-09-30 derived from stale input(s): ram.model_process.ctx8192, ram.vibey_side_gib
ram.kv_per_token_kib stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx131072.kv_mib, bench.gpt-oss:20b.ctx4096.kv_mib
ram.minimum_gb stale 2026-09-30 derived from stale input(s): ram.minimum_need_gib
ram.minimum_need_gib stale 2026-09-30 derived from stale input(s): ram.model_process.ctx32768, ram.vibey_side_gib
ram.model_process.ctx131072 stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx131072.device_mib, bench.gpt-oss:20b.ctx131072.host_model_mib, bench.gpt-oss:20b.ctx131072.host_compute_mib
ram.model_process.ctx32768 stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx32768.device_mib, bench.gpt-oss:20b.ctx32768.host_model_mib, bench.gpt-oss:20b.ctx32768.host_compute_mib
ram.model_process.ctx8192 stale 2026-09-30 derived from stale input(s): bench.gpt-oss:20b.ctx8192.device_mib, bench.gpt-oss:20b.ctx8192.host_model_mib, bench.gpt-oss:20b.ctx8192.host_compute_mib
ram.recommended_gb stale 2026-09-30 derived from stale input(s): ram.recommended_need_gib
ram.recommended_need_gib stale 2026-09-30 derived from stale input(s): ram.model_process.ctx131072, ram.vibey_side_gib
ram.vibey_side_gib stale 2026-09-30 derived from stale input(s): process.cli.worker_once.max_rss_mib, process.serve.idle_rss_mib

Not verified

  • Windows hosts: nothing was measured.
  • Linux as an installed system: every Linux cell ran in a container, so a distribution's own kernel, idle memory and desktop session were not measured; Linux memory uses the OS allowance assumed from the Mac (3.5 GiB).
  • Linux on x86_64 at the seed: measured under QEMU emulation on the Apple M5, so only sizes and versions. uv itself crashed under QEMU (signal 11), so every x86_64 cell's install sizes and glibc floor, and every x86_64 timing, wait for the native CI runner.
  • Arch Linux on arm64: there is no official image (archlinux:latest is amd64 only; Arch Linux ARM is a separate project).
  • The model's memory on Linux: the line M(c) = M0 + k*c was fitted on the Mac's Metal backend; the weights and KV cache are the same bytes on Linux, but its CPU and CUDA compute buffers were not measured. The Linux memory figures assume no discrete GPU (the whole model in system RAM).
  • CPU scaling (Amdahl's law): the thread sweep had not run at the seed (the host was busy with another client's model), and no x86_64 host with the model runs it, so the core counts are not measured on either architecture.
  • The ledger's bytes per job: not measured yet (the next weekly run measures it); one job's growth is page-granular, so the measurement will be an upper bound.
  • Ollama's Linux bundle: its unpacked size is the bytes of its tar stream (contents plus headers), not an installed tree.
  • Other hardware: 16 GB and 32/64 GB Macs, other Apple chips, x86 CPUs, NVIDIA/AMD discrete GPUs. The 16 GB verdict and the discrete-GPU size are arithmetic.
  • The macOS GPU working-set fraction on a 16 GB Mac: taken as this host's ratio (widely reported as about 2/3 there).
  • CPU only with the weights held in memory: the measured CPU rates ran under memory pressure (weights paged in), so they are a lower bound.
  • The minimum CPU core count: nothing below this 10-core M5 was tested.
  • OS headroom on a headless or server install: 3.5 GiB is an assumption from this desktop Mac.
  • A real gptossloop BUILD session end to end: runner turns are bounded by arithmetic from the depth test.
  • Concurrent lanes: one request at a time was measured (ADR-0058 measured two slots on this host).
  • PostgreSQL 14-17 and git older than 2.54: only PostgreSQL 18.4 and git 2.54 ran; the floors are declared.
  • Python 3.12.0-3.12.12: only 3.12.13 ran. Python 3.15 does not install yet (no pydantic-core wheel).
  • pip and pipx install paths: only uv was used.
  • The krypton desktop GUI, the VS Code extension inside VS Code, and the React Native app at runtime; iOS and Android native builds.
  • The desktop client's minimum library versions: only the installed versions were built.
  • Paid engines end to end, the RabbitMQ bus, and the opt-in integrations (Azure, the operator, webhooks, SMTP).
  • The LAN hub on a real network interface (mDNS multicast was not observed).

Reproducing and changing the figures

python scripts/minimum_specs.py measure       # probe this host; merge into the record (long)
python scripts/minimum_specs.py derive        # recompute the derived figures from the record
python scripts/minimum_specs.py render        # rewrite this page's and the paper's tables
python scripts/minimum_specs.py check         # CI: exit 1 if either is out of step
python scripts/minimum_specs.py cells         # the Linux matrix the configuration declares
python scripts/minimum_specs.py cell --distro fedora --arch aarch64 --out fedora-aarch64.json
python scripts/minimum_specs.py merge fedora-aarch64.json   # fold a cell into the record

A cell needs Docker (or a compatible docker CLI, [minimum_specs.linux] docker) and the network, to pull the image, the distribution's packages and uv. The cell for ollama_bundle_distro also streams Ollama's Linux bundle through zstd on the host.

To change an assumption, a threshold or a context size, edit scripts/minimum_specs.toml, then run derive and render. check runs in CI through tests/meta/test_minimum_specs.py. It fails when a derived figure disagrees with its inputs or a generated table disagrees with the record. The measurement itself needs PostgreSQL, reachable through the DSN named by admin_url_env, where it creates, migrates and drops a scratch database. It also needs Ollama and uv. It never touches any other database.