AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Resonate Recursive Fan Out Pattern Python

skill-resonatehq-resonate-skills-resonate-recursive-fan-out-pattern-python · by resonatehq

Implement recursive fan-out in Python for parallel workflow execution — spawn N sub-workflows from a parent, optionally recurse deeper, await results, handle partial failure. Use when processing a tree, batch, or crawl where the work shape is dynamic and each child is independently durable.

— No reviews yet
0 installs
31 views
0.0% view→install

Install

$ agentstack add skill-resonatehq-resonate-skills-resonate-recursive-fan-out-pattern-python

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ● Environment & secrets Used
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-resonatehq-resonate-skills-resonate-recursive-fan-out-pattern-python)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Resonate Recursive Fan Out Pattern Python? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Resonate Recursive Fan-Out Pattern — Python

Overview

Recursive fan-out is when a durable function spawns child invocations (either of itself or of siblings), optionally waits for them in parallel, and possibly continues recursing. Each child is its own Resonate promise; if a worker crashes, each child resumes independently.

The pattern is expressed in Python by launching multiple ctx.run(...) or ctx.rpc(...) calls before awaiting them — collect the futures first, then await them. This is different from TS's map-over-promises shape only in syntax; the semantics are identical.

When to use

  • Batch processing where items are independent
  • Web crawling / tree traversal with dynamic depth
  • Map-reduce style workflows
  • Any fan-out where each leaf is a discrete, retryable unit of work

Don't use for parallel I/O within a single step (use async clients directly inside a ctx.run envelope) or for a tight inner loop (overhead of a promise per item dominates).

Parallel fan-out in the same process

Launch children without blocking; collect futures; await them:

from __future__ import annotations
import asyncio, os, time
from typing import TYPE_CHECKING
from resonate.resonate import Resonate

if TYPE_CHECKING:
    from resonate.context import Context

r = Resonate(url=os.environ.get("RESONATE_URL", "http://localhost:8001"))

async def enrich_batch(ctx: Context, order_ids: list[str]) -> list[dict]:
    # Launch all children (returns futures immediately)
    futures = [ctx.run(enrich_one, oid) for oid in order_ids]

    # Await them all; order preserved
    results = [await f for f in futures]
    return results

async def enrich_one(ctx: Context, order_id: str) -> dict:
    # Enrichment logic — this is a leaf
    return {"order_id": order_id, "enriched": True}

Each enrich_one call is an independent durable promise. If the parent worker crashes after launching children but before awaiting, the children continue; on parent replay, awaiting the future hits the stored promise value.

Note: structured concurrency guarantees the parent cannot settle until all spawned children (even unawaited ones) complete. Explicitly awaiting them gives you the results.

Parallel fan-out across workers

Use ctx.rpc to dispatch children to remote workers:

async def parallel_enrich(ctx: Context, order_ids: list[str]) -> list[dict]:
    futures = [
        ctx.options(target="enrichment-workers").rpc("enrich_one", oid)
        for oid in order_ids
    ]
    return [await f for f in futures]

This horizontally scales across any enrichment-workers group. Fair queueing is the Resonate server's responsibility; your code doesn't manage worker pools.

Recursive fan-out

A durable function calling itself via rpc is legal — useful for tree traversal and crawlers:

async def crawl(ctx: Context, url: str, depth: int) -> dict:
    page = await ctx.run(fetch_page, url)

    if depth  int:
    if n  int:
    if n  list[dict]:
    results = []
    for batch in _chunks(items, concurrency):
        futures = [ctx.run(process_one, item) for item in batch]
        results.extend([await f for f in futures])
    return results

Each batch of up to concurrency runs in parallel; the next batch only starts after the previous one fully settles.

Partial failure handling

By default, if any child raises, the parent's await f re-raises. To continue on individual failures:

async def enrich_tolerant(ctx: Context, order_ids: list[str]) -> list[dict]:
    futures = [ctx.run(enrich_one, oid) for oid in order_ids]

    results = []
    for f in futures:
        try:
            results.append(await f)
        except Exception as err:
            results.append({"error": str(err)})
    return results

Each child's error is caught individually; the parent returns a mixed list of successes and error dicts.

Idempotency via stable invocation IDs

Fan-out children get deterministic ids automatically (e.g., {parent_id}.1, {parent_id}.2). The SDK assigns these based on dispatch order during replay — you do NOT pass an explicit invocation id to ctx.rpc or ctx.run; there is no id option on ctx.options in v0.7.0. Stable replay ordering is sufficient for idempotency inside a durable function.

async def enrich_batch(ctx: Context, batch_id: str, order_ids: list[str]) -> list[dict]:
    # Child ids are assigned deterministically by the SDK (e.g. {parent_id}.1, .2, ...)
    # Pass the function name first, then the arg
    futures = [
        ctx.options(version=1).rpc("enrich_one", oid)
        for oid in order_ids
    ]
    return [await f for f in futures]

If you need an explicit invocation id — for example to share a promise with an external observer — use the client-level r.rpc(id, fn, *args) from outside a durable function. Inside a durable function, ids are deterministic and managed by the runtime.

Distinct Python idioms

  • Futures launched before awaited: futures = [ctx.run(fn, x) for x in items] then [await f for f in futures] — plain list comprehensions. No special syntax needed.
  • Do NOT use asyncio.gather: ctx.run(...) and ctx.rpc(...) return Resonate futures, not asyncio futures. asyncio.gather(ctx.run(...), ctx.run(...)) will not work correctly. The correct fan-out is futures = [ctx.run(fn, x) for x in items]; results = [await f for f in futures].
  • islice from itertools for bounded parallelism — cleaner than manual indexing.
  • try/except per-future — Python's narrow scoping lets you catch one child's failure without affecting siblings.
  • No Promise.all or Promise.allSettled — [await f for f in futures] gives all-or-first-error semantics; per-future try/except gives allSettled semantics.
  • Structured concurrency — unawaited children are still joined by the runtime before the parent resolves; you don't need to track them explicitly to prevent orphans.

Related skills

  • resonate-basic-durable-world-usage-python — ctx.run, ctx.rpc, ctx.detached, ctx.options
  • resonate-saga-pattern-python — fan-out inside a saga for parallel forward steps
  • resonate-human-in-the-loop-pattern-python — fan-out where each child waits for its own human approval
  • durable-execution — foundational replay semantics

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.