Skip to main content

Fronta

Task queue on PostgreSQL for Python. Workers run async def handlers in-process and executables in bubblewrap sandboxes; an optional server exposes the queue over REST and MCP with a small dashboard.

Fronta keeps its tables in a fronta schema of your own PostgreSQL 16+ database; there is no broker. Workers claim rows with SELECT … FOR UPDATE SKIP LOCKED, hold leases renewed by heartbeats, and record every state change in one fenced transaction, so the tasks of a crashed or stalled worker are reaped and retried while attempts remain. Priorities, scheduled runs, dedupe keys, retries with jittered backoff and concurrency limits (per task type and per key) are enforced in the database; attempt timeouts and cancellation by the worker.

Status: alpha. The API and the schema can change between minor versions before 1.0 (see CHANGELOG.md). Linux, Python 3.12–3.14, PostgreSQL 16+.

Install

uv add fronta               # SDK + worker
uv add "fronta[server]"     # + REST/MCP server and dashboard

pip install fronta works the same. Hosts that run sandboxed processes need bwrap (bubblewrap), prlimit (util-linux) and unprivileged user namespaces; import fronta and the SDK work anywhere.

Example

# app/tasks.py
import fronta
from pydantic import BaseModel


class Resize(BaseModel):
    image_id: int
    width: int


@fronta.task("resize", input=Resize, max_attempts=5, attempt_timeout=120)
async def resize(ctx: fronta.Context, job: Resize) -> dict[str, int]:
    await ctx.progress({"stage": "download"})
    ...  # idempotent work that honors CancelledError
    return {"bytes": 12345}


worker = fronta.Worker([resize])
export FRONTA_DSN=postgresql://user:pass@host/db   # the role must be able to create the schema
fronta db init                    # creates schema `fronta`; safe to repeat
fronta worker app.tasks:worker    # runs until SIGTERM/SIGINT
# enqueue.py: any process that reaches the database
import asyncio

import fronta
from app.tasks import Resize, resize


async def main() -> None:
    await fronta.open_pool()  # once, at application start
    try:
        task_id = await resize.enqueue(Resize(image_id=7, width=800), priority=5, key="resize-7")
        print(task_id)
    finally:
        await fronta.close_pool()  # at application shutdown


asyncio.run(main())

enqueue(..., conn=conn) joins your own psycopg transaction instead of using the pool. key dedupes: while a task with the same key is queued or running, enqueue returns its id; once that task has finished, the same key enqueues a new one.

A handler gets the validated input and a Context (task_id, attempt, log, progress(), enqueue(), cancelled, state from the worker lifespan). It must handle asyncio.CancelledError and be safe to run twice: after a lost lease the task runs again.

A sandboxed process task, with a placeholder executable:

class Convert(BaseModel):
    source: str


convert = fronta.process_task(
    "convert",
    ["/usr/bin/convert-tool", "--from-stdin"],  # reads the JSON input on stdin
    input=Convert,
    sandbox=fronta.Sandbox(memory_bytes=512 << 20, cpu_time_s=60, max_pids=16),
    max_concurrency=4,
)

The process runs in a private tmpfs /work without network; its result is {"exit_code", "stdout", "stderr", "truncated"}. Exit code 0 means the task succeeded; anything else fails the attempt.

Server

FRONTA_SERVER_TOKEN=... fronta server      # 127.0.0.1:8000

REST under /api/v1 (task types, enqueue, get, list, cancel), MCP at /mcp, dashboard at /. The SDK only enqueues; inspection and cancellation go through the server. FRONTA_SERVER_TOKEN is required (the server never runs open: even on loopback a browser could be made to cancel or enqueue tasks); every REST and MCP request sends it as Authorization: Bearer <token>. Put a TLS-terminating reverse proxy in front of it outside a private network. Endpoints, inputs and error codes: docs/reference.md.

Deploy

One database, any number of workers, optionally a server. Each process reads FRONTA_* environment variables; FRONTA_DSN is the only required one. Run workers under a supervisor that restarts them: a worker exits 0 after a graceful stop and 70 when a handler ignores cancellation or blocks the event loop.

