sagaflow
Durable execution for long-running agent workflows, on top of Temporal.
You write a Python workflow that calls models, runs tools, and writes artifacts. sagaflow runs each step as a Temporal activity, so when the worker dies — or a 40-minute fan-out crashes halfway — the next launch resumes from the last completed step instead of starting over. Results land in ~/.sagaflow/INBOX.md whether or not you're still attached to the session that started them.
Quick start
pip install sagaflow
temporal server start-dev &
export ANTHROPIC_API_KEY=sk-ant-...
sagaflow launch hello-world --name alice --await
# → hello, alice
Kill the terminal mid-run and re-launch the same workflow ID: it picks up from where the worker died.
What you get
- Resumes after crashes. Activity-level checkpointing via Temporal — workers, sessions, and laptops can all die without losing in-flight work.
- Decoupled from the caller. Fire-and-forget submissions land in an append-only inbox; a session-start hook surfaces unread results next time you open Claude Code.
- Provider-agnostic transport. Anthropic SDK by default; point
ANTHROPIC_BASE_URLat Bedrock, a model gateway, or any compatible proxy. - Auto-managed worker. First
sagaflow launchspawns a worker daemon;sagaflow doctorreports health.
Install
pip install sagaflow
Requirements:
- Python 3.11+
- Temporal CLI running locally:
brew install temporal && temporal server start-dev - An Anthropic API key (or a compatible proxy via
ANTHROPIC_BASE_URL)
Authoring a skill
A skill is a directory under ~/.claude/skills/<skill-name>/ containing three things:
workflow.py— a Temporal workflow class (the durable orchestration)__init__.py— aregister()function that wires the workflow into sagaflowprompts/*.md— the system/user prompts the workflow's activities load
Here is the complete hello-world skill (the one sagaflow launch hello-world --name alice runs).
~/.claude/skills/hello-world/workflow.py — the durable workflow:
from dataclasses import dataclass
from datetime import timedelta
from temporalio import workflow
with workflow.unsafe.imports_passed_through():
from sagaflow.durable.activities import (
EmitFindingInput, SpawnSubagentInput, WriteArtifactInput,
)
from sagaflow.durable.retry_policies import HAIKU_POLICY
@dataclass(frozen=True)
class HelloWorldInput:
run_id: str
name: str
inbox_path: str
run_dir: str
greeter_system_prompt: str
greeter_user_prompt: str
@workflow.defn(name="HelloWorldWorkflow")
class HelloWorldWorkflow:
@workflow.run
async def run(self, inp: HelloWorldInput) -> str:
prompt_path = f"{inp.run_dir}/prompt.txt"
await workflow.execute_activity(
"write_artifact",
WriteArtifactInput(path=prompt_path, content=inp.greeter_user_prompt),
start_to_close_timeout=timedelta(seconds=10),
retry_policy=HAIKU_POLICY,
)
parsed = await workflow.execute_activity(
"spawn_subagent",
SpawnSubagentInput(
role="greeter", tier_name="HAIKU",
system_prompt=inp.greeter_system_prompt,
user_prompt_path=prompt_path,
max_tokens=64, tools_needed=False,
),
start_to_close_timeout=timedelta(seconds=600),
heartbeat_timeout=timedelta(seconds=120),
retry_policy=HAIKU_POLICY,
)
greeting = parsed.get("GREETING", "hello")
await workflow.execute_activity(
"emit_finding",
EmitFindingInput(
inbox_path=inp.inbox_path, run_id=inp.run_id,
skill="hello-world", status="DONE", summary=greeting,
timestamp_iso=workflow.now().isoformat(timespec="seconds"),
),
start_to_close_timeout=timedelta(seconds=10),
retry_policy=HAIKU_POLICY,
)
return greeting
Each execute_activity call is a checkpoint. If the worker dies between them, replay resumes from the last completed one.
~/.claude/skills/hello-world/__init__.py — registration:
from typing import Any
from sagaflow.durable.activities import emit_finding, spawn_subagent, write_artifact
from sagaflow.prompts import load_prompt
from sagaflow.registry import SkillRegistry, SkillSpec
from skills.hello_world.workflow import HelloWorldInput, HelloWorldWorkflow
def _build_input(*, run_id, run_dir, inbox_path, cli_args: dict[str, Any]) -> HelloWorldInput:
name = str(cli_args.get("name", "world"))
return HelloWorldInput(
run_id=run_id, name=name,
inbox_path=inbox_path, run_dir=run_dir,
greeter_system_prompt=load_prompt(__file__, "greeter.system"),
greeter_user_prompt=load_prompt(__file__, "greeter.user", substitutions={"name": name}),
)
def register(registry: SkillRegistry) -> None:
registry.register(SkillSpec(
name="hello-world",
workflow_cls=HelloWorldWorkflow,
activities=[write_artifact, emit_finding, spawn_subagent],
build_input=_build_input,
))
register() is what the worker calls at startup to discover the skill. _build_input translates CLI args (--name alice) into the workflow's input dataclass and loads prompts from disk.
~/.claude/skills/hello-world/prompts/greeter.system.md:
You are a greeter. Output a greeting using the format
STRUCTURED_OUTPUT_START / GREETING|<text> / STRUCTURED_OUTPUT_END.
Do not include any other text.
~/.claude/skills/hello-world/prompts/greeter.user.md:
Greet $name
That's the whole skill. sagaflow launch hello-world --name alice finds the registration, builds the input, hands the workflow to Temporal, and the worker runs it durably.
CLI
sagaflow launch <name> --arg key=value [--await] # submit a workflow
sagaflow inbox # list unread results
sagaflow dismiss <run-id> # mark as read
sagaflow doctor # diagnose temporal/worker/hook
How it works
sagaflow launch
│
▼
preflight (install hook, spawn worker if missing)
│
▼
Temporal (localhost:7233) ── workflow ID ── worker daemon
│
▼
activities:
• model calls
• file I/O
• inbox emit
│
▼
~/.sagaflow/INBOX.md + desktop notify
│
▼
next session: SessionStart
hook surfaces unread runs
If the worker crashes mid-run, the next sagaflow launch (or the next worker poll) resumes from the last completed activity. Activities that already succeeded don't re-execute.
Development
git clone https://github.com/npow/sagaflow
cd sagaflow
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check sagaflow tests
mypy sagaflow
pytest
# Opt-in end-to-end tests (require live Temporal + real Anthropic access)
SAGAFLOW_E2E=1 pytest
License
Metadata
Release files for sagaflow 0.10.26
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sagaflow-0.10.26.tar.gz | 541.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sagaflow-0.10.26-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 826.8 kB
Release files / sagaflow-0.10.26.tar.gz
| Download URL | sagaflow-0.10.26.tar.gz |
|---|---|
| Size | 541.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
afadeec1209049e0731f05b14bdec25ab497bff74d85a1dfe67081d9c5c0bb59
|
|
BLAKE2b-256 checksum How to use checksums |
c388bbf8492a4b9a52667879b486bbac4c4860a6ccf427c7f239242248a79415
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 2, 2026.
Transparency logRelease files / sagaflow-0.10.26-py3-none-any.whl
| Download URL | sagaflow-0.10.26-py3-none-any.whl |
|---|---|
| Size | 285.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bf61cd4a44fe16bc8f0f519c2b39bb9c9e40b1b5eadbaa7504f26312e1989aff
|
|
BLAKE2b-256 checksum How to use checksums |
9d188eb8a204d6a546bc466f3a3c5755d41547b59af492e5907f18680d21ded9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 2, 2026.
Transparency logSecurity for sagaflow 0.10.26
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sagaflow-0.10.26.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.