tuvl
/ˈtuːvəl/ · തൂവൽ · feather
YAML-driven workflow orchestration engine — compose pipelines of functional nodes,
LLM calls, API integrations, and MCP tools, served as a REST API
with Postgres persistence and Redis-backed state.
Stable release. tuvl 1.0.0 is production-ready — the API and YAML schemas are stable and versioned under SemVer: breaking changes bump the major version.
tuvl lets you define, run, and manage multi-step AI workflows using plain YAML files. No boilerplate. No complex overhead. No lock-in.
Install tuvl[standard] during development to get Tuvl Insight — a full browser-based developer portal for designing models, configuring datasources, managing LLM providers, building workflows visually, controlling access, and testing execution — all without leaving your local environment.
- Website: tuvl.io
- GitHub: tuvl-io/tuvl
🏛️ Architecture & Data Flow
flowchart TD
subgraph Configuration
YAML[YAML Definitions] -->|load_all_configs| Reg[In-Memory Registries]
end
subgraph Transport Layer
Reg -->|Mount Endpoints| REST[FastAPI REST Server]
Reg -->|Mount Services| GRPC[gRPC Server]
end
Client([Clients]) -->|HTTP/JSON| REST
Client -->|HTTP/2 Protobuf| GRPC
subgraph Security: Authentication & Authorization
REST --> Auth[Biscuit Token Auth<br>Verify Crypto Signature]
GRPC --> Auth
Auth -->|Extract Identity| AuthZ[IAM Scope Guard<br>Enforce Model/Route Scopes]
end
AuthZ -->|Workflow Route| Engine{WorkflowEngine.run}
AuthZ -->|Auto-Generated CRUD| UoW[Workflow Unit of Work<br>Pydantic-Validated CRUD]
subgraph Execution & Integrations
Engine -->|ModelOp| UoW
UoW -->|SQLModel Object Mapper| PG[(PostgreSQL)]
Engine -->|Agent| LLM[LiteLLM Any Provider]
Engine -->|DataSearch| RAG[(pgvector RAG)]
Engine -->|Functional| Nodes[Custom Python Nodes]
Engine -->|MCP| MCP[MCP Tools]
Engine -->|APICall| ExtAPI[External APIs]
end
📦 Installation
Core (production server)
pip install tuvl
# or
uv add tuvl
Installs the full workflow engine and REST API server. Sufficient for all production deployments.
With Tuvl Insight (developer portal)
pip install "tuvl[standard]"
# or
uv add "tuvl[standard]"
Adds Tuvl Insight — a full browser-based developer portal, active only when TUVL_DEV_MODE=true. See the Tuvl Insight section for the full feature list.
CLI (global tool)
uv tool install tuvl
SDKs
Coming soon — SDKs are being prepared for release alongside the source code.
| SDK | Package | Status |
|---|---|---|
| Python | pip install tuvl-sdk / uv add tuvl-sdk |
Coming soon |
| JavaScript / TypeScript | npm install @tuvl/client |
Stable — @tuvl/client |
The SDKs provide typed clients for triggering workflows, subscribing to execution events via SSE, and managing workflow instances from your own applications.
🚀 Quick Start
1. Scaffold a new project:
# Create a new folder named 'my-project' in the current directory
tuvl init my-project
# Create it inside a specific parent directory
tuvl init my-project --project-dir /path/to/workspace
# Initialise an already-named path (project name inferred from last component)
tuvl init --project-dir /path/to/workspace/my-project
tuvl init always creates the target directory (it will not overwrite an existing one). Prompts for Postgres and LLM provider credentials and writes a pyproject.toml + .env.
2. Install dependencies:
uv sync
Installs tuvl and any project-level packages into a local .venv. Add extra packages with uv add <package> — useful when your nodes use libraries like pandas or httpx.
3. Start the development server:
uv run tuvl dev
Starts the engine with hot reload on http://localhost:8885. If tuvl[standard] is installed, the Insight dashboard is available at http://localhost:8885/insight.
4. Run in production:
tuvl run --project-dir /path/to/project --workers 4
Starts the production uvicorn server without hot reload.
5. Ship it:
tuvl ship
Validates the project, builds a production container image, and generates a Helm chart under deploy/chart/<name>/ ready for helm install.
🗂️ Project Structure
A tuvl project directory contains:
my-project/
├── pyproject.toml # Project deps — add extras with: uv add <pkg>
├── .env # LLM API keys and database config (git-ignored)
├── .env.example # Safe-to-commit template
├── config.yaml # Directory layout (customisable)
├── models/ # Data model definitions (YAML)
├── datasources/ # Datasource definitions (YAML)
├── llms/ # LLM / AgentModel configs (YAML)
├── nodes/ # Custom Python node implementations
├── artifacts/ # Versioned prompts/steering/skills (.md) + guardrail/hook/MCP configs (kind: Artifact)
└── workflows/ # Workflow definitions (YAML)
Share a project by committing everything except .env — collaborators run uv sync to reproduce the exact environment.
⚙️ CLI Reference
| Command | Description |
|---|---|
tuvl init <name> |
Create <name>/ in the current directory and scaffold inside it |
tuvl init <name> --project-dir <parent> |
Create <parent>/<name>/ and scaffold inside it |
tuvl init --project-dir <path> |
Scaffold into <path>/ (project name inferred from last component) |
tuvl init <name> --sample |
Same as above, plus sample recruitment pipeline covering every step kind |
tuvl init <name> --multi-tenant |
Scaffold in multi-tenant mode — locks deployment_mode: multi_tenant in .tuvl/system.yaml, injects tenant_id columns into every model, and enables Postgres Row-Level Security tooling |
tuvl dev |
Start the engine in dev mode with hot reload. The dev key is written to .tuvl/.dev-session (mode 0600); pass --show-key to print it, and --auto-login to automatically bypass the Insight security screen |
tuvl run |
Start the production server. Requires a persistent TUVL_BISCUIT_PRIVATE_KEY in .env (generate one with tuvl keys generate) — unlike tuvl dev, it will not fall back to an ephemeral signing key |
tuvl test |
Run LLM-as-a-Judge tests against workflow definitions. In multi-tenant projects, automatically injects a synthetic tenant so the data-layer guard doesn't reject test runs |
tuvl validate |
Validate every YAML/.yml file anywhere in the project tree, dispatched by kind: — the same recursive discovery the runtime uses. Checks models, workflows, datasources, LLM presets, embeddings, collections, and federation providers; an unrecognized kind: is an error. --strict treats warnings as errors |
tuvl ship |
Package the project for production: validates it, generates a production Dockerfile + .dockerignore and a Helm chart under deploy/chart/<name>/, then builds the container image. --tag sets the image reference, --no-build writes artifacts only, --push pushes after building, --force regenerates existing files |
tuvl keys generate |
Print a fresh Ed25519 Biscuit signing key (64-hex) plus the .env line; add --write to write it into the project .env (owner-only perms, --force to overwrite) |
tuvl db generate-rls |
Emit ALTER TABLE … ENABLE ROW LEVEL SECURITY + tenant-scoped policy SQL for every tenant-aware model (multi-tenant only) |
tuvl db check-rls |
Verify that RLS is enabled and policies are present on every tenant-aware table |
tuvl stream-watch |
Tail workflow execution events from the engine's SSE stream. --timeout sets a read-timeout in seconds for the stream body (default: none, so a slow agent iteration or HITL wait won't abort the connection) |
tuvl help |
Show all commands |
Common options
tuvl dev --port 9000 --project-dir ./my-project
tuvl dev --show-key # print the dev API key (off by default)
tuvl dev --auto-login # automatically bypass the Tuvl Insight security screen
tuvl run --host 0.0.0.0 --port 8885 --workers 2
tuvl run --allow-host 10.0.0.0/8 # IP allowlist
tuvl keys generate # print a production Biscuit key + the .env line
tuvl keys generate --write # write TUVL_BISCUIT_PRIVATE_KEY into .env (needed by `tuvl run`)
tuvl validate --project-dir ./my-project
tuvl ship --tag ghcr.io/acme/my-project:1.0.0 --push # container + Helm chart
tuvl stream-watch screen-candidate --timeout 30 # bound the SSE read-timeout (default: none)
🔩 Workflow Step Kinds
Workflows are YAML files defining a sequence of steps. Each step has a kind:
| Kind | Description |
|---|---|
Functional |
Execute a registered Python node function |
Agent |
The one LLM step — mode: completion (a single retried litellm call) or mode: autonomous (a bounded tool-calling loop: the model calls declared tools — other steps — until it emits an outcome.enum, capped by max_iterations / token_budget). Guided by inline text or versioned artifact:// prompts/steering/skills; gated by guardrail artifacts; optionally watched by a spec.supervisor |
APICall |
Make an outbound HTTP request and map the response into context |
MCP |
Call a tool via the Model Context Protocol — the server connection (stdio or SSE) is declared once as a type: mcp artifact and referenced via mcp.server: artifact://<name> |
Router |
Evaluate a condition and branch to a named route |
ModelOp |
Perform CRUD operations on a registered data model |
Response |
Shape and return the final HTTP response from context keys |
HumanInTheLoop |
Pause for a human approve/reject decision, then resume where it left off |
Workflows are triggered over HTTP (trigger.path + method), on a cron schedule (trigger.schedule: "*/5 * * * *" — UTC, single-fire across workers via a Postgres advisory lock, skip-on-miss), or both. Schedule-only workflows mount no HTTP route at all.
🧰 Makefile Targets
For development within the monorepo:
make setup # Install core dependencies
make setup-all # Install core + Tuvl Insight + dev tools
make dev-ui # Run server + Insight dashboard (TUVL_DEV_MODE=true)
make dev-core # Run server in dev mode without UI
make build-ui # Compile React SPA into tuvl-insight wheel
make build # Build both tuvl and tuvl-insight wheels → dist/
make publish # build → publish both wheels to PyPI (set UV_PUBLISH_TOKEN)
make lint # Run ruff check
make fmt # Run ruff check --fix + ruff format
make check # CI gate: ruff check + ruff format --check
make typecheck # Run mypy (advisory)
make clean # Remove build artifacts
🔬 Tuvl Insight (Developer Portal)
Installed via pip install "tuvl[standard]". Active only when TUVL_DEV_MODE=true.
Tuvl Insight is not just an observability tool — it is a complete local developer portal that covers the full development lifecycle of a tuvl project.
Design & Configuration
| Section | Description |
|---|---|
| Workflows | Visual YAML editor with a live graph view of step connections, routes, and node types. Create, edit, and save workflow definitions without touching files directly. |
| Models | Form-driven model designer — define fields, types (PostgreSQL-native and extended), primary keys, relationships, and constraints. Generates YAML automatically. |
| Datasources | Configure Postgres datasource connections (host, port, database, credentials) with a structured form. |
| LLM Models | Manage AgentModel definitions — select from preset providers (OpenAI, Anthropic, Ollama, Groq, Gemini, LiteLLM proxy) or configure custom endpoints and API keys. |
Access Control
| Section | Description |
|---|---|
| IAM | Create and manage users and roles. Assign and revoke role scopes. The role editor shows a live scope-suggestion palette — click any scope pill to add it to the role without typing. Scope strings are served by GET /admin/scopes and cover every CRUD model, workflow, and system scope registered in the engine. Full CRUD for the built-in identity and access management layer. |
| Federation | Configure OAuth2/OIDC federation providers (Google, GitHub, Microsoft, and custom). Manage client credentials and scopes. |
Infrastructure & Reference
| Section | Description |
|---|---|
| Settings | Configure infrastructure components — Redis connection, the CRUD API kill switch (API Access section), and other runtime settings. |
| API Docs | Embedded Swagger UI and ReDoc viewer for the live tuvl REST API, including all dynamically-generated workflow and model endpoints. |
Execution Testing
| Feature | Where | Description |
|---|---|---|
| Tuvl Lens | Spectrum page (/insight) |
Execute a single workflow node in isolation with mock state — unit-test individual steps without running a full workflow |
| Tuvl Spectrum | Spectrum page (/insight) |
Run a complete workflow and capture a deep-copy state snapshot after every step — full execution trace with per-step timing |
| Workflow Canvas Test Mode | Workflow editor toolbar | Run any workflow directly from the canvas with a custom JSON input. Results stream live: nodes light up with status badges, the right-hand Test Out panel shows a step-by-step context diff, and a bottom status bar tracks overall progress |
| Agents dashboard | Agents page (/insight) |
Live view of in-flight autonomous Agent runs — iteration/token progress, an expandable per-run trace timeline, and pause / resume / abort / steer controls (operator API: /api/agents/runs) |
| Agent Supervisor | spec.supervisor (workflow YAML) — or the off-spine Supervisor node in the Insight canvas |
A per-workflow watcher that observes each autonomous Agent run and pauses/aborts/steers it via deterministic rules and/or an LLM judge (criteria — inline policy text or an artifact:// steering artifact) — breaking a runaway loop mid-flight. Author it visually: drop the Supervisor node, pick a model + policy, and it serializes to spec.supervisor. Emits OTel agent metrics (agent.iterations/tool_calls/aborts/…) |
| Portal UI | GET /insight |
Browser interface to the full developer portal |
Workflow Canvas Test Mode — at a glance
- Open a workflow in the editor and click ▶ Test in the toolbar.
- Fill the input JSON — required fields are auto-scaffolded from the workflow's
input_schema. - Click ▶ Run. The canvas switches automatically to test mode:
- Each node shows a live status badge (⏳ running, ✓ success, ✗ error).
- The Test Out panel opens on the right, streaming step results as they arrive.
- Click any step to inspect the before/after context diff with Logs and Error sub-tabs.
- The Final State entry shows every key in the terminal context.
- A status bar at the bottom of the canvas shows overall result and a one-click View Output shortcut.
🔒 Security & Multi-Tenancy
tuvl ships single-tenant by default and multi-tenant by opt-in. Both modes share the same auth, observability, and hardening primitives.
Authentication & Identity
- Biscuit tokens — cryptographically attenuable bearer tokens with offline verification. The engine fails closed in production if no signing key is configured.
- Dev sentinel — a fixed sentinel keypair is used only when
TUVL_DEV_MODE=true; production startup refuses to boot with the sentinel active. - User identity propagation —
current_user_idis bound from the verified Biscuitprincipal()fact and attached to every log line and OpenTelemetry span. - Token revocation — a Redis-backed blacklist supports immediate revocation; the UI revokes any prior session token before storing a new one on login.
Multi-Tenant Mode (tuvl init --multi-tenant)
| Layer | Guarantee |
|---|---|
| Tenant binding | tenant() fact extracted from the Biscuit at verification time → pushed into current_tenant_id contextvar (sync + async + gRPC) |
| Fail-closed boundary | MissingTenantContextError raised at the data-access layer when the contextvar is unset — never silently leaks across tenants |
| Postgres RLS | tuvl db generate-rls emits per-model policies keyed on app.current_tenant_id; a SQLAlchemy after_begin hook issues SET LOCAL app.current_tenant_id for every session |
| Defence in depth | Raw-SQL paths (RAG, search) guard the tenant filter at the query builder; tuvl db check-rls is a CI-friendly verifier |
| Cross-version safety | Versioned model tables use a composite key including tenant_id to prevent cross-tenant collisions on shared sequences |
Embed the engine in your own host application via the public tuvl.tenancy module:
from tuvl.tenancy import tenant_scope
# Set the tenant before invoking any tuvl handler. Every DB session opened
# inside the scope binds `SET LOCAL app.current_tenant_id`, so RLS isolates
# the tenant automatically. Use `async_tenant_scope` from async code.
with tenant_scope("acme-corp"):
... # call your mounted tuvl workflow endpoints / handlers here
Hardening Defaults
- CRUD endpoint scope enforcement — every auto-generated model route (
/models/{model}/…) enforces a Biscuitscope()fact. Default names are{modelname.lower()}:read,{modelname.lower()}:write,{modelname.lower()}:delete; override per model withspec.accessin theModelDefinitionYAML, optionally pinning a tier to specific IAM groups viaspec.access.{read,write,delete}_groups(scope AND group both required; an undeclared tier cascades down from the next-more-privileged one). Tokens carryingiam:adminsatisfy every scope and group check. - CRUD kill switch — turn off the entire
/models/*surface project-wide withspec.api.expose_model_crud: falsein.tuvl/system.yaml, or theTUVL_EXPOSE_MODEL_CRUDenv var (wins over the YAML); toggle it live from the Insight Settings page in dev mode. Restart required to take effect. - Workflow trigger default-deny — in production, every trigger route (REST mount, versioned run route, gRPC
RunWorkflow) requires a valid bearer token even when the workflow declares norequired_scope/required_group; anonymous access needs an explicitspec.trigger.public: true. Dev mode exempts unscoped workflows so quickstarts run tokenless. - Secrets file modes —
.tuvl/.dev-sessionand.envare created with mode0600so other accounts on shared hosts can't read them. - Dev key handling — never echoed to the terminal by default (
tuvl dev --show-keyopts in); always persisted owner-only. - UI session storage — the dev portal stores the bearer key in
sessionStorage(tab-scoped) with a 15-minute idle expiry watchdog. - Markdown rendering —
rehype-sanitizeis applied to everyReactMarkdownmount to neutralise XSS in user-controlled prompt output. - Env-driven UI config — REST and gRPC base URLs come from
VITE_API_BASE/VITE_GRPC_BASE; the Vite dev proxy honoursVITE_API_TARGETfor remote engine connections. - Verbose error logging — gated behind
import.meta.env.DEVso production bundles don't leak structured RPC errors to the console. - Allowlists —
tuvl run --allow-hostaccepts IP and CIDR ranges; localhost is always permitted. - Pooling & limits — connection pool sizing is configurable; a shared HTTP client is reused across API-call steps.
Observability
- Structured JSON logs via
structlogwithtenant_id,user_id,workflow,step_id, andrun_idon every record. - OpenTelemetry spans cover HTTP requests, workflow executions, individual steps, LLM calls, and database transactions — see docs/observability.md.
- HITL (human-in-the-loop) state machine emits dedicated events for pause, resume, and timeout transitions.
License & contributing
tuvl is licensed under the MIT License — see LICENSE — and will remain MIT-licensed. It is, and will stay, free to use, modify, and distribute.
Development currently happens in a private repository, so the full source is not yet publicly browsable. This is temporary: the complete codebase will be published openly in a future release. In the meantime the project is open to contribution — bug reports, feature proposals, and patches are all welcome. See CONTRIBUTING to get started, or reach out at developer@tuvl.io.
Metadata
Release files for tuvl 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tuvl-1.0.1.tar.gz | 7.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tuvl-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 7.5 MB
Release files / tuvl-1.0.1.tar.gz
| Download URL | tuvl-1.0.1.tar.gz |
|---|---|
| Size | 7.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
69f114cbe8883fe2739801414c3be5ef3394a892d39fca89e7d298ff6cfd4a3e
|
|
BLAKE2b-256 checksum How to use checksums |
cc26d0847f858cf0ff770fa3995a000298f2f6ac05cfafb98ef543a7640ff832
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.14
|
Release files / tuvl-1.0.1-py3-none-any.whl
| Download URL | tuvl-1.0.1-py3-none-any.whl |
|---|---|
| Size | 345.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0742afd1ab032388fb0fca09750ebcada33e8d378b75aeb7412868fa9d166e29
|
|
BLAKE2b-256 checksum How to use checksums |
a58e27170d3e7299093765df8368c6bf772e9e7a49112ceb9fcdc947b1ce2ed6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.14
|