Skaal v0.2.0 · alpha
Infrastructure as Constraints · for Python

Declare the contract.
Skaal picks the stack.

Write latency, durability, throughput, and residency constraints directly in Python. A Z3-backed planner scores your catalog, selects the cheapest backend path that satisfies them, and generates the runtime, Dockerfiles, and Pulumi programs for whatever target you point it at.

Get started Browse the CLI
One app model, many targets Auditable plan.skaal.lock Local · AWS · GCP
app.py Python
from skaal import App, storage
from skaal.storage import Map

app = App("todo")


@storage(
    read_latency="< 10ms",
    durability="strong",
    throughput="> 100 rps",
    retention="30d",
)
class Todos(Map[str, dict]):
    pass

plan.skaal.lock · target=local

Resolved

Storage.Todos

3 candidates evaluated
dynamodb $0.018 / wu · 7ms p50 selected
postgres $0.024 / wu · 12ms p50 cost
sqlite $0 · 5ms p50 throughput
Runs on
Python 3.11+ Z3 solver SQLite · Postgres · Redis DynamoDB · Firestore S3 · GCS · Local FS Pulumi · Docker · Lambda · Cloud Run
How Skaal works

Stop hard-coding the stack into your business logic.

Most frameworks bake an infrastructure choice into your code on day one. Skaal flips that — describe the behavior your code needs, and let the planner resolve a target- specific implementation. Move from local SQLite to DynamoDB by changing a TOML file, not your application.

01 / Declare

Constraints, not connection strings

Express latency, durability, throughput, residency, and access patterns on typed surfaces — Map, Collection, BlobStore, VectorStore.

@storage · @function · @schedule
02 / Catalog

Describe what each environment can do

Per-environment TOML catalogs list available backends with cost, latency, durability, and capability flags. Overlay extends for dev → staging → prod.

catalogs/local.toml · aws.toml
03 / Solve

The Z3 planner picks the cheapest valid path

Each constraint becomes a satisfiability clause. UNSAT comes with closest-match diagnostics: which constraint failed, and how far off each candidate was.

plan.skaal.lock · skaal plan --explain
04 / Generate

Runtime + deploy artifacts, ready to ship

Skaal emits the runtime entry point, Dockerfile, Pulumi program, and stack metadata under artifacts/. skaal deploy hands the rest to Pulumi.

artifacts/ · Lambda · Cloud Run · Docker
Storage surfaces

Six tiers. One declarative API.

Skaal ships first-class surfaces for every storage shape a real application reaches for. Backend choice happens at solve time — your code keeps the same generic API from local dev to production.

Key-value

Map · Collection

Generic Store[T] with cursor pagination, secondary indexes, and per-row TTL.

SQLite Postgres Redis DynamoDB Firestore

Relational

@app.relational

SQLModel-backed entities with Alembic migrations — autogenerate, upgrade, downgrade, drift check, dry-run SQL.

SQLite Postgres Alembic 1.13

Vector

VectorStore

Similarity search with metadata filters. Embeddings declared via __skaal_embeddings__, namespaces per surface.

Chroma Postgres / pgvector Pinecone

Blob

BlobStore · @app.storage(kind="blob")

Object storage with the same constraint vocabulary. Stream uploads, presigned URLs, content-type metadata.

Local FS S3 GCS

Channels

EventLog · Outbox

Durable event streams with consumer groups, replay-by-offset, and an Outbox engine for at-least-once relay.

Local KV Redis Streams SNS · SQS

Compute

@app.function · @app.job · @app.schedule

Functions, background jobs, and cron — with per-function retry, circuit breaker, rate-limit, and bulkhead policies.

Local async Lambda Cloud Run
Catalog overlays

One app. Many environments. Zero forks.

Catalogs inherit and override. Stage a higher-durability prod stack on top of your dev catalog with an extends table — Skaal validates the merged result and surfaces the source of every backend in skaal catalog sources.

catalogs/local.toml

Dev defaults — fast feedback, ephemeral storage, no cloud bills.

# base catalog
[storage.sqlite]
read_latency     = "< 5ms"
durability       = "local"
throughput       = "> 5000 rps"
cost_per_unit    = 0.0
supports_ttl     = true

[storage.redis]
read_latency     = "< 2ms"
durability       = "ephemeral"

catalogs/prod.toml

Inherits local, swaps the storage tier for managed services.

[skaal]
extends = "./local.toml"
remove  = ["storage.sqlite"]