# /etc/systemd/system/fronta-worker.service
[Unit]
Description=Fronta worker
After=network-online.target

[Service]
User=app
WorkingDirectory=/srv/app
# FRONTA_DSN=... and other FRONTA_* variables; readable by root only (mode 0600)
EnvironmentFile=/etc/fronta/worker.env
ExecStart=/srv/app/.venv/bin/fronta worker app.tasks:worker
# SIGTERM goes to the worker only, which stops its sandboxes itself; SIGKILL to everything
KillMode=mixed
Restart=always
RestartSec=2
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

systemctl enable --now fronta-worker. On SIGTERM the worker stops claiming, lets running attempts finish for FRONTA_GRACE_S, then stops the rest (another grace period for cooperative cancellation, then a kill) and records every outcome before it exits. That takes at most 2 × FRONTA_GRACE_S + 6 × FRONTA_KILL_TIMEOUT_S (91 s with the defaults 30 s and 5 s) unless the database is unreachable or a sandbox cannot be killed: then the worker keeps trying rather than lose an outcome, and systemd's SIGKILL at TimeoutStopSec ends it. That loses no data: sandboxes die with the worker and unrecorded attempts are retried when their lease expires.

Throughput is bounded by the database's commit rate (at least two durable commits per task, plus heartbeats and progress): about 150 no-op tasks/s in total on a laptop PostgreSQL with fsync; a claim costs ~5 ms on a 70k-row queue. Configuration, retry policy, guarantees and the measurements: docs/reference.md.

Not covered

  • Exactly-once side effects: a worker stalled past its lease may still be running while the task is retried elsewhere; the stale attempt's writes to Fronta are rejected, its other effects are not.
  • Workflows, chains, or periodic tasks (only run_at).
  • Schema migrations before 1.0: a release that changes the schema needs fronta db init on a fresh schema.
  • Windows or macOS workers: sandboxes are Linux-only and nothing else is tested there.

Development

uv sync --all-extras
docker run -d --name fronta-test-pg -e POSTGRES_USER=fronta -e POSTGRES_PASSWORD=fronta \
  -e POSTGRES_DB=fronta -p 127.0.0.1:5439:5432 postgres:16
export FRONTA_TEST_DSN=postgresql://fronta:fronta@127.0.0.1:5439/fronta
make check       # lint, format, types, architecture, deps (also the git pre-commit hook)
make checkall    # check + the full test suite + pip-audit

CI runs make checkall on every pull request and push to main, on Python 3.12–3.14 and at the lowest dependency versions the declared bounds allow. To release: set the version (uv version X.Y.Z), add the CHANGELOG section, merge, then push the tag vX.Y.Z from that main commit; the gate runs again, the package goes to PyPI and a GitHub release is created. SPEC.md is the contract.

License

MIT. The dashboard bundles Alpine.js (MIT); see THIRD_PARTY_NOTICES.md.

Metadata

Release files for fronta 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fronta 0.1.0
File Size Uploaded
fronta-0.1.0.tar.gz 66.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fronta 0.1.0
File Interpreter ABI Platform
fronta-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 142.6 kB

Release files / fronta-0.1.0.tar.gz

Download URL fronta-0.1.0.tar.gz
Size 66.5 kB
Tags Source
SHA-256 checksum
How to use checksums
8a44b28ab1ad12e9040191fc17ef65fa06ddd2d6614401270f6f1d7f1fa6164d
BLAKE2b-256 checksum
How to use checksums
5263502d5b43be89972dedba5c852fb399c9612361f129b87b5034391d06e2d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fronta-0.1.0-py3-none-any.whl

Download URL fronta-0.1.0-py3-none-any.whl
Size 76.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9c4b2c353ce36055ae833353f2980631f3500c89e2cbbf91b37cb3ede16b90bf
BLAKE2b-256 checksum
How to use checksums
9008a732ea972b62685683ff7ef9a689686e827ad1a07751339ee42afb6df4ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page