Execution graphs

Execution graphs

Wrap work in a task() scope and the SDK records a DAG of everything that happened inside it: LLM calls and your own steps, auto-parented, with status, timings, tokens, cost, and a rules-based failure classification.

research.py
async def research(client):
    with client.task("market-research") as t:
        await client.messages.create(
            model="auto",
            messages=[{"role": "user", "content": "Who are the top 3 competitors?"}],
        )

        try:
            with client.step("fetch-pricing"):
                raise TimeoutError("pricing API timed out")
        except TimeoutError:
            pass  # a step can fail without killing the whole task

    # Re-runs only the failed step, with a rebuilt minimal context, not a
    # replay of the whole transcript. The retry lands in the same graph,
    # linked to the node it replaces via depends_on.
    response = await client.resume(t.task_id)

    return client.get_task_graph(t.task_id)

It works in any framework, with no integration code, because parenting uses context variables rather than a wrapper API.

Failure classification

A failed node is labelled by a rules-based classifier rather than left as a raw traceback. The reasons are:

ReasonMeaning
context_overflowThe request exceeded the model's context window
budgetA provider rate-limit, quota, or billing error: an HTTP 429 lands here
tool_errorA tool or step raised
stallThree consecutive failed same-name siblings: a retry loop
refusalThe model declined to answer
unknownNothing matched

LangGraph

A zero-touch LangGraph adapter is included, which mirrors super-steps into the same task graph. Replace MemorySaver() with FluxCheckpointer(client):

python
from fluxcompute.integrations.langgraph import FluxCheckpointer

app = graph.compile(checkpointer=FluxCheckpointer(flux_client))

The adapter requires LangGraph itself: pip install langgraph.

Known limitation

The adapter mirrors only successful checkpoint writes. When a LangGraph node raises, LangGraph never calls put()/aput() for that step, so the adapter has no hook to observe the failure and it is never recorded in the task graph, including the failure reasons above. If you need failure visibility for a failure-prone node, wrap that node's body in client.step() directly; its auto-parenting works fine alongside FluxCheckpointer.

Resume, and what durability costs

Recording and resume are both free and fully offline. client.resume() works whenever the process that ran the task still holds its graph, with no network call beyond the provider request itself. For a worked run, the company brain example injects a retired model id at one step and resumes exactly that call in the same graph.

What's paid

Durability. If the process that ran the task has since exited, resuming means reconstructing the graph from wherever it was persisted, which needs telemetry to have been on and the hosted platform to have kept a copy. The two ways that can be missing raise different things: with no recovery plugin registered, client.resume() on a task from a dead process raises FluxRecoveryNotInstalled; with a plugin registered that cannot find the task, it raises KeyError, the same as an unknown task_id in-process. See fluxcompute.dev for the durable-recovery and dashboard offering.