Open source · Apache 2.0 · workflows + agents

Durable workflows. Durable agents.

Flux is a distributed workflow engine for Python, with a first-class agent framework built in. Async-native. Type-safe. Multi-provider. Build agents that pause, resume, retry, and replay like the workflows they actually are.

Star on GitHub
The case for Flux

Most engines pick one or two. Flux gives you all three, by default.

01 / STATEFUL

Workflows that remember.

Every step is checkpointed. Every event is replayable. Pause a workflow for a human approval today, resume it tomorrow with new input. State is durable across restarts, deploys, and failures — and when a hot path doesn't need replay, mark it transient and skip the bookkeeping.

02 / DISTRIBUTED

Scale without rewriting.

Run locally as a script, then drop in workers when you need to scale. One Python file, n servers, n workers, coordinated over Server-Sent Events and PostgreSQL with resource-aware task assignment.

03 / RESILIENT

Built to recover.

Retries with exponential backoff. Fallbacks. Rollbacks. Timeouts that fire. Cancellation that propagates. The same machinery that makes a payment workflow durable makes an AI agent survive a flaky LLM call.

Agents, built on workflows

AI agents that don't lose their place.

Most agent frameworks treat durability as an afterthought. Flux makes it the foundation. Pause for human approval. Resume after a crash. Replay deterministically to debug. Your agent inherits all of it, because your agent is a workflow.

Providers
Anthropic claude-sonnet-4.5
OpenAI gpt-4o
Gemini gemini-2.5
Ollama local · llama · qwen · mistral

One primitive. Every pattern.

The agent() primitive returns a Flux task that calls an LLM. Add tools. Add skills. Add memory. Delegate to sub-agents. Stream tokens. Get structured output.

Every option is composable. Every call is checkpointed. Switch from GPT-4o to Claude to a local Ollama model by changing one string. The rest of your code doesn't move.

Multi-turn conversation
workflow.pause / resume
Survives a crash mid-run
event checkpointing
Deterministic debugging
event replay
Retry on transient LLM error
task retry policy
Scale across machines
distributed workers
agent_signature.py
# The full agent() signature — every capability declared in one place.

from flux.tasks.ai import agent

assistant = await agent(
    system_prompt="You are a thoughtful research assistant.",
    model="anthropic/claude-sonnet-4-5",

    # Capabilities — all optional, all composable
    tools=[search, fetch_page, summarize],
    skills=skill_catalog,             # SKILL.md files (agentskills.io)
    agents=[fact_checker, editor],    # sub-agent delegation

    # Reasoning & planning
    planning=True,
    max_plan_steps=15,
    approve_plan=True,             # human approves the plan first
    reasoning_effort="medium",

    # Memory
    working_memory=working_memory(),
    long_term_memory=long_term_memory(provider=sqlite("mem.db")),

    # Output & streaming
    response_format=Briefing,         # Pydantic typed output
    stream=True,

    # Safety rails
    max_tool_calls=20,
    max_tokens=4096,

    # Lifecycle hooks
    on_complete=[dream],              # consolidate memory in background
    on_pause=[notify_user],
)
Capabilities, in detail

Everything an agent needs. Nothing it doesn't.

Tools

Any task is a tool

Annotate a Python function with @task, hand it to agent(tools=[...]), and the LLM can call it. Tool calls are checkpointed events: replay them, retry them, audit them.

tools=[search_web, fetch_page,
       send_email, deploy_pr]
Skills

SKILL.md, discovered

Implements the agentskills.io open standard. Drop SKILL.md files in a directory; the catalog discovers them at runtime. The LLM picks which to activate per task.

SkillCatalog.from_directory(
    "./skills"
)
Memory

Working & long-term

Working memory holds the live conversation and tool results. Long-term memory persists across sessions in SQLite or Postgres, scoped per user, per agent, per anything.

working_memory=working_memory(),
long_term_memory=long_term_memory(
    provider=sqlite("mem.db"),
    scope="user:alice",
)
Dreaming

Background consolidation

