Render Workflows | Render
Render Workflows
Run thousands of parallel tasks with zero ops overhead. Scale seamlessly, retry automatically, and debug with full visibility.
Early access to Python and TypeScript SDKs begins soon.
@task
async def analyze_documents(file_paths: list[str]):
files = await get_files(file_paths)
results = await asyncio.gather(
*[summarize_with_llm(path) for path in files]
)
return results
@task(
options=Options(
retry=Retry(max_retries=3, factor=2)
)
)
def summarize_with_llm(path: str) -> dict:
text = read_file(path)
return call_llm_for_summary(text)
Smarter background jobs, right on queue
Model your batch processes and agentic workloads as composable tasks. Branch, retry, and fan out with ease.
SDK-first
Use lightweight TypeScript and Python SDKs to define tasks in your code with clean, declarative syntax. No DSLs or YAML required.
Queue-based autoscaling
Worker pools scale in seconds to handle bursty workloads. Scale down to zero when the task queue is empty.
Durable execution
Automatically retry failed tasks and surface failure patterns. Restore from stateful checkpoints without losing work.
Complete observability
Track, debug, and monitor runs in real time. Visualize bottlenecks in parallel runs with built-in instrumentation.
Your stack's elastic execution engine
Workflow steps are queued and scheduled on workers that spin up on demand. You define the jobs, and Render’s engine handles the distribution.
Engineered for your most demanding workloads
Dispatch entire fleets of long-lived, stateful compute—perfect for AI agents, ETL pipelines, and event-driven architectures.
Durable
Automatic retries
Avoid hitting rate limits with exponential backoff.
Stateful checkpointing
Restore to your last healthy state after an interruption.
Idempotent execution
Avoid duplicated work, even across retries.
Scalable
Fan out in seconds
Spin up hundreds or even thousands of workers when your queue spikes.
Run for hours
Go beyond 15-minute serverless limits—tasks can stay active for a day or more.
Scale to zero
Workers automatically spin down when there’s nothing to work on. Only pay for what you use.
Effortless
Drop-in libraries
Start running with just a few lines of code—no heavy frameworks or steep learning curve.
Local dev support
Iterate on your machine, then scale on ours.
First-class observability
View per-task logs, retries, and timelines.
Declaratively distributed
Express tasks as functions in Python, TypeScript, or Go. Render takes it from there: provisioning, monitoring, retries, and more.
@task
async def query_llms_and_evaluate(prompt: str) -> str:
# Query 3 LLMs in parallel using model names
model_configs = [
{"provider": "openai", "model": "gpt-5"},
{"provider": "anthropic", "model": "claude-opus-4"},
{"provider": "google", "model": "gemini-2.5-pro"},
]
responses = await asyncio.gather(
*[query_llm(cfg["provider"], cfg["model"], prompt) for cfg in model_configs]
)
# Have a 4th LLM evaluate and select the best response
return await select_best_result(responses)
@task(
options=Options(
retry=Retry(max_retries=3, wait_duration_ms=5000, factor=2)
)
)
async def query_llm(provider: str, model_name: str, prompt: str) -> str:
providers = {
"openai": ChatOpenAI,
"anthropic": ChatAnthropic,
"google": ChatGoogleGenerativeAI,
}
# Construct the appropriate LLM client based on provider
llm = providers[provider](model=model_name)
response = await llm.ainvoke([HumanMessage(content=prompt)])
return response.content
@task(
options=Options(
retry=Retry(max_retries=2, wait_duration_ms=1000, factor=1.5)
)
)
async def select_best_result(responses: list[str]) -> str:
evaluator = ChatOpenAI(model="gpt-5")
eval_prompt = (
"Which response is best?\n\n + " +
"\n".join(f"{i}: {{r}}" for i, r in enumerate(responses))
)
evaluation = await evaluator.ainvoke([HumanMessage(content=eval_prompt)])
return evaluation.content
Ready to queue up?
Workflows are launching soon. Request early access and help shape the future of background orchestration.