← Files RenderARCHIVED FILE
skills/render-workflows/references/task-patterns.md
6.58 KB · Sep 30, 2026 · 23:18 UTC
# Task Patterns
Common workflow patterns for both Python and TypeScript.
## Contents
- Fan-out / fan-in
- ETL pipeline
- Retry-heavy tasks
- Error handling in tasks
- Integration patterns (cron triggers, cross-workflow calls)
## Fan-Out / Fan-In
Process a batch of items in parallel using subtasks, then aggregate results.
**Python:**
```python
import asyncio
@app.task
async def process_batch(image_urls: list[str]) -> dict:
results = await asyncio.gather(
*[process_image(url) for url in image_urls]
)
successful = sum(1 for r in results if r["success"])
return {
"total": len(image_urls),
"processed": successful,
"failed": len(image_urls) - successful,
"results": list(results),
}
@app.task
def process_image(url: str) -> dict:
return {"url": url, "success": True}
```
**TypeScript:**
```typescript
const processImage = task(
{ name: "processImage" },
function processImage(url: string): { url: string; success: boolean } {
return { url, success: true };
},
);
task(
{ name: "processBatch" },
async function processBatch(imageUrls: string[]): Promise<object> {
const results = await Promise.all(
imageUrls.map(url => processImage(url)),
);
const successful = results.filter(r => r.success).length;
return {
total: imageUrls.length,
processed: successful,
failed: imageUrls.length - successful,
results,
};
},
);
```
## ETL Pipeline
Extract, transform, load pattern with chained subtasks.
**Python:**
```python
@app.task
async def etl_pipeline(source: str, destination: str) -> dict:
raw_data = await extract(source)
transformed = await transform(raw_data)
result = await load(destination, transformed)
return result
@app.task
def extract(source: str) -> dict:
return {"records": []}
@app.task
def transform(data: dict) -> dict:
return {"records": []}
@app.task
def load(destination: str, data: dict) -> dict:
return {"loaded": len(data["records"])}
```
**TypeScript:**
```typescript
const extract = task(
{ name: "extract" },
function extract(source: string): object {
return { records: [] };
},
);
const transform = task(
{ name: "transform" },
function transform(data: object): object {
return { records: [] };
},
);
const load = task(
{ name: "load" },
function load(destination: string, data: object): object {
return { loaded: 0 };
},
);
task(
{ name: "etlPipeline" },
async function etlPipeline(source: string, destination: string): Promise<object> {
const rawData = await extract(source);
const transformed = await transform(rawData);
return await load(destination, transformed);
},
);
```
## Retry-Heavy Tasks
Tasks with aggressive retry for unreliable external APIs.
**Python:**
```python
from render_sdk import Retry
@app.task(retry=Retry(max_retries=5, wait_duration_ms=2000, backoff_scaling=2.0))
def call_external_api(endpoint: str, payload: dict) -> dict:
import urllib.request
import json
req = urllib.request.Request(
endpoint,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
```
**TypeScript:**
```typescript
task(
{
name: "callExternalApi",
retry: { maxRetries: 5, waitDurationMs: 2000, backoffScaling: 2.0 },
timeoutSeconds: 60,
},
async function callExternalApi(endpoint: string, payload: object): Promise<object> {
const resp = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json();
},
);
```
## Error Handling in Tasks
Tasks that raise exceptions are retried automatically according to their retry config. Use this intentionally. For common execution issues and fixes, see [troubleshooting.md](troubleshooting.md).
**Python:**
```python
@app.task(retry=Retry(max_retries=3, wait_duration_ms=1000))
def resilient_task(data: dict) -> dict:
result = process(data)
if not result["valid"]:
raise ValueError("Invalid result, retrying...")
return result
```
**TypeScript:**
```typescript
task(
{
name: "resilientTask",
retry: { maxRetries: 3, waitDurationMs: 1000 },
},
function resilientTask(data: object): object {
const result = process(data);
if (!result.valid) {
throw new Error("Invalid result, retrying...");
}
return result;
},
);
```
---
## Integration Patterns
Patterns for triggering workflow tasks from outside a workflow context.
## Cron-Triggered Workflows
Render Workflows do not have built-in scheduling. Use a Render cron job to trigger tasks on a schedule.
**Python cron job (synchronous)** (`cron_trigger.py`):
```python
from render_sdk import Render
def main():
render = Render()
result = render.workflows.run_task("my-workflow/daily-cleanup", [])
print(f"Cleanup completed: {result.status}")
if __name__ == "__main__":
main()
```
**Python cron job (asynchronous)** (`cron_trigger.py`):
```python
import asyncio
from render_sdk import RenderAsync
async def main():
render = RenderAsync()
started = await render.workflows.start_task("my-workflow/daily-cleanup", [])
finished = await started
print(f"Cleanup completed: {finished.status}")
asyncio.run(main())
```
**TypeScript cron job** (`cron-trigger.ts`):
```typescript
import { Render } from "@renderinc/sdk";
const render = new Render();
async function main() {
const result = await render.workflows.runTask("my-workflow/daily-cleanup", []);
console.log("Cleanup completed:", result.status);
}
main();
```
Set up the cron job in the Render Dashboard or via MCP with the desired schedule.
## Cross-Workflow Calls
A task in one workflow can trigger tasks in a **different** workflow using the SDK client (not subtask syntax):
**Python:**
```python
from render_sdk import RenderAsync
@app.task
async def orchestrate(data: dict) -> dict:
render = RenderAsync()
result = await render.workflows.run_task(
"other-workflow/process",
[data],
)
return result.results
```
**TypeScript:**
```typescript
import { Render } from "@renderinc/sdk";
task(
{ name: "orchestrate" },
async function orchestrate(data: object): Promise<unknown> {
const render = new Render();
const result = await render.workflows.runTask(
"other-workflow/process",
[data],
);
return result.results;
},
);
```
> **Note:** Cross-workflow calls are not tracked as subtask relationships in the Dashboard.
SHA-256: 456acdf32d1bed89e933f81f5458d8b0bbf2eb81c1985cce3671cacfee7cab61