A four-phase workflow (orient, gather signal, consolidate, prune) that runs after the agent finishes. Distills working memory into clean long-term knowledge while you're not watching.

from flux.tasks.ai.dreaming import dream
agent(..., on_complete=[dream])
Planning

Plan, approve, execute

Set planning=True and the agent drafts an explicit plan before acting. Optionally pause for human approval. Replan dynamically on failure.

planning=True,
approve_plan=True,
max_plan_steps=15,
Delegation

Sub-agents as tools

Pass other agents into the agents= parameter. A lead agent can delegate to specialists, and each delegation runs as its own workflow execution with its own tools and prompt.

lead = await agent(
    ...,
    agents=[researcher, coder],
)
Streaming

Tokens in real time

Stream LLM tokens to the client as they arrive over Server-Sent Events. State checkpoints don't block the stream, so you get observability and durability at the same time.

agent(..., stream=True)
Structured output

Typed responses

Pass a Pydantic model to response_format= and the agent returns instances of that type. No regex parsing. No JSON repair loops.

class Briefing(BaseModel):
    summary: str
    citations: list[str]

agent(..., response_format=Briefing)
MCP

Tool discovery via protocol

Connect to any MCP server and the agent picks up its tools dynamically. Bidirectional: Flux can host an MCP server too, exposing your workflows to other agents.

mcp_servers=[
  {"url": "https://mcp.github.io",
   "auth": "bearer"}
]
Or skip the code. Declare your agent in YAML.
flux agent start coder --mode terminal
coder.yaml
name: coder
model: anthropic/claude-sonnet-4-5
description: Senior engineer with workspace access

system_prompt: |
  You are a senior software engineer. Use the
  available tools to read, search, and edit
  files. Run the test suite after changes.

tools:
  - system_tools:
      workspace: .
      timeout: 60

mcp_servers:
  - url: https://mcp.github.example.com
    auth: bearer
    secret: GITHUB_TOKEN

planning: true
max_plan_steps: 15
max_tool_calls: 30
reasoning_effort: medium
stream: true
in your terminal
# Create the agent
$ flux agent create coder \
    --file coder.yaml

# Start it — terminal, API, or web UI
$ flux agent start coder \
    --mode terminal

# …or expose it as an HTTP API
$ flux agent start coder \
    --mode api --port 9100

# Compose with other agents
$ flux agent create lead \
    --file delegation.yaml
# lead.yaml lists "agents: [coder, researcher]"
# the lead delegates each sub-task to the right specialist
Under the hood

Server, workers, state. Open at every layer.

Flux is a small set of cooperating processes connected over standard protocols. Run it on your laptop. Run it across a cluster. The shape of the system stays the same.

CLIENTS SERVER WORKERS STORAGE CLI flux ... REST API /docs · OpenAPI MCP sse · http · stdio Python SDK workflow.run() FLUX SERVER · COORDINATOR workflow catalog registered · versioned scheduling cron · interval · once dispatching resource-aware assignment worker registry auth · sessions · resources secrets management encrypted vault workflows as services REST + MCP, automatic AUTH & SECURITY · CROSS-CUTTING OIDC · JWT API keys RBAC · wildcards AES-256-GCM checkpoints worker · w-01 cpu · 4.2/8 mem · 1.2/8GB worker · w-02 cpu · 6.8/8 mem · 3.4/16GB worker · w-03 cpu · 2.1/8 gpu · A100 worker · w-04 cpu · 5.5/16 mem · 6.7/32GB + scale events · artifacts · checkpoints · registry encrypted at rest
01 / CLIENTS

However you reach it.

  • CLIWorkflow, schedule, agent, secret, worker management. All from flux ....
  • REST APIFastAPI under the hood. Swagger UI at /docs. Anything that speaks HTTP works.
  • MCP serverModel Context Protocol. Expose workflows to AI agents over SSE, http, or stdio.
  • Python SDKRun workflows in-process for tests and scripts. No server required.
02 / SERVER

