Installation¶
This guide walks you through installing tuvl and its dependencies.
Prerequisites¶
Before installing tuvl, ensure you have:
- Python 3.13 or 3.14 — tuvl 2.0 supports
>=3.13,<3.15. uv picks a compatible interpreter automatically. - uv — Fast Python package manager (recommended)
- PostgreSQL — For data persistence (optional for development)
- Docker — Optional. The easiest way to run PostgreSQL, Redis and Laya locally, and required by
tuvl shipto build the production image - Redis — Optional for
tuvl dev; needed when you run more than one worker (why) - Laya (
tuvl/laya-serve) — Optional. A self-hosted decision model fordecideagents; runs as its own container - Ollama — For local LLM inference (optional)
What you actually need
To try tuvl you need Python and uv, and a PostgreSQL database. Everything else above is opt-in: Redis only matters for multi-worker deployments, and Laya only if a decide agent uses it as its decision model.
Python 3.14
tuvl 1.x was held to Python 3.13 because its token library (biscuit-python) had no 3.14 builds. tuvl 2.0 signs tokens as standard JWTs and runs on 3.13 and 3.14 on Linux, macOS and Windows.
Installing uv¶
curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
pip install uv
2.0 public beta¶
tuvl 2.0.0 is released on 20 October 2026. Until then, 2.0 is available as the public beta
2.0.0b3. A plain uv tool install tuvl still installs the latest 1.x release, so ask for the
beta explicitly:
# CLI + dev server + Insight (recommended)
uv tool install --prerelease allow "tuvl[standard]==2.0.0b3"
# CLI only
uv tool install "tuvl==2.0.0b3"
--prerelease allow is needed because uv only takes a pre-release for packages you name yourself,
and tuvl[standard] pulls in tuvl-insight 2.0.0b3 indirectly.
The TypeScript client has a matching beta on npm's beta channel:
npm install @tuvl/client@beta # 2.0.0-beta.1
From 20 October the commands below install 2.0.0 directly. Report beta issues on GitHub.
Installing tuvl CLI¶
Install the tuvl CLI globally using uv:
# Base CLI only
uv tool install tuvl
# With dev server and built-in UI (recommended)
uv tool install "tuvl[standard]"
Tip
tuvl[standard] includes the dev server (tuvl dev), the built-in tuvl insight UI, and hot-reload support. Use the base install for production-only deployments where the UI is not needed.
Verify the installation:
tuvl --version
Installing tuvl as a Project Dependency¶
For projects that embed the engine directly:
# Base engine
uv add tuvl
# With dev server and built-in UI
uv add "tuvl[standard]"
Or with pip:
pip install "tuvl[standard]"
Optional Dependencies¶
PostgreSQL¶
brew install postgresql@16
brew services start postgresql@16
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql
docker run -d \
--name tuvl-postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=tuvl \
-p 5432:5432 \
postgres:16-alpine
Redis (optional)¶
Redis shares OAuth state and the token-revocation list between workers. A single tuvl dev
process works without it (an in-process fallback is used); add it before running more than one
worker. See Redis configuration for the datasource file.
brew install redis
brew services start redis
sudo apt install redis-server
sudo systemctl start redis-server
docker run -d --name tuvl-redis -p 6379:6379 redis:7-alpine
Then add datasources/redis-primary.yaml with type: redis, host: ${REDIS_HOST:localhost}.
Laya decision server (optional)¶
Laya is an open-weight model that picks one value of a
decide agent's enum with a confidence. It runs on CPU in its own container, offline, with
verified weights baked in. Use it when you want decisions made on your own infrastructure instead
of calling a hosted model.
The quickest route is to let tuvl init scaffold it:
tuvl init my-project --decision-model laya -y
cd my-project
docker compose up -d laya # serves on http://localhost:8000
This writes llms/laya.yaml (a provider: laya AgentModel), LAYA_API_* entries in .env and a
compose.yaml. To run the container by hand:
docker run -d --name laya -p 127.0.0.1:8000:8000 tuvl/laya-serve:0.4.7-1-en
docker run -d --name laya -p 127.0.0.1:8000:8000 tuvl/laya-serve:0.4.7-1
| Image | Use with | Memory |
|---|---|---|
…-en |
model: english |
about 2 GiB |
| full | model: auto or multilingual |
about 3.5 GiB |
The image is about 2.7 GB (CPU PyTorch plus the weights), amd64 and arm64. Check it is up with
curl localhost:8000/health. Details, checkpoints and settings are in
AI Models → Laya and Decide.
Ollama (Local LLM)¶
brew install ollama
ollama serve
# In another terminal, pull a model
ollama pull llama3
curl -fsSL https://ollama.com/install.sh | sh
ollama serve
# In another terminal
ollama pull llama3
docker run -d \
--name ollama \
-p 11434:11434 \
-v ollama:/root/.ollama \
ollama/ollama
docker exec ollama ollama pull llama3
Verifying Installation¶
Create the sample project and run its checks:
tuvl init my-project --sample --preset gemini -y # or openai | anthropic | ollama
cd my-project
tuvl validate --strict
tuvl test
tuvl dev --auto-login
tuvl dev serves the engine on port 8885 and Insight at http://127.0.0.1:8885/insight/.
With --auto-login this machine skips the key screen; otherwise paste the session key, stored in
.tuvl/.dev-session (tuvl dev --show-key prints it).
Troubleshooting¶
Windows: port already in use or permission denied¶
Symptom — tuvl dev fails to start the engine on its default port (8885) with one of:
[Errno 48] Address already in use/Only one usage of each socket address … is normally permittedPermissionError: [WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions
Why this happens — either another process is already bound to the port, or — common on Windows — the port falls inside a range Windows has reserved for dynamic allocation. Hyper-V, WSL 2, and Docker Desktop routinely reserve large TCP ranges; binding a port inside a reserved range fails with WinError 10013 even though nothing is actively listening on it.
Inspect the reserved ranges:
netsh interface ipv4 show excludedportrange protocol=tcp
If 8885 sits inside an excluded range (or is otherwise taken), run tuvl on a free port:
tuvl dev --port 8899
# Persist a different port for all tuvl commands in this shell
$env:TUVL_DEV_PORT = "8899"
tuvl dev
Tip
The port also appears in the login URL tuvl prints (http://127.0.0.1:<port>/insight/). If you rely on the OAuth callback flow, align TUVL_OAUTH_BASE_URL with the port you chose (defaults to http://localhost:8885).
The default 8885 spells T·U·V·L on a phone keypad and deliberately avoids the crowded 8000/8080/8888 range — but any free, non-reserved port works.
Next Steps¶
- Quickstart Guide — Build your first workflow
- Code generation —
tuvl codegen: typed schemas, stubs and tests - Shipping to production —
tuvl ship: image, Helm chart, Laya and secrets - Project Structure — Understand the project layout
- Agentic Manual — how tuvl projects are written