{"id":37663502,"url":"https://github.com/mauhpr/cadence","last_synced_at":"2026-04-02T18:04:41.424Z","repository":{"id":332264556,"uuid":"1132999948","full_name":"mauhpr/cadence","owner":"mauhpr","description":"A declarative Python framework for orchestrating service logic with rhythm and precision","archived":false,"fork":false,"pushed_at":"2026-03-29T15:43:48.000Z","size":439,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T17:42:12.865Z","etag":null,"topics":["async","circuit-breaker","decorators","fastapi","flask","orchestration","pipeline","python","resilience","retry","workflow"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/cadence-orchestration/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mauhpr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-12T18:36:44.000Z","updated_at":"2026-03-29T15:43:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mauhpr/cadence","commit_stats":null,"previous_names":["mauhpr/cadence"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/mauhpr/cadence","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mauhpr%2Fcadence","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mauhpr%2Fcadence/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mauhpr%2Fcadence/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mauhpr%2Fcadence/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mauhpr","download_url":"https://codeload.github.com/mauhpr/cadence/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mauhpr%2Fcadence/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","circuit-breaker","decorators","fastapi","flask","orchestration","pipeline","python","resilience","retry","workflow"],"created_at":"2026-01-16T11:54:11.826Z","updated_at":"2026-04-02T18:04:41.416Z","avatar_url":"https://github.com/mauhpr.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cadence\n\n[![PyPI version](https://badge.fury.io/py/cadence-orchestration.svg)](https://badge.fury.io/py/cadence-orchestration)\n[![Python Versions](https://img.shields.io/pypi/pyversions/cadence-orchestration.svg)](https://pypi.org/project/cadence-orchestration/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Tests](https://github.com/mauhpr/cadence/actions/workflows/test.yml/badge.svg)](https://github.com/mauhpr/cadence/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/mauhpr/cadence/branch/main/graph/badge.svg)](https://codecov.io/gh/mauhpr/cadence)\n\n**A declarative Python framework for building service and AI orchestration logic with explicit control flow.**\n\nCadence lets you build complex orchestration — from checkout flows to LLM pipelines — with a clean, readable API. Define your business logic as composable notes, handle errors gracefully, and scale with confidence.\n\n## Why Cadence?\n\n| Challenge | How Cadence Helps |\n|-----------|-------------------|\n| Service orchestration is tangled in imperative code | Declarative `.then()` / `.sync()` / `.split()` chain makes the flow visible |\n| LLM APIs are unreliable | Built-in `@retry`, `@timeout`, `@fallback`, `@circuit_breaker` |\n| Parallel tool calling is error-prone | `.sync()` with automatic score isolation and merging |\n| Intent routing needs ugly if/else trees | `.split(condition, if_true, if_false)` keeps it clean |\n| LLMs hallucinate framework code | Constrained 4-method DSL is easy for models to generate correctly |\n| Workflows crash mid-execution | `.with_checkpoint()` resumes from last successful step |\n| Frameworks lock you into their ecosystem | Zero dependencies — bring any LLM client, any HTTP library |\n\n## Features\n\n- **Declarative Cadence Definition** - Build complex workflows with a fluent, chainable API\n- **Parallel Execution** - Run tasks concurrently with automatic context isolation and merging\n- **Branching Logic** - Conditional execution paths with clean syntax\n- **Resilience Patterns** - Built-in retry, timeout, fallback, and circuit breaker\n- **Framework Integration** - First-class support for FastAPI and Flask\n- **Observability** - Hooks for logging, metrics, and tracing\n- **Event-Driven Architecture** - Structured domain events with listener registration and async support\n- **Workflow Checkpointing** - Crash recovery with pluggable checkpoint stores\n- **Type Safety** - Full type hints and generics support\n- **Zero Dependencies** - Core library has no required dependencies\n- **LLM Pipeline Ready** - Natural fit for RAG, tool calling, multi-agent, and model fallback chains\n- **LLM-Friendly DSL** - Constrained grammar that LLMs can generate correctly with minimal hallucination\n\n## Installation\n\n```bash\npip install cadence-orchestration\n```\n\nWith optional integrations:\n\n```bash\n# FastAPI integration\npip install cadence-orchestration[fastapi]\n\n# Flask integration\npip install cadence-orchestration[flask]\n\n# OpenTelemetry tracing\npip install cadence-orchestration[opentelemetry]\n\n# Prometheus metrics\npip install cadence-orchestration[prometheus]\n\n# All integrations\npip install cadence-orchestration[all]\n```\n\n## Quick Start\n\n```python\nfrom dataclasses import dataclass\nfrom cadence import Cadence, Score, note\n\n@dataclass\nclass OrderScore(Score):\n    order_id: str\n    items: list = None\n    total: float = 0.0\n    status: str = \"pending\"\n\n@note\nasync def fetch_items(score: OrderScore):\n    # Fetch order items from database\n    score.items = await db.get_items(score.order_id)\n\n@note\nasync def calculate_total(score: OrderScore):\n    score.total = sum(item.price for item in score.items)\n\n@note\nasync def process_payment(score: OrderScore):\n    await payment_service.charge(score.order_id, score.total)\n    score.status = \"paid\"\n\n# Build and run the cadence\ncadence = (\n    Cadence(\"checkout\", OrderScore(order_id=\"ORD-123\"))\n    .then(\"fetch_items\", fetch_items)\n    .then(\"calculate_total\", calculate_total)\n    .then(\"process_payment\", process_payment)\n)\n\nresult = await cadence.run()\nprint(f\"Order {result.order_id}: {result.status}\")\n```\n\n### LLM Pipeline Example\n\nThe same primitives work for AI pipelines — parallel retrieval, intent routing, resilience on model calls:\n\n```python\nfrom dataclasses import dataclass, field\nfrom cadence import Cadence, Score, note, retry, timeout, fallback\n\n@dataclass\nclass RAGScore(Score):\n    query: str\n    context: list = field(default_factory=list)\n    answer: str = \"\"\n\n@note(timeout=5.0)\nasync def retrieve(score: RAGScore):\n    score.context = await vector_db.search(score.query)\n\n@note(fallback={\"default\": [], \"field\": \"context\"}, timeout=10.0)\nasync def web_search(score: RAGScore):\n    score.context += await search_api.query(score.query)\n\n@note(retry={\"max_attempts\": 3, \"backoff\": \"exponential\"}, timeout=30.0)\nasync def generate(score: RAGScore):\n    score.answer = await llm.generate(score.query, context=score.context)\n\nrag = (\n    Cadence(\"rag\", RAGScore(query=\"What is Cadence?\"))\n    .sync(\"retrieve\", [retrieve, web_search])   # parallel retrieval\n    .then(\"generate\", generate)                  # LLM call with retry + timeout\n)\n```\n\nThis is identical in shape to the service orchestration example above — same API, same resilience patterns, different domain.\n\n## Core Concepts\n\n### Sequential Notes\n\nExecute notes one after another:\n\n```python\ncadence = (\n    Cadence(\"process\", MyScore())\n    .then(\"note1\", do_first)\n    .then(\"note2\", do_second)\n    .then(\"note3\", do_third)\n)\n```\n\n### Parallel Execution\n\nRun independent tasks concurrently with automatic score isolation:\n\n```python\ncadence = (\n    Cadence(\"enrich\", UserScore(user_id=\"123\"))\n    .sync(\"fetch_data\", [\n        fetch_profile,\n        fetch_preferences,\n        fetch_history,\n    ])\n    .then(\"merge_results\", combine_data)\n)\n```\n\n### Conditional Branching\n\nRoute execution based on runtime conditions:\n\n```python\ncadence = (\n    Cadence(\"order\", OrderScore())\n    .then(\"validate\", validate_order)\n    .split(\"route\",\n        condition=is_premium_customer,\n        if_true=[priority_processing, express_shipping],\n        if_false=[standard_processing, regular_shipping]\n    )\n    .then(\"confirm\", send_confirmation)\n)\n```\n\n### Child Cadences\n\nCompose cadences for complex orchestration:\n\n```python\npayment_cadence = Cadence(\"payment\", PaymentScore())...\nshipping_cadence = Cadence(\"shipping\", ShippingScore())...\n\ncheckout_cadence = (\n    Cadence(\"checkout\", CheckoutScore())\n    .then(\"prepare\", prepare_order)\n    .child(\"process_payment\", payment_cadence, merge_payment)\n    .child(\"arrange_shipping\", shipping_cadence, merge_shipping)\n    .then(\"complete\", finalize_order)\n)\n```\n\n## Resilience Patterns\n\n### Retry with Backoff\n\n```python\nfrom cadence import retry\n\n@note(retry={\"max_attempts\": 3, \"delay\": 1.0, \"backoff\": \"exponential\"})\nasync def call_external_api(score):\n    response = await http_client.get(score.api_url)\n    score.data = response.json()\n```\n\n### Timeout\n\n```python\nfrom cadence import timeout\n\n@note(timeout=5.0)\nasync def slow_operation(score):\n    score.result = await long_running_task()\n```\n\n\u003e **Note:** On Windows, the `@timeout` decorator only works with async functions. Sync function timeouts require Unix signals (`SIGALRM`) which are not available on Windows.\n\n### Fallback\n\n```python\nfrom cadence import fallback\n\n@note(fallback={\"default\": \"unknown\", \"field\": \"status\"})\nasync def get_status(score):\n    score.status = await status_service.get(score.id)\n```\n\n### Circuit Breaker\n\n```python\nfrom cadence import circuit_breaker\n\n@circuit_breaker(failure_threshold=5, recovery_timeout=30.0)\n@note\nasync def call_fragile_service(score):\n    score.data = await fragile_service.fetch()\n```\n\n### Combined Resilience\n\nCombine retry, timeout, and fallback in a single decorator:\n\n```python\nfrom cadence import note\n\n# Shorthand — covers the common case\n@note(retry=3, timeout=15.0)\nasync def call_service(score):\n    score.result = await external_api.call()\n\n# Full control via dicts\n@note(\n    retry={\"max_attempts\": 3, \"backoff\": \"exponential\", \"delay\": 0.5},\n    timeout=30.0,\n    fallback={\"default\": None, \"field\": \"result\"},\n)\nasync def call_service(score):\n    score.result = await external_api.call()\n```\n\nStandalone decorators (`@retry`, `@timeout`, `@fallback`) still work for stacking when you need maximum flexibility.\n\n## Framework Integration\n\n### FastAPI\n\n```python\nfrom fastapi import FastAPI\nfrom cadence.integrations.fastapi import CadenceRouter\n\napp = FastAPI()\nrouter = CadenceRouter()\n\n@router.cadence(\"/orders/{order_id}\", checkout_cadence)\nasync def create_order(order_id: str):\n    return OrderScore(order_id=order_id)\n\napp.include_router(router)\n```\n\n### Flask\n\n```python\nfrom flask import Flask\nfrom cadence.integrations.flask import CadenceBlueprint\n\napp = Flask(__name__)\nbp = CadenceBlueprint(\"orders\", __name__)\n\n@bp.cadence_route(\"/orders/\u003corder_id\u003e\", checkout_cadence)\ndef create_order(order_id):\n    return OrderScore(order_id=order_id)\n\napp.register_blueprint(bp)\n```\n\n## Observability\n\n### Hooks System\n\n```python\nfrom cadence import Cadence, LoggingHooks, TimingHooks\n\ncadence = (\n    Cadence(\"monitored\", MyScore())\n    .with_hooks([LoggingHooks(), TimingHooks()])\n    .then(\"note1\", do_work)\n)\n```\n\n### Custom Hooks\n\n```python\nfrom cadence import CadenceHooks\n\nclass MyHooks(CadenceHooks):\n    async def before_note(self, note_name, score):\n        print(f\"Starting: {note_name}\")\n\n    async def after_note(self, note_name, score, duration, error=None):\n        print(f\"Completed: {note_name} in {duration:.2f}s\")\n\n    async def on_error(self, note_name, score, error):\n        alert_team(f\"Error in {note_name}: {error}\")\n```\n\n### Prometheus Metrics\n\n```python\nfrom cadence.reporters import PrometheusReporter\n\nreporter = PrometheusReporter(prefix=\"myapp\")\n\ncadence = (\n    Cadence(\"tracked\", MyScore())\n    .with_reporter(reporter.report)\n    .then(\"note1\", do_work)\n)\n```\n\n### OpenTelemetry Tracing\n\n```python\nfrom cadence.reporters import OpenTelemetryReporter\n\nreporter = OpenTelemetryReporter(service_name=\"my-service\")\n\ncadence = (\n    Cadence(\"traced\", MyScore())\n    .with_reporter(reporter.report)\n    .then(\"note1\", do_work)\n)\n```\n\n## Event System\n\nReact to cadence and note lifecycle events with structured listeners:\n\n```python\nfrom cadence.events import EventEmitter, NOTE_COMPLETED, CADENCE_FAILED\n\nemitter = EventEmitter()\nemitter.on(NOTE_COMPLETED, lambda e: print(f\"{e.note_name} done in {e.duration:.2f}s\"))\nemitter.on(CADENCE_FAILED, lambda e: alert(f\"{e.cadence_name} failed: {e.error}\"))\nemitter.on(\"*\", lambda e: audit_log.write(e))  # wildcard for all events\n\ncadence = (\n    Cadence(\"checkout\", OrderScore(order_id=\"ORD-123\"))\n    .with_hooks(emitter)\n    .then(\"validate\", validate_order)\n    .then(\"charge\", process_payment)\n)\nawait cadence.run()\n```\n\nEvent types: `NOTE_STARTED`, `NOTE_COMPLETED`, `NOTE_FAILED`, `CADENCE_STARTED`, `CADENCE_COMPLETED`, `CADENCE_FAILED`.\n\n## Checkpointing\n\nResume workflows from the last successful step after a crash:\n\n```python\nfrom cadence import Cadence\nfrom cadence.checkpoint import InMemoryCheckpointStore\n\nstore = InMemoryCheckpointStore()\n\ncadence = (\n    Cadence(\"disburse\", PaymentScore(order_id=\"ORD-123\"))\n    .with_checkpoint(store, run_id=\"order-123\")\n    .then(\"validate\", validate)\n    .then(\"charge\", charge)         # if it crashes here...\n    .then(\"disburse\", disburse)\n)\nawait cadence.run()  # ...re-run skips validate, resumes from charge\n```\n\nImplement the `CheckpointStore` protocol with Redis, a database, or any durable backend for production use.\n\n## Cadence Diagrams\n\nGenerate visual diagrams of your cadences:\n\n```python\nfrom cadence import to_mermaid, to_dot\n\n# Generate Mermaid diagram\nprint(to_mermaid(my_cadence))\n\n# Generate DOT/Graphviz diagram\nprint(to_dot(my_cadence))\n```\n\n## CLI\n\nCadence includes a CLI for scaffolding and utilities:\n\n```bash\n# Initialize a new project\ncadence init my-project\n\n# Generate a new cadence\ncadence new cadence checkout\n\n# Generate a new note with resilience decorators\ncadence new note process-payment --retry 3 --timeout 30\n\n# Generate cadence diagram\ncadence diagram myapp.cadences:checkout_cadence --format mermaid\n\n# Validate cadence definitions\ncadence validate myapp.cadences\n```\n\n## Score Management\n\n### Immutable Score\n\nFor functional-style cadences:\n\n```python\nfrom cadence import ImmutableScore\n\n@dataclass(frozen=True)\nclass Config(ImmutableScore):\n    api_key: str\n    timeout: int = 30\n\n# Create new score with changes\nnew_config = config.with_field(\"timeout\", 60)\n```\n\n### Atomic Operations\n\nThread-safe score updates for parallel execution:\n\n```python\nfrom cadence import Score, AtomicList, AtomicDict\n\n@dataclass\nclass AggregatorScore(Score):\n    results: AtomicList = None\n    cache: AtomicDict = None\n\n    def __post_init__(self):\n        super().__post_init__()\n        self.results = AtomicList()\n        self.cache = AtomicDict()\n\n# Safe concurrent updates\nscore.results.append(new_result)\nscore.cache[\"key\"] = value\n```\n\n## Error Handling\n\n```python\nfrom cadence import CadenceError, NoteError\n\ncadence = (\n    Cadence(\"handled\", MyScore())\n    .then(\"risky\", risky_operation)\n    .on_error(handle_error, stop=False)  # Continue on error\n    .then(\"cleanup\", cleanup)\n)\n\nasync def handle_error(score, error):\n    if isinstance(error, NoteError):\n        logger.error(f\"Note {error.note_name} failed: {error}\")\n        score.errors.append(str(error))\n```\n\n### Parallel Error Context\n\nIdentify which task failed in a `.sync()` group:\n\n```python\nfrom cadence import ParallelNoteError\n\ntry:\n    await cadence.run()\nexcept ParallelNoteError as e:\n    print(f\"Task '{e.note_name}' (index {e.task_index}) \"\n          f\"in group '{e.group_name}' failed: {e.original_error}\")\n```\n\n## Documentation\n\n- [API Reference](docs/api.md)\n- [Design Documents](docs/design.md)\n- [Examples](examples/)\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nCadence is released under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmauhpr%2Fcadence","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmauhpr%2Fcadence","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmauhpr%2Fcadence/lists"}