The coordinator.

  • Workflow catalogRegister Python files; the server tracks every workflow, its version, its schema.
  • SchedulingCron, interval, one-time. Auto-created when workflows register. Pause, resume, modify.
  • DispatchingMatches executions to workers on resources, labels, and free capacity slots. Polling or event-driven, one config key.
  • Worker registryBootstrap-token auth. Live tracking of CPU/memory/GPU. Session management.
  • ReplicatedRun one server or several. Replicas coordinate through PostgreSQL — no leader election, no extra moving parts.
  • Secret managementEncrypted vault with per-task secret_requests injection.
  • Workflows as servicesEvery registered workflow is automatically a REST endpoint and an MCP tool.
03 / WORKERS

Where the work runs.

  • Auto-registeringBoot, present a token, get a session. Reconnect on network loss.
  • Resource-awareReport CPU, memory, GPU, and concurrency slots. Server matches load to capacity.
  • SSE-drivenReceive scheduled, resumed, and cancelled events as they happen.
  • Isolated runsEach execution gets its own credential-less subprocess by default — or run in-process, or in Docker.
  • CheckpointingSend execution state back to the server after each step.
  • Horizontally scaledAdd more, anywhere. Drain gracefully on SIGTERM — finish the work, flush state, exit.
04 / STORAGE

The single source of truth.

  • EventsEvery state change: task started, retried, cached, paused, completed.
  • ArtifactsTask inputs, outputs, intermediate results. Addressable, replayable, cacheable.
  • CheckpointsResume any execution from its last good state. Survive crashes and restarts.
  • Replayable historyWalk through a past execution event-by-event. Deterministic debug. Retention sweeps are one config away.
  • Pluggable backendSQLite for local. PostgreSQL for clusters, with schema migrations managed for you. Same API.
Secure at every layer.
Cross-cutting
Authentication

Multiple identity providers.

Use your existing IdP via OpenID Connect, or issue API keys for service accounts. Discovery, JWKS rotation, and clock-skew tolerance all built in.

  • OIDC with JWT validation
  • API keys (SHA-256 hashed)
  • Bootstrap tokens for workers
  • Execution tokens, HMAC-signed
Authorization

RBAC with wildcard permissions.

Permissions are colon-separated and support wildcards at any segment. Built-in roles cover common cases; custom roles compose freely.

  • workflow:*:*:run
  • execution:*:read
  • Roles: admin · operator · viewer · worker
  • Per-task permission tree
Encryption at rest

AES-256-GCM, with PBKDF2.

Sensitive fields are encrypted before they touch the database. Authenticated encryption ensures tampering is detectable, not just decryption-blocking.

  • AES-256-GCM cipher
  • PBKDF2 · 1M iterations · SHA-256
  • Per-record 32-byte salt
  • Per-task secret_requests injection
Identity & principals

Service accounts and users.

A unified principal registry tracks who's who, what they can do, and when they last connected. Disable access without deleting history.

  • Service accounts & user identities
  • External issuer binding (OIDC)
  • Last-seen tracking
  • Enable / disable, never delete
The runtime

Skip the platform engineering. It's already in here.

Retries with backoff

Configurable max attempts, initial delay, exponential multiplier, per task.

Timeouts that fire

Hard cutoffs per task. Hung HTTP calls, runaway loops, deadlocks: all bounded.

Fallback & rollback

Define an alternate path on failure. Or compensate: close the file, refund the charge.

Authentication, pluggable

OIDC for humans, API keys for services, execution tokens for workflows. JWT-validated, SHA-256-hashed, expirable.

RBAC with wildcards

Colon-separated permissions like workflow:*:*:run. Built-in roles: admin, operator, viewer, worker.

Encryption at rest

AES-256-GCM with PBKDF2 (1M iterations, SHA-256). Per-record salt. Authenticated, so tampering is detectable, not just decryption-resistant.

Auto-scheduling

Cron, interval, one-time. Schedules created when workflows register. Pause, resume, modify.

Pause & resume

Wait for an external trigger, an input, an upstream signal. Resume with new data, minutes or months later.

Graceful cancellation

