Skip to main content

Retry policies, backoff execution, and idempotency gates for AI execution systems

Project description

nodus-retry

Retry policies, backoff execution, and idempotency gates for AI execution systems.

Named retry policies for each execution type, sync and async execution with exponential backoff, non-retryable error classification, and an EXACTLY_ONCE idempotency gate via EffectStore. No required external dependencies — pure stdlib.

Note: nodus-retry is a required dependency of nodus-lang>=4.1.0. The EffectStore and InMemoryEffectStore are also available from nodus-lang scripts via std:effects.

Status: v0.1.0 — published on PyPI.


Install

pip install nodus-retry

What it provides

Component Purpose
RetryPolicy Immutable retry configuration (max_attempts, backoff, exponential, guarantee)
Named policies FLOW_NODE_DEFAULT, AGENT_LOW_MEDIUM, AGENT_HIGH_RISK, ASYNC_JOB_DEFAULT, NODUS_SCHEDULED_DEFAULT, NO_RETRY
resolve_retry_policy Look up policy by execution_type + risk_level string
execute_with_retry Run a sync callable under a RetryPolicy
execute_with_retry_async Run an async callable under a RetryPolicy
is_retryable_error Classify error strings as retryable or not
InMemoryEffectStore Thread-safe EXACTLY_ONCE idempotency gate (default; per-process)
SqliteEffectStore Durable, restart-safe idempotency gate (stdlib sqlite3)
compute_action_id SHA-256 hash of (action_type, payload, scope)

RetryPolicy

from nodus_retry import RetryPolicy

policy = RetryPolicy(
    max_attempts=3,
    backoff_ms=200,
    exponential_backoff=True,   # 200ms, 400ms, 800ms
    high_risk_immediate_fail=False,
    execution_guarantee="AT_LEAST_ONCE",  # or "EXACTLY_ONCE"
)

Named policies

from nodus_retry import (
    FLOW_NODE_DEFAULT,       # 3 attempts, 500ms exponential, AT_LEAST_ONCE
    AGENT_LOW_MEDIUM,        # 3 attempts, 200ms exponential, AT_LEAST_ONCE
    AGENT_HIGH_RISK,         # 1 attempt, high_risk_immediate_fail=True
    ASYNC_JOB_DEFAULT,       # 5 attempts, 1000ms exponential
    NODUS_SCHEDULED_DEFAULT, # 3 attempts, 300ms exponential
    NO_RETRY,                # max_attempts=1
)

Resolve by name

from nodus_retry import resolve_retry_policy

policy = resolve_retry_policy("FLOW_NODE_DEFAULT")
policy = resolve_retry_policy("AGENT_LOW_MEDIUM")

Execution

from nodus_retry import execute_with_retry, execute_with_retry_async, FLOW_NODE_DEFAULT

# Sync
result = execute_with_retry(
    lambda: call_external_api(),
    policy=FLOW_NODE_DEFAULT,
)

# Async
result = await execute_with_retry_async(
    my_async_fn,
    policy=AGENT_LOW_MEDIUM,
)

Retries on any exception unless is_retryable_error classifies the error string as non-retryable (e.g. auth errors, validation errors).


Error classification

from nodus_retry import is_retryable_error

is_retryable_error("connection timeout")    # True
is_retryable_error("rate limit exceeded")   # True
is_retryable_error("unauthorized")          # False
is_retryable_error("invalid input")         # False

EffectStore — EXACTLY_ONCE idempotency

from nodus_retry import InMemoryEffectStore, compute_action_id

store = InMemoryEffectStore()

# Compute a deterministic action ID
action_id = compute_action_id("memory.write", {"key": "k", "value": "v"}, scope="run-001")

# Check before executing
already_done, cached = store.resolve(action_id)
if already_done:
    return cached   # idempotent return

store.pending(action_id, input_hash="h")
try:
    result = do_the_work()
    store.complete(action_id, "success", result)
    return result
except Exception:
    store.complete(action_id, "failed", None)
    raise

EffectStore is a @runtime_checkable Protocol — any class with resolve, pending, and complete methods satisfies it.

Durable EXACTLY_ONCE across restarts

InMemoryEffectStore is the default and is per-process — a crash loses its records. For @exactly_once that survives process restarts, use the drop-in SqliteEffectStore (stdlib sqlite3, still zero third-party dependencies):

from nodus_retry import SqliteEffectStore

rt.set_effect_store(SqliteEffectStore("effects.db"))

It commits on every write (WAL journal), so a completed effect is durable before complete() returns; a fresh process pointed at the same file resolves prior effects idempotently. Pass ":memory:" for a non-durable in-process store. This does not change the default — durability stays the host's opt-in choice.


Design

  • No required dependencies. Pure stdlib (hashlib, json, sqlite3, threading, asyncio, dataclasses).
  • nodus-lang>=4.1.0 requires this package. InMemoryEffectStore is wired into the VM as self.effect_store and exposed via std:effects.

Development

pip install -e ".[dev]"
pytest tests/ -q

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nodus_retry-0.2.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nodus_retry-0.2.0-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file nodus_retry-0.2.0.tar.gz.

File metadata

  • Download URL: nodus_retry-0.2.0.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for nodus_retry-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ba067d8cdbb05d6b27c012961d95b5f02b6053406478731499f46a494c029738
MD5 18cc64c9415c6ed614895d0bb5dd9f55
BLAKE2b-256 a547f325b6bf54ba118377446c5c3a24a457b0f97a396b9d8366077e6b53beee

See more details on using hashes here.

File details

Details for the file nodus_retry-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: nodus_retry-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for nodus_retry-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4d91cfa61762be11ce09c99acd177ce2472bab055ab9c081c9942665bc185f90
MD5 7775052840dffde21ef7df4d9401c663
BLAKE2b-256 a17635782d197e786aee84cd6898f363d1aed36d0cb12367d7f4f1494810dbd8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page