From 6d5f049e7daffbb85da39525d9f7b75045e1bcf1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 10:53:01 +0000 Subject: [PATCH 1/2] docs: add budget lifecycle outcome table to README Documents all scenarios where @cycles commits, releases, or does neither (DENY, dry-run, EXPIRED, FINALIZED). https://claude.ai/code/session_012fx4F3BKF7QHZ5WMN1Jaua --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 686859a..c864b28 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,23 @@ result = call_llm("Hello", tokens=100) > ``` > The key (e.g. `cyc_live_abc123...`) is shown only once — save it immediately. For key rotation and lifecycle details, see [API Key Management](https://runcycles.io/how-to/api-key-management-in-cycles). +### Budget lifecycle + +The `@cycles` decorator wraps your function in a reserve → execute → commit/release lifecycle: + +| Scenario | Outcome | Detail | +|---|---|---| +| Reservation denied (409) | **Neither** | `BudgetExceededError` (or similar) raised; function never executes | +| `dry_run=True`, any decision | **Neither** | Returns `DryRunResult` or raises; no real reservation created | +| Function returns successfully | **Commit** | Actual amount charged; unused remainder auto-released | +| Function raises any exception | **Release** | Full reserved amount returned to budget; exception re-raised | +| Commit fails (5xx / network) | **Retry** | Exponential backoff with configurable attempts | +| Commit fails (non-retryable 4xx) | **Release** | Reservation released after non-retryable client error | +| Commit gets RESERVATION_EXPIRED | **Neither** | Server already reclaimed budget on TTL expiry | +| Commit gets RESERVATION_FINALIZED | **Neither** | Already committed or released (idempotent replay) | + +All raised exceptions from the guarded function trigger release. See [How Reserve-Commit Works](https://runcycles.io/protocol/how-reserve-commit-works-in-cycles) for the full protocol-level explanation. + ### Programmatic client ```python From 5caefaa3e0e553751b3f691c83062210a3bfa423 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Mar 2026 10:56:48 +0000 Subject: [PATCH 2/2] docs: fix budget lifecycle table accuracy - List all denial error types instead of just BudgetExceededError - Add missing IDEMPOTENCY_MISMATCH row (neither commit nor release) https://claude.ai/code/session_012fx4F3BKF7QHZ5WMN1Jaua --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c864b28..edbe5c0 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The `@cycles` decorator wraps your function in a reserve → execute → commit/ | Scenario | Outcome | Detail | |---|---|---| -| Reservation denied (409) | **Neither** | `BudgetExceededError` (or similar) raised; function never executes | +| Reservation denied | **Neither** | `BudgetExceededError`, `OverdraftLimitExceededError`, or `DebtOutstandingError` raised; function never executes | | `dry_run=True`, any decision | **Neither** | Returns `DryRunResult` or raises; no real reservation created | | Function returns successfully | **Commit** | Actual amount charged; unused remainder auto-released | | Function raises any exception | **Release** | Full reserved amount returned to budget; exception re-raised | @@ -73,6 +73,7 @@ The `@cycles` decorator wraps your function in a reserve → execute → commit/ | Commit fails (non-retryable 4xx) | **Release** | Reservation released after non-retryable client error | | Commit gets RESERVATION_EXPIRED | **Neither** | Server already reclaimed budget on TTL expiry | | Commit gets RESERVATION_FINALIZED | **Neither** | Already committed or released (idempotent replay) | +| Commit gets IDEMPOTENCY_MISMATCH | **Neither** | Previous commit already processed; no release attempted | All raised exceptions from the guarded function trigger release. See [How Reserve-Commit Works](https://runcycles.io/protocol/how-reserve-commit-works-in-cycles) for the full protocol-level explanation.