[storage.dynamodb]
read_latency     = "< 8ms"
durability       = "strong"
throughput       = "> 10000 rps"
residency        = "eu-west-1"
cost_per_unit    = 0.018

[storage.dynamodb.deploy]
table_class      = "STANDARD_IA"
billing_mode     = "PAY_PER_REQUEST"
Built-in machinery

The runtime concerns you'd otherwise duct-tape together.

Skaal includes the operational primitives most frameworks leave to the user. Pattern engines and a six-stage migration controller are wired into the runtime — no hand-rolled outbox table, no afternoon spent on Alembic boilerplate.

Pattern engines, batteries-included

EventLog, Projection, Saga, and Outbox are real engines started by the runtime — not metadata stubs. Hook them up with decorators; the dispatch loop does the rest.

  • EventLog — durable streams with consumer groups and replay-by-offset
  • Projection — tail an event log into derived state, with checkpoints
  • Saga — coordinate multi-step workflows with compensations
  • Outbox — at-least-once channel relay, atomic with your write
  • Per-function resilience — retry, circuit breaker, rate limit, bulkhead

Six-stage zero-downtime migrations

Move a tier from one backend to another without dropping traffic. Skaal walks plan, dual-write, backfill, dual-read, cutover, and decommission — each stage explicit, each rollback supported.

Plan
Dual-write
Backfill
Dual-read
Cutover
Decom
  • Relational migrations — Alembic-driven autogenerate, upgrade, downgrade, drift check
  • --dry-run SQL renderer — review the exact statements before they run
  • Schema versioning — multi-backend Alembic projects per app
Command line

From pip install to deployed in five commands.

The CLI is shaped around the planner. Every command operates on the same plan.skaal.lock — there are no hidden side effects, and every artifact is regenerable.

~/projects/todo
$ pip install "skaal[serve,runtime]"
$ skaal init todo # scaffolds ./todo/
$ cd todo

$ skaal run --reload
› watching ./ for changes
› using catalog: catalogs/local.toml
› solve: 3 candidates → sqlite (selected)
✓ http://127.0.0.1:8000  · ready in 412ms

$ skaal plan --catalog catalogs/prod.toml
  Storage.Todos       dynamodb    7ms p50  $0.018/wu
  Storage.Sessions    redis       1ms p50  $0.004/wu
  Compute.create_todo lambda      cold 280ms
✓ resolved · plan.skaal.lock written

$ skaal deploy --target aws --stack prod
› artifacts/ written  · Pulumi up · 14 resources
✓ deployed → https://api.example.com
skaal init Scaffold a starter project with pyproject.toml, a base catalog, and a hello-world app.
skaal run Local dev loop with auto-reload. Mounts ASGI/WSGI apps and starts the pattern engines.
skaal plan Solve the catalog against your constraints. Writes plan.skaal.lock with reasons and rejected candidates.
skaal build Generate the runtime entry point, Dockerfile, and Pulumi program for the resolved plan.
skaal migrate Walk the six migration stages or run Alembic-driven relational schema changes with --dry-run.
skaal catalog Inspect the merged catalog, validate extends overlays, list backend sources.
Built for

The shapes Skaal is meant to support.

Real applications, not toy CRUD. Mount any ASGI or WSGI framework — FastAPI, Starlette, Litestar, Flask, Dash — and let Skaal handle the storage, scheduling, and deployment scaffolding around it.

Backends

API + Postgres + queue

  • FastAPI mounted via mount_asgi
  • SQLModel entities with Alembic migrations
  • Outbox-relayed background jobs
  • Per-route retry & rate-limit policies

RAG & AI

Vector retrieval at scale

  • VectorStore with metadata filters
  • Embeddings declared on the surface
  • BlobStore for source documents
  • Streamed responses via mounted ASGI

Internal tools

Dash app + scheduled ETL

  • Dash mounted via mount_wsgi
  • @app.schedule cron jobs
  • Relational tier as the data layer
  • Local dev → Cloud Run with one catalog swap

Stop writing the same plumbing for every new app.

Declare the contract once. Let Skaal pick the stack, generate the artifacts, and keep your code portable from your laptop to whatever cloud you ship to next.

Install

  • pip install "skaal[serve]"HTTP runtime
  • pip install "skaal[runtime]"+ pattern engines
  • pip install "skaal[vector]"+ vector tier
  • pip install "skaal[deploy]"+ Pulumi templates