Cancel running workflows. CancelledError propagates so rollback handlers run.

Task caching

Set cache=True. Identical inputs return memoized results: expensive computation runs once.

Deterministic replay

Every event is recorded. Replay a workflow event-by-event to debug exactly what happened. No “works on my machine”.

Durable — or transient

Checkpoint every step by default. Or mark a workflow durability="transient" and skip the bookkeeping when speed beats replay.

Resource-aware workers

Workers report CPU, memory, GPU, free slots, and live metrics. Tasks declare requirements. Scoring policies rank the eligible workers — or the server just picks the least loaded.

Subworkflows

Workflows can call other workflows. Each gets its own execution, its own state, its own retries. Composition without coupling.

Sync, async, or stream

Run a workflow and wait. Or fire-and-forget. Or subscribe to its events over SSE. Same workflow, three execution modes.

Built-in tasks

parallel, pipeline, Graph, sleep, pause, now, uuid4, choice. The orchestration primitives, batteries included.

Type-safe by default

ExecutionContext[T] is generic. Type hints on tasks. Pyright-checked. Errors caught before they ship.

FastAPI included

REST API for workflows, schedules, secrets, workers. Swagger UI at /docs. Plug it into anything that speaks HTTP.

The API

Decorators in. Reliability out.

Flux is the workflow engine that gets out of your way. Two decorators, full type hints, async/await throughout. Then everything you'd expect from a durable runtime, for free.

workflows.py
# A real agent loop. Multi-turn conversation that survives restarts.
# Pattern from examples/ai/conversational_agent_anthropic.py

from flux import task, workflow, ExecutionContext
from flux.tasks import pause
from anthropic import AsyncAnthropic

@task.with_options(
    secret_requests=["ANTHROPIC_API_KEY"],
    retry_max_attempts=3,
    retry_backoff=2,
    timeout=60,
)
async def conversation_turn(messages, user_message, system_prompt, secrets={}):
    """One turn: append user message, call Claude, append response."""
    messages.append({"role": "user", "content": user_message})

    client = AsyncAnthropic(api_key=secrets["ANTHROPIC_API_KEY"])
    response = await client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        system=system_prompt,
        messages=messages,
    )

    reply = response.content[0].text
    messages.append({"role": "assistant", "content": reply})
    return messages, reply

@workflow
async def conversational_agent(ctx: ExecutionContext[dict]):
    initial = ctx.input or {}
    system_prompt = initial.get("system_prompt", "You are a helpful assistant.")
    max_turns = initial.get("max_turns", 10)

    messages = []
    messages, _ = await conversation_turn(messages, initial["message"], system_prompt)

    # The loop: pause for the next user message, run the turn, repeat.
    # Each pause is a durable checkpoint — resume tomorrow if you want.
    for turn in range(1, max_turns):
        resume_input = await pause(f"awaiting_user_input_turn_{turn}")
        next_message = (resume_input or {}).get("message")
        if not next_message:
            break
        messages, _ = await conversation_turn(messages, next_message, system_prompt)

    return {"history": messages, "turns": len(messages) // 2}
# Any @task becomes a tool. The agent decides when to call it.
# Tool calls are checkpointed — debug-replay them deterministically.

from flux import task, workflow, ExecutionContext
from flux.tasks.ai import agent
from pydantic import BaseModel

@task
async def get_weather(city: str) -> dict:
    """Fetch current weather for a city."""
    return await open_meteo.fetch(city)

@task
async def get_forecast(city: str, days: int = 3) -> list:
    """Multi-day forecast for a city."""
    return await open_meteo.forecast(city, days)

class Forecast(BaseModel):
    summary: str
    high_c: float
    advice: str

@workflow
async def weather_briefing(ctx: ExecutionContext[str]):
    briefer = await agent(
        system_prompt="Brief the user on weather and travel conditions.",
        model="anthropic/claude-sonnet-4-5",
        tools=[get_weather, get_forecast],
        response_format=Forecast,    # typed output via Pydantic
    )
    return await briefer(ctx.input)
# Working memory + long-term memory. Plus dreaming —
# a background workflow that consolidates what mattered.

from flux import workflow, ExecutionContext
from flux.tasks.ai import agent, system_tools
from flux.tasks.ai.memory import working_memory, long_term_memory, sqlite
from flux.tasks.ai.dreaming import dream

@workflow
async def research_assistant(ctx: ExecutionContext[dict]):
    assistant = await agent(
        system_prompt="Research, store findings, recall when useful.",
        model="anthropic/claude-sonnet-4-5",
        tools=[system_tools(workspace=".")],
        working_memory=working_memory(),
        long_term_memory=long_term_memory(
            provider=sqlite("memory.db"),
            agent="researcher",
            scope="user:alice",
        ),
        on_complete=[dream],   # consolidate memory in the background
    )
    return await assistant(ctx.input["message"])
# Run tasks concurrently. Flux gathers, checkpoints, returns.

from flux import task, workflow, ExecutionContext
from flux.tasks import parallel

@task
async def say_hi(name): return f"Hi, {name}"

@task
async def say_hello(name): return f"Hello, {name}"

@task
async def say_hola(name): return f"Hola, {name}"

@workflow
async def greetings(ctx: ExecutionContext[str]):
    return await parallel(
        say_hi(ctx.input),
        say_hello(ctx.input),
        say_hola(ctx.input),
    )
# Chain tasks into a pipeline. Output flows in, transformed out.

from flux import task, workflow, ExecutionContext
from flux.tasks import pipeline

@task
async def multiply_by_two(x): return x * 2

@task
async def add_three(x): return x + 3

@task
async def square(x): return x * x

@workflow
async def transform(ctx: ExecutionContext[int]):
    return await pipeline(
        multiply_by_two,
        add_three,
        square,
        input=ctx.input,
    )
# Retries, fallbacks, rollbacks, timeouts — all declarative.

from flux import task, workflow, ExecutionContext

async def cleanup(): ...
async def use_cache(): return "stale-but-safe"

@task.with_options(
    retry_max_attempts=3,
    retry_delay=1,
    retry_backoff=2,
    timeout=30,
    fallback=use_cache,
    rollback=cleanup,
    secret_requests=['API_KEY'],
    cache=True,
)
async def charge_customer(amount, secrets: dict = {}):
    return await stripe.charge(amount, key=secrets['API_KEY'])
# Auto-schedule on registration. Cron, intervals, or one-time.

from flux import workflow, task, cron, ExecutionContext

@task
async def generate_report(date: str):
    return f"Report for {date}"

@workflow.with_options(
    name="daily_report",
    schedule=cron("0 9 * * MON-FRI", timezone="UTC"),
)
async def daily_report(ctx: ExecutionContext):
    report = await generate_report("today")
    return {"report": report}

# $ flux workflow register reports.py
# $ flux schedule list  →  daily_report_auto · 0 9 * * MON-FRI
# When linear isn't enough: define workflows as DAGs.

from flux import task, workflow, ExecutionContext
from flux.tasks import Graph

@task
async def fetch(id): ...

@task
async def parse(data): ...

@task
async def enrich(parsed): ...

@task
async def commit(enriched): ...

@workflow
async def ingest(ctx: ExecutionContext[str]):
    g = (Graph("ingest")
         .add_node("fetch", fetch)
         .add_node("parse", parse)
         .add_node("enrich", enrich)
         .add_node("commit", commit)
         .add_edge("fetch", "parse")
         .add_edge("parse", "enrich")
         .add_edge("enrich", "commit")
         .start_with("fetch")
         .end_with("commit"))
    return await g(ctx.input)
Coming soon

Flux Cloud. Same code. Zero ops.

The same engine you run locally, now managed and observable. Hosted workers, durable Postgres, replay debugging, traces. Workflows and agents, both. Drop the @workflow decorator and ship to production.

Early access opens in waves. No spam. Unsubscribe at any time.

You’re on the list.

We’ll be in touch when access opens.

Something went wrong. Please try again, or email hello@fluxhq.dev.