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

Resonate Basic Debugging Python

skill-resonatehq-resonate-skills-resonate-basic-debugging-python · by resonatehq

Debug and troubleshoot Resonate applications using the Python SDK v0.7.0 and Resonate Server v0.9.x. Use when investigating stuck workflows, non-deterministic replays, registration errors, async/await mistakes, unexpected retries, or server connectivity issues.

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

Install

$ agentstack add skill-resonatehq-resonate-skills-resonate-basic-debugging-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-basic-debugging-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 Basic Debugging Python? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Resonate Debug and Troubleshoot — Python

Overview

Use this skill to diagnose Resonate runtime issues in Python. The Python SDK v0.7.0 uses async def + await (not generators); failure modes differ from TypeScript's generator-based v1 API and from the legacy v0.6.x Python SDK. Focus on reproducible symptoms, promise state, registration, and determinism.

For the language-agnostic replay + recovery mental model, read durable-execution first.

Triage flow

  1. Classify the failure: import/syntax error, runtime SDK error, server error code, workflow stuck/hanging, or unexpected replay behavior.
  2. Confirm Python is ≥ 3.12 and resonate-sdk>=0.7.0 is installed.
  3. Confirm the Resonate server is reachable: resonate dev starts it on localhost:8001 (Rust v0.9.x, single port — no separate store port).
  4. Confirm the worker process is running, connected to the right URL and group, and registered for the right functions.
  5. Inspect the promise state and call graph for the invocation ID.
  6. Validate determinism — no time.time(), no random.random(), no un-checkpointed side effects in the orchestrator body.

Version + compat pins

  • Python ≥ 3.12 — the SDK uses recent typing syntax. 3.11 and below won't import.
  • resonate-sdk>=0.7.0 — async/await rewrite; incompatible with v0.6.x API.
  • Resonate server v0.9.x (Rust, single port 8001) — run with resonate dev for local development.
  • resonate-sdk on PyPI (not resonate — the bare name is a different package).

If from resonate.resonate import Resonate fails, you either have the wrong package installed or you're on Python str: ctx.run(bar, arg) # missing await; returns a future the SDK sees but you lose the result return "done"


The future is tracked by structured concurrency (the runtime will still join it before settling `foo`), but the result is lost and your orchestrator continues without it. Fix:

```python
result = await ctx.run(bar, arg)

Used yield instead of await

async def foo(ctx: Context, arg: str) -> str:
    result = yield ctx.run(bar, arg)    # wrong — this is generator syntax

The v0.7.0 SDK uses async def + await, not def + yield. Mixing them produces a TypeError or unexpected behavior. Use await.

Made the function a plain def instead of async def

def foo(ctx: Context, arg: str) -> str:   # missing async
    result = await ctx.run(bar, arg)       # SyntaxError

All durable functions must be async def. The SDK registers and dispatches them as coroutines.

Not awaiting r.promises.*

r.promises.resolve(approval_id, Value(data={"ok": True}))   # missing await

All r.promises.* methods are async. Missing await means the resolve is never sent. Fix:

await r.promises.resolve(approval_id, Value(data={"ok": True}))

Not calling await r.stop() on shutdown

The SDK needs to drain in-flight work cleanly. Always:

try:
    result = await r.run(id, fn, arg).result()
finally:
    await r.stop()

Registration errors

| Symptom | Likely cause | Fix | |---|---|---| | Function 'X' is not registered | rpc target name doesn't match a registered name on any reachable worker | Check r.register(fn) is called; ensure the calling name and registered name match (if you used r.register(fn, name="custom") the caller must use "custom") | | Function version must be greater than zero | Passing version=0 explicitly | Default is 1; omit version or pass a positive integer | | Args unencodeable | Argument to run/rpc not JSON-serializable | Use dicts, lists, strings, numbers, bools, None, or types with custom encoders registered |

Promise-state errors

| Code | Meaning | Typical cause | |---|---|---| | 40900 | Promise already exists | Two calls used the same invocation ID; treat as idempotency — the second call returns the first's result. If you wanted a fresh run, change the ID | | 40400 | Promise not found | Wrong ID, wrong server URL, or promise was purged | | 40300 | Already resolved | Normal if you're checking idempotency; the value is the first resolution | | 40301 | Already rejected | Normal; the reason is the first rejection | | 40303 | Already timed out | Promise's deadline passed; re-create with a new ID if you want to retry |

Inspect from the CLI:

resonate promises get 
resonate tree 
resonate promises search 

"My workflow hangs"

Three common causes, in order of likelihood:

  1. No worker for the target group. If your rpc targets "backend" but no process has registered with group="backend", the invocation is queued forever. Check: resonate promises get → state stays pending; confirm your worker process is running and connected.
  2. Forgot await (see pitfalls above). The orchestrator advanced past a step without waiting for it.
  3. Server not reachable. Confirm resonate dev is running on port 8001; check RESONATE_URL env var.

"My workflow replays things it shouldn't"

Replays are normal — Resonate re-executes the durable function from the top on every resumption, using stored promise values for the awaited checkpoints. Side effects outside the ctx.run envelope re-execute on every replay.

async def foo(ctx: Context, arg: str) -> str:
    print(f"starting {arg}")              # prints on every replay
    log.info(f"starting {arg}")           # logs on every replay
    result = await ctx.run(work, arg)
    return result

Fix: move any side effect into a helper called via ctx.run:

async def log_start(ctx: Context, arg: str) -> None:
    log.info(f"starting {arg}")

async def foo(ctx: Context, arg: str) -> str:
    await ctx.run(log_start, arg)         # logged exactly once, persisted
    result = await ctx.run(work, arg)
    return result

Non-determinism regressions

Common non-deterministic code that breaks recovery:

async def bad(ctx: Context, x: str) -> str:
    t = time.time()                        # changes between runs
    r = random.random()                    # changes between runs
    path = os.environ.get("FEATURE")      # changes if env mutates
    if t > 1700000000 or r > 0.5:
        return await ctx.run(branch_a)
    return await ctx.run(branch_b)

Each resumption may hit a different branch than the original run. Wrap non-deterministic values in leaf functions via ctx.run:

async def _get_time(ctx: Context) -> float:
    return time.time()

async def _get_random(ctx: Context) -> float:
    return random.random()

async def good(ctx: Context, x: str) -> str:
    t = await ctx.run(_get_time)
    r = await ctx.run(_get_random)
    # Now t and r are stable across replays (stored at first execution)

Minimal repro templates

Worker:

# worker.py
from __future__ import annotations
import asyncio, os
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"), group="workers")

async def ping(ctx: Context, name: str) -> str:
    return f"pong {name}"

r.register(ping)

async def main() -> None:
    try:
        await asyncio.Event().wait()    # block forever; SDK polls for work
    finally:
        await r.stop()

asyncio.run(main())

Client:

# client.py
from __future__ import annotations
import asyncio, os, time
from resonate.resonate import Resonate

async def main() -> None:
    r = Resonate(url=os.environ.get("RESONATE_URL", "http://localhost:8001"))
    try:
        result = await r.options(target="workers").rpc(
            f"ping-{time.time_ns()}", "ping", "alice"
        ).result()
        print(result)
    finally:
        await r.stop()

asyncio.run(main())

If the client hangs, check: worker running? same URL? worker in the right group? resonate promises get → what state is it in?

CLI one-liners

# local dev server — zero config, single port 8001
resonate dev

# inspect a workflow's tree of promises
resonate tree 

# get a specific promise's state
resonate promises get 

# resolve/reject external promises from the CLI (e.g., for HITL testing)
resonate promises resolve  --data '{"approve": true}'
resonate promises reject  --data '{"reason": "denied"}'

# search by prefix or tags
resonate promises search 'order:*'

Related skills

  • resonate-basic-ephemeral-world-usage-python — Client APIs; useful when the failure is at the process-entry layer
  • resonate-basic-durable-world-usage-python — Context APIs; useful when the failure is inside a durable function
  • durable-execution — foundational; the mental model for recovery + replay
  • resonate-philosophy — the "do not build these things" list; many debug sessions end up being about patterns that were already warned against here

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.