diff --git a/about.md b/about.md index 5565beba..01789727 100644 --- a/about.md +++ b/about.md @@ -5,9 +5,9 @@ description: "Cycles is an open-source runtime authority layer for AI agents — # About Cycles -Cycles is the [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) layer for AI agents. It sits between an agent's decision to act and the action itself, and it answers one question on every tool call, for every agent, in every delegation chain: *is this agent allowed to do this, right now, given what it's already done?* +Cycles is a [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) layer for AI agents. When an application puts it on a mandatory execution boundary, Cycles answers a narrower, enforceable question before each protected action: *does the configured budget have enough capacity for the amount and scope the caller submitted?* -If the answer is no, the action doesn't happen. Not "gets logged for later review." Not "triggers an alert." **Doesn't happen.** +If the integration requires a successful reservation before execution, a rejected reservation blocks the action instead of merely logging or alerting after the fact. Application authorization still decides whether the agent may use a specific tool or set of arguments. ## Who's building this @@ -21,11 +21,11 @@ The full origin story — including the overnight agent loop that burned through Three convictions shape every design decision in Cycles. They aren't new ideas — they're battle-tested patterns from distributed-systems engineering, applied to autonomous agents. -**Enforcement must be atomic.** A half-applied budget is worse than no budget. Cycles uses a reserve-commit lifecycle: budget is atomically reserved before an agent acts, actual usage is committed after, and unused capacity is released. No race conditions. No [time-of-check-to-time-of-use](https://dev.to/amavashev/your-ai-agent-budget-check-has-a-race-condition-33ei) gaps. +**Budget enforcement must be atomic.** A half-applied budget is worse than no budget. Cycles uses a reserve-commit lifecycle: budget is atomically reserved before an agent acts, actual usage is committed after, and unused capacity is released. This closes the [time-of-check-to-time-of-use](https://dev.to/amavashev/your-ai-agent-budget-check-has-a-race-condition-33ei) overspend gap at the budget boundary; the host remains responsible for executing only after a successful reservation. -**Authority must attenuate, not propagate.** When an agent spawns a sub-agent, the sub-agent gets a carved-out sub-budget and a restricted action mask. [Authority can only decrease with depth, never increase](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation). +**Authority should attenuate, not propagate.** When an agent spawns a sub-agent, the orchestrator can provision a smaller Cycles budget and separately restrict the sub-agent's tools, data, and credentials. Cycles enforces the submitted budget dimensions; the application enforces the action policy. Together, those controls can make [authority decrease with depth](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation). -**Control must be structural, not semantic.** You can't rely on an LLM to respect a system prompt that says "don't spend more than $10." That's a suggestion to a probabilistic system. Structural controls operate outside the LLM, at the infrastructure layer, and enforce boundaries deterministically. One is a hope. The other is an engineering guarantee. +**Control must be structural, not semantic.** You can't rely on an LLM to respect a system prompt that says "don't spend more than $10." A mandatory control outside the model can enforce that configured budget even when the model would continue. The guarantee applies to protected calls that the host routes through the boundary. ## What Cycles is not diff --git a/blog/26-integrations-every-ai-framework-one-budget-protocol.md b/blog/26-integrations-every-ai-framework-one-budget-protocol.md index a0e1775c..94c24c42 100644 --- a/blog/26-integrations-every-ai-framework-one-budget-protocol.md +++ b/blog/26-integrations-every-ai-framework-one-budget-protocol.md @@ -3,7 +3,7 @@ title: "26 Integrations for One AI Budget Protocol" date: 2026-04-02 author: Albert Mavashev tags: [announcement, integrations, langchain, langgraph, autogen, openai, anthropic, groq, django, nextjs, flask, anyagent, runtime-authority] -description: "Cycles ships 26 integrations across Python, TypeScript, Java, and Rust. See how one protocol coordinates runtime budget controls across diverse agent stacks." +description: "At launch, Cycles documented 26 integrations across Python, TypeScript, Java, and Rust. See how one protocol coordinates runtime budget controls across stacks." blog: true sidebar: false head: diff --git a/blog/429-classification-gap-why-retry-logic-lying.md b/blog/429-classification-gap-why-retry-logic-lying.md index 3081783b..c34aadb7 100644 --- a/blog/429-classification-gap-why-retry-logic-lying.md +++ b/blog/429-classification-gap-why-retry-logic-lying.md @@ -2,7 +2,7 @@ title: "HTTP 429: Why Retry Logic Lies" date: 2026-04-10 author: Brenton Williams -tags: [ai-agents, retry-logic, retry-storms, rate-limits, http-429, 429, agent-infrastructure, runtime-authority] +tags: [agents, retry-logic, retry-storms, rate-limits, http-429, 429, agent-infrastructure, runtime-authority] description: "HTTP 429 can mean wait, cap, or stop. Learn how classifying each response before retrying prevents retry storms and unsafe AI agent behavior in production." blog: true sidebar: false @@ -111,7 +111,7 @@ And once that happens, everything downstream inherits the wrong decision. ## The failure cascade -This is not theoretical. The [retry storm incident pattern](/incidents/retry-storms-and-idempotency-failures) — and the [$1,800 CRM outage retry storm](/blog/ai-agent-failures-budget-controls-prevent) — are exactly this failure mode. +This is not theoretical as a failure pattern. The [retry-storm incident pattern](/incidents/retry-storms-and-idempotency-failures) and a [$33.86 illustrative CRM retry model](/blog/ai-agent-failures-budget-controls-prevent) show how layered retries multiply calls. **What this looks like in a real run** ```text @@ -327,7 +327,7 @@ Until that is resolved, every layer above it is operating on a lie. ## Further reading - [Retry Storms and Idempotency Failures](/incidents/retry-storms-and-idempotency-failures) — the dedicated incident pattern this post describes -- [5 AI Agent Failures Budget Controls Would Prevent](/blog/ai-agent-failures-budget-controls-prevent) — includes a $1,800 retry storm incident from a CRM outage +- [5 Agent Cost Failures Runtime Budgets Can Bound](/blog/ai-agent-failures-budget-controls-prevent) — includes a checked $33.86 retry-storm model - [The State of AI Agent Incidents](/blog/state-of-ai-agent-incidents-2026) — Category A4 catalogues retry-storm cost multiplication - [How Reserve-Commit Works in Cycles](/protocol/how-reserve-commit-works-in-cycles) — the enforcement model referenced throughout this post - [Degradation Paths in Cycles](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer) — deny, downgrade, disable, or defer strategies for the STOP case diff --git a/blog/a-200-ok-is-not-an-audit-trail.md b/blog/a-200-ok-is-not-an-audit-trail.md index 5c6a0b7e..96c5e57c 100644 --- a/blog/a-200-ok-is-not-an-audit-trail.md +++ b/blog/a-200-ok-is-not-an-audit-trail.md @@ -50,7 +50,7 @@ It is the difference between "trust me, the budget said no" and handing someone Three properties, each doing a specific job: -- **Content-addressed.** The `evidence_id` is the SHA-256 of the envelope's [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) (JCS) canonical bytes — computed with the `evidence_id` and `signature` fields themselves left blank. The id *is* the integrity check: change one byte of a decision and it no longer matches. +- **Content-addressed.** The `evidence_id` is the SHA-256 of the envelope's [RFC 8785](https://www.rfc-editor.org/info/rfc8785/) (JCS) canonical bytes — computed with the `evidence_id` and `signature` fields themselves left blank. The id *is* the integrity check: change one byte of a decision and it no longer matches. - **Signed.** An Ed25519 signature then covers a second canonical pass — the same envelope with the `evidence_id` now filled in and `signature` still blank — proving origin. Forge the contents and the signature fails. - **In-band, then fetchable.** Cycles computes the `evidence_id` *synchronously* and returns it on the response (`cycles_evidence: { evidence_id, cycles_evidence_url }`); the expensive signing and storage happen asynchronously, off the request path. A consumer records the id and later fetches the signed envelope from the public `GET /v1/evidence/{id}` capability URL — and verifies it on its own. (Because signing is async, a fetch immediately after the response can return a transient `404` until the envelope lands — retry.) diff --git a/blog/action-authority-demo-support-agent-walkthrough.md b/blog/action-authority-demo-support-agent-walkthrough.md index 6e5277f2..e531f97d 100644 --- a/blog/action-authority-demo-support-agent-walkthrough.md +++ b/blog/action-authority-demo-support-agent-walkthrough.md @@ -3,7 +3,7 @@ title: "Block Agent Emails Before Execution" date: 2026-03-22 author: Albert Mavashev tags: [action-authority, demo, agents, runtime-authority, walkthrough, action-control, side-effects] -description: "A support agent can use CRM, notes, and email — but should every run send? Cycles blocks the customer email before execution. Three decorators, one exception." +description: "See a support-agent demo where a mandatory decorator requests a Cycles reservation and a zero-allocation toolset ledger prevents the attempted email call." blog: true sidebar: false head: diff --git a/blog/agent-budget-patterns-visual-guide.md b/blog/agent-budget-patterns-visual-guide.md index f9eb85ce..2b9986c6 100644 --- a/blog/agent-budget-patterns-visual-guide.md +++ b/blog/agent-budget-patterns-visual-guide.md @@ -24,9 +24,9 @@ These patterns aren't mutually exclusive — most production systems combine two ## Pattern 1: Tenant Isolation Budgets -**When to use:** Multi-[tenant](/glossary#tenant) platforms where each customer or team gets their own AI agent access and you need hard spend isolation between them. +**When to use:** Multi-[tenant](/glossary#tenant) platforms where each customer or team gets their own AI agent access and you need separate runtime budget ceilings. -The simplest and most common starting point. Each tenant gets an independent budget that cannot be exceeded, regardless of what other tenants are doing. +The simplest and most common starting point. Each tenant gets an independent budget, so one tenant's submitted reservations do not consume another tenant's allocation. The ceiling applies to operations routed through the mandatory integration boundary and to the estimates the caller submits. ```python # Tenant isolation: each tenant has a completely independent budget @@ -48,7 +48,7 @@ async def run_agent_for_tenant(tenant_id, task): ``` **Trade-offs:** -- Provides complete blast-radius isolation — one tenant's runaway agent cannot affect others +- Provides separate ledgers, so one tenant's protected calls cannot consume another tenant's allocation - Simple to reason about and explain to customers - Can lead to underutilization: if Tenant A uses 10% of their budget and Tenant B hits 100%, there's no sharing - Requires careful initial sizing — set too low and legitimate workloads get blocked @@ -103,12 +103,12 @@ async def research_with_degradation(query, budget_dollars=10.00): remaining = budget.remaining() if remaining > 5.00: result = await budget.execute( - agent.run(query, model="claude-opus-4-20250514") + agent.run(query, model="claude-opus-4-8") ) # Phase 2: Fall back to a cheaper model elif remaining > 1.00: result = await budget.execute( - agent.run(query, model="claude-sonnet-4-20250514") + agent.run(query, model="claude-sonnet-4-6") ) # Phase 3: Return cached/partial results else: @@ -128,9 +128,9 @@ We cover degradation strategies in detail in [How to Think About Degradation Pat ## Pattern 4: Shared Pool with Priority Tiers -**When to use:** When you want to maximize utilization of a fixed budget across multiple agents or users, with guarantees for high-priority work. +**When to use:** When you want to maximize utilization of a fixed budget across multiple agents or users while preferring high-priority work. -Instead of giving each consumer a fixed allocation, you share a pool but enforce priority ordering when the pool runs low. +Instead of giving each consumer a fixed allocation, you share a pool and have the application defer lower-priority work when the pool runs low. Cycles does not natively order reservations by priority. A read-then-act threshold is advisory and can race under concurrency; if capacity for critical work must be guaranteed, provision a separate protected ledger for that work. ```python # Shared pool with priority tiers @@ -140,7 +140,8 @@ pool = cycles.create_budget( period="monthly" ) -# Priority tiers determine who gets denied first +# Application policy decides which work to submit as the pool gets low. +# This pre-check is a scheduling hint, not an atomic priority guarantee. PRIORITY_THRESHOLDS = { "critical": 0.0, # Only denied at $0 remaining "high": 0.10, # Denied below 10% remaining @@ -164,7 +165,7 @@ async def execute_with_priority(task, priority="normal"): **Trade-offs:** - Higher overall utilization — no budget sits idle while another is exhausted -- Critical work is protected even under heavy load +- Application scheduling can preserve more headroom for critical work - Harder to predict per-team or per-user costs for billing purposes - Requires agreement on what constitutes "critical" vs. "low" priority - Risk of low-priority work getting permanently starved in busy periods @@ -176,33 +177,31 @@ async def execute_with_priority(task, priority="normal"): This is less a budget _structure_ and more a deployment pattern, but it's essential for any team that isn't starting from scratch. Shadow mode tracks what _would_ have been denied without actually denying anything. ```python -# Shadow mode: log but don't enforce -budget = cycles.create_budget( - scope=f"tenant:{tenant_id}", - limit_dollars=100.00, - period="daily", - mode="shadow" # Track but don't enforce +# Evaluate the same request without creating a reservation. +decision = await cycles.reserve( + subject={"tenant": tenant_id, "workflow": workflow_id}, + estimate=estimated_microcents, + unit="USD_MICROCENTS", + dry_run=True, ) -# In shadow mode, execute() always succeeds but logs violations -result = await budget.execute(agent.run(task)) - -# After a validation period, check the shadow logs -shadow_report = cycles.get_shadow_report( - scope=f"tenant:{tenant_id}", - period="last_7_days" -) -# Output: "23 calls would have been denied. Peak overage: $47.30." -# Now you can tune the limit before switching to enforce mode. +# Dry run creates no reservation or balance mutation. The application records +# every decision and actual outcome; denied evaluations may also emit +# reservation.denied from the current reference server. +app_log.write({ + "decision": decision, + "estimate": estimated_microcents, + "actual": await run_and_measure(task), +}) ``` **Trade-offs:** -- Zero risk of breaking production workflows during rollout +- The budget result does not block the workflow; the application still owns logging, execution, and other failure handling - Generates real data for sizing budgets accurately - Adds latency (the budget check still happens, just without enforcement) - Teams sometimes stay in shadow mode too long, delaying the value of enforcement -Our [shadow mode rollout guide](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) walks through the full process, including how to analyze shadow logs and choose enforcement cutover criteria. +Our [shadow mode rollout guide](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) walks through the full process, including how to retain and analyze dry-run responses and choose enforcement cutover criteria. ## Pattern 6: Hybrid Model (Tokens + Dollars) @@ -211,29 +210,23 @@ Our [shadow mode rollout guide](/how-to/shadow-mode-in-cycles-how-to-roll-out-bu Token counts and dollar costs diverge when you use multiple models, when pricing changes, or when non-LLM tools (web search, code execution) are part of the agent's toolkit. ```python -# Hybrid budget: track both dimensions -budget = cycles.create_budget( - scope=f"run:{run_id}", - limits={ - "tokens": 500_000, # Hard cap on token consumption - "dollars": 15.00, # Hard cap on dollar spend - }, - on_exhausted="reject" -) - -async def execute_hybrid(task): - # Both limits are checked atomically - result = await budget.execute( - agent.run(task), - estimated={ - "tokens": estimate_tokens(task), - "dollars": estimate_cost(task), - } +# Cycles budgets and reservation amounts each use one unit. Provision separate +# ledgers for TOKENS and USD_MICROCENTS, then have the application acquire both +# reservations before execution. If the second reserve fails, release the first. +token_hold = await reserve(unit="TOKENS", estimate=estimate_tokens(task)) +try: + cost_hold = await reserve( + unit="USD_MICROCENTS", + estimate=estimate_cost_microcents(task), ) - return result - -# Useful for cases where a cheap model uses many tokens -# or an expensive model uses few +except Exception: + await token_hold.release("cost_budget_not_available") + raise + +# Execute only after both holds succeed, then settle each measured amount. +result, actual_tokens, actual_microcents = await run_and_measure(task) +await token_hold.commit(actual_tokens) +await cost_hold.commit(actual_microcents) ``` **Trade-offs:** @@ -241,6 +234,7 @@ async def execute_hybrid(task): - Useful for capacity planning beyond just cost - More complex to configure and explain to users - Requires accurate estimation for both dimensions +- The two unit-specific reservations are coordinated by the application; Cycles does not make them one cross-unit atomic transaction ## Combining Patterns @@ -251,7 +245,7 @@ Most production systems layer two or three of these patterns. A common combinati 3. **[Graceful degradation](/glossary#graceful-degradation)** (Pattern 3) within each workflow run 4. **Shadow mode** (Pattern 5) for rollout -This gives you hard isolation between customers, right-sized limits per use case, user-friendly behavior at the limits, and a safe path to enforcement. +For instrumented paths, this gives you separate customer ceilings, right-sized limits per use case, user-friendly behavior at the limits, and a measured path to enforcement. ``` Tenant Budget ($500/mo) diff --git a/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation.md b/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation.md index f8a93068..6e7cf0f9 100644 --- a/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation.md +++ b/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation.md @@ -37,20 +37,20 @@ Consider a customer support orchestrator that delegates to a refund agent: | Property | Orchestrator | Refund Agent (current) | Refund Agent (attenuated) | |----------|-------------|----------------------|--------------------------| -| Budget | $50.00 | $50.00 (inherited) | $5.00 (sub-budget) | +| Budget path | $50 workflow ledger | Shared workflow ledger only | Shared workflow + explicit $5 agent ledger | | Actions | CRM read, CRM write, email, refund | CRM read, CRM write, email, refund (inherited) | CRM read, refund ≤ $100 | | Data scope | All customers | All customers (inherited) | Current ticket customer only | | Delegation depth | Unlimited | Unlimited (inherited) | 0 (terminal — cannot delegate further) | The left column is what ships today. The right column is what should ship. The difference is not trust — the refund agent is the same code either way. The difference is authority boundaries enforced at the delegation point. -## The attenuation pattern: sub-budgets, action masks, and depth limits +## The attenuation pattern: child ledgers, action masks, and depth limits Authority attenuation requires three enforcement mechanisms at every delegation boundary. -### 1. Sub-budget carving +### 1. Explicit child ledger -When Agent A delegates to Agent B, it reserves a portion of its own budget as Agent B's ceiling. Agent B cannot spend more than that sub-budget, regardless of what the parent has available. +Before Agent A enables Agent B, the operator provisions a smaller ledger at the child agent scope. Cycles does not transfer or reserve a portion of the parent balance at delegation time. Each protected child call submits the workflow and agent subjects, so it must fit both matching ledgers. ```python import uuid @@ -61,33 +61,25 @@ from runcycles import ( client = CyclesClient(CyclesConfig.from_env()) -# Orchestrator reserves against the workflow scope — $50 budget allocated there -orchestrator_res = client.create_reservation(ReservationCreateRequest( - idempotency_key=str(uuid.uuid4()), - subject=Subject(tenant="acme-corp", workflow="support"), - estimate=Amount(unit=Unit.USD_MICROCENTS, amount=5_000_000_000), # $50.00 -)) - -# Delegate to refund agent — a SEPARATE reservation on a narrower scope. +# A protected refund-agent model/tool call uses the narrower scope. # The "agent" field adds a deeper scope: tenant:acme-corp/workflow:support/agent:refund # A budget of $5 must be explicitly allocated at that scope via the Admin API. -refund_res = client.create_reservation(ReservationCreateRequest( +refund_call_res = client.create_reservation(ReservationCreateRequest( idempotency_key=str(uuid.uuid4()), subject=Subject( tenant="acme-corp", workflow="support", agent="refund", - dimensions={"run": "ticket-4821"}, + dimensions={"run_id": "ticket-4821"}, ), - estimate=Amount(unit=Unit.USD_MICROCENTS, amount=500_000_000), # $5.00 + estimate=Amount(unit=Unit.USD_MICROCENTS, amount=25_000_000), # $0.25 )) -# The refund agent's world is bounded by BOTH its agent-level budget ($5) -# AND the parent workflow budget ($50). If either is exhausted, the live reservation -# is rejected. A prompt-injected loop burns at most the agent-level allocation. +# This call must fit BOTH the agent-level ledger ($5) +# AND the workflow ledger ($50). If either lacks capacity, the reservation fails. ``` -The key concept: Cycles budgets are **independent at each scope level** — they do not automatically propagate from parent to child. You must explicitly allocate a budget at the child scope (e.g. `tenant:acme-corp/workflow:support/agent:refund`) via the Admin API. A [reservation](/glossary#reservation) then checks **every derived scope atomically** — the child scope's allocation and the parent scope's allocation must both have room. This is what makes attenuation enforceable: the child's ceiling is set by its own allocation, not inherited from the parent. +The key concept: Cycles budgets are **independent at each scope level** — they do not automatically propagate from parent to child. You must explicitly allocate a budget at the child scope (e.g. `tenant:acme-corp/workflow:support/agent:refund`) via the Admin API. A [reservation](/glossary#reservation) checks every explicitly provisioned ledger among the derived scopes atomically; derived scopes without ledgers are skipped. The child and broader workflow ceilings both apply only when both ledgers exist. ### 2. Toolset-scoped exposure budgets @@ -153,8 +145,8 @@ flowchart TD U[User Request] --> O[Orchestrator Agent] O -->|"reserve $50, depth=3"| CB[Cycles Budget] - O -->|"sub-budget: $5
toolsets: crm, refund
depth=1"| RA[Refund Agent] - O -->|"sub-budget: $2
toolsets: crm
depth=0"| LA[Lookup Agent] + O -->|"agent ceiling: $5
toolsets: crm, refund
depth=1"| RA[Refund Agent] + O -->|"agent ceiling: $2
toolsets: crm
depth=0"| LA[Lookup Agent] RA -->|"reserve $5 on child scope"| CB LA -->|"reserve $2 on child scope"| CB @@ -170,7 +162,7 @@ flowchart TD style CB fill:#2d5a27,color:#fff ``` -The orchestrator holds the total budget. Each delegated agent gets an explicitly provisioned sub-budget, an orchestrator-restricted tool set, and a decremented depth counter. Cycles enforces the submitted spend and exposure budgets at the protocol level before execution. Tool inventory, parameter authorization, and depth limiting remain orchestration logic. Together, those mechanisms bound a compromised or misbehaving sub-agent at the mandatory execution boundary. +The workflow ledger supplies the shared ceiling. Each delegated agent uses an explicitly provisioned child ledger, an orchestrator-restricted tool set, and a decremented depth counter. Cycles enforces submitted spend and exposure budgets before instrumented execution; it does not create or transfer child balances. Tool inventory, parameter authorization, and depth limiting remain orchestration logic. ## Why the industry keeps getting this wrong diff --git a/blog/agent-registries-are-not-runtime-governance.md b/blog/agent-registries-are-not-runtime-governance.md index 49e4c198..20d0a202 100644 --- a/blog/agent-registries-are-not-runtime-governance.md +++ b/blog/agent-registries-are-not-runtime-governance.md @@ -128,7 +128,7 @@ A runtime authority request can carry: | Environment | `production` | | Toolset | `refund.issue` | | Risk tier | `high` | -| Budget scope | `tenant:acme/workflow:refund/run:4821` | +| Budget scope | `tenant:acme/workflow:refund-run-4821` | Some of this metadata is enforcement input; some is audit context. The important part is that it travels with the runtime decision. @@ -148,21 +148,21 @@ The registry tells operators: - which credentials and approved toolsets are associated with it - whether it should be suspended, retired, or reviewed -Runtime authority tells operators: +Cycles records and application telemetry together can tell operators: - which actions were allowed, capped, or denied - which budget or risk scope was consumed - whether the incident is isolated to one tenant, workflow, or agent -- whether child agents inherited narrower authority -- whether enforcement stopped the side effect before it happened +- whether the application mapped child agents to narrower budget scopes +- whether the host honored a rejection before the side effect happened -That makes fleet operations safer. A registry can help identify the affected population. [Bulk actions](/how-to/using-bulk-actions-for-tenants-and-webhooks) can suspend tenants, pause webhooks, or adjust budgets for that population. Runtime events, balances, and [reservation](/glossary#reservation) records confirm what was allowed, capped, denied, or settled. +That makes fleet operations safer. A registry can help identify the affected population. [Bulk actions](/how-to/using-bulk-actions-for-tenants-and-webhooks) can suspend tenants, pause webhooks, or adjust budgets for that population. Cycles events, balances, reservation records, and application logs show the budget decisions and settlement; the host remains the source of truth for tool authorization and external outcomes. ## Where Cycles Fits Cycles is not an agent registry. It does not replace Microsoft Agent 365, an internal CMDB, a marketplace approval flow, or an IAM platform. -Cycles sits at the runtime decision point. It uses scoped budgets, reserve-commit semantics, [idempotency keys](/glossary#idempotency-key), and audit events to decide whether a proposed action remains within bounds. +Cycles sits at the budget decision point. It uses scoped budgets, reserve-commit semantics, and [idempotency keys](/glossary#idempotency-key) to decide whether the submitted estimate remains within configured budget bounds, then records applicable audit and event data. The host separately authorizes the action and enforces the result. That makes it complementary to registry systems. The registry defines the agent's intended envelope. Cycles meters and enforces the envelope while the agent runs. diff --git a/blog/agent-skill-marketplace-supply-chain-playbook.md b/blog/agent-skill-marketplace-supply-chain-playbook.md index 35a44754..5a08c66f 100644 --- a/blog/agent-skill-marketplace-supply-chain-playbook.md +++ b/blog/agent-skill-marketplace-supply-chain-playbook.md @@ -110,9 +110,9 @@ A mandatory [runtime authority](/glossary#runtime-authority) boundary adds anoth - **Capability declarations can become enforceable contracts.** A host or handler can compare the proposed tool against an approved manifest before execution. Cycles can separately reserve a caller-assigned budget, but the current server does not maintain the capability manifest or automatically count invocations by action kind. - **[Risk-tier classification](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk) can assign budget independent of cost.** The caller maps each attempted action to `RISK_POINTS` and makes the reservation mandatory. This is an application policy, not automatic server-side classification. - **The [three-way decision](/glossary#three-way-decision) is external to prompt content.** A dry-run or `decide` call returns `ALLOW`, `ALLOW_WITH_CAPS`, or `DENY`; a live reservation succeeds with `ALLOW` or `ALLOW_WITH_CAPS`, or rejects insufficient budget. The host still has to enforce the result before running the skill. -- **Per-runtime, per-skill blast-radius can be made observable.** Live reservations and settlement produce budget lifecycle records. Because `decide` and dry-run evaluations are non-persisting, the host must also log attempted calls, preflight outcomes, and capability-policy decisions if a security team needs to answer "what did skill X actually try across our fleet last week?" +- **Per-runtime, per-skill submitted exposure can be made observable.** Live reservations and settlement create the applicable reservation, balance, and audit records, while only selected transitions emit runtime Events. `decide` and dry-run evaluations create no reservation or balance mutation; denied evaluations emit `reservation.denied` in the current reference server, but allowed evaluations and external outcomes still require host logging. Retain attempted calls and capability-policy decisions if a security team needs to answer "what did skill X actually try across our fleet last week?" -The pattern that makes this work is the same one that makes the package-registry playbook work: enforce at the layer where the consequence happens, not at the layer where the artifact happens. Provenance proves the artifact wasn't tampered with. Runtime authority proves the action wasn't allowed beyond declared scope. They're complementary; neither replaces the other. +The pattern that makes this work is the same one that makes the package-registry playbook work: enforce at the layer where the consequence happens, not at the layer where the artifact happens. Provenance provides evidence about artifact origin and integrity. A mandatory Cycles boundary proves the submitted estimate passed the configured budget checks; host authorization and outcome logs cover whether the action itself was permitted and what happened. These controls are complementary. ## Minimum viable marketplace policy diff --git a/blog/agent-skills-are-the-new-supply-chain.md b/blog/agent-skills-are-the-new-supply-chain.md index add65f33..a44d0687 100644 --- a/blog/agent-skills-are-the-new-supply-chain.md +++ b/blog/agent-skills-are-the-new-supply-chain.md @@ -160,7 +160,7 @@ That separation is what makes skills usable in production. - [OWASP Agentic Skills Security Assessment Checklist](https://owasp.org/www-project-agentic-skills-top-10/checklist.html) - script and natural-language instruction review guidance - [OWASP Top 10 for Agentic Applications 2026](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) - broader agentic risk framework - [Microsoft Security Blog: Addressing OWASP Top 10 Risks in Agentic AI](https://www.microsoft.com/en-us/security/blog/2026/03/30/addressing-the-owasp-top-10-risks-in-agentic-ai-with-microsoft-copilot-studio/) - lifecycle governance and deployed-agent controls -- [MCP Tool Poisoning Has an 84% Success Rate](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) - tool metadata and supply-chain attack context +- [MCP-ITP: Target-Tool Calls Reached 84.2%](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) - what the MCPTox tool-poisoning benchmark measured - [AI Agent Risk Assessment](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk) - scoring tool risk before production - [Shadow Mode to Hard Enforcement](/blog/shadow-to-enforcement-cutover-decision-tree) - signal-driven cutover from observe-only to blocking - [Webhook Event Delivery Protocol](/protocol/webhook-event-delivery-protocol) - event delivery reliability for governance signals diff --git a/blog/agents-are-cross-cutting-your-controls-arent.md b/blog/agents-are-cross-cutting-your-controls-arent.md index 75236b96..2e0a42b6 100644 --- a/blog/agents-are-cross-cutting-your-controls-arent.md +++ b/blog/agents-are-cross-cutting-your-controls-arent.md @@ -52,21 +52,21 @@ The four categories of control most teams reach for first each fall short for th ### Provider spending caps -OpenAI and Anthropic offer organization or workspace-level spending limits. Google Cloud and AWS provide budget alerts and service quotas that play a similar role, though as alerts and throughput limits rather than hard spend caps. All of them are useful safety nets against catastrophic monthly bills. +OpenAI exposes spend alerts, optional hard organization/project spend limits, rate limits, and prepaid billing controls. Anthropic uses prepaid credits, usage tiers, and workspace reporting. Google Cloud and AWS add billing alerts and capacity quotas. These are useful vendor-native controls, but their scopes and hard-stop semantics differ. But a provider cap lives inside that provider's billing system. It can see what your account spent on its product. It cannot see what you spent on any other provider, on any tool, or on behalf of which customer. -A monthly OpenAI cap of $10,000 does not stop a runaway loop that burns $4,000 in three hours, because the cap measures against the calendar month, not against the run. It does not distinguish your fifty tenants from each other, because it does not know what a tenant is. It does not include the search API call, the payment processor charge, or the outbound mailer that happen between the model calls. And it does not see Anthropic at all. +An OpenAI project alert of $10,000 does not stop a runaway loop that burns $4,000 in three hours, while an optional hard project limit still governs the whole project and is not instantaneous. Neither control creates a per-run boundary. A shared provider project also cannot infer your fifty application tenants, include a separate search API or payment charge, or see Anthropic usage. -The deeper point is not that any of those gaps is a feature request. Provider caps exist to protect the provider from your account. They were never designed to govern your application's cross-provider, cross-tool, cross-customer surface. The full granularity, scope, and timing analysis is in [Cycles vs Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps). +The deeper point is not that any of those gaps is a feature request. Provider controls govern identities, traffic, credits, or billing inside that provider. They do not automatically model your application's cross-provider, cross-tool, cross-customer hierarchy. The full granularity, scope, and timing analysis is in [Cycles vs Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps). ### Observability platforms -Langfuse, Helicone, LangSmith, and OpenLLMetry are excellent at what they do. They trace, attribute cost, measure latency, surface anomalies, and give teams the visibility to diagnose what an agent did after it did it. +Langfuse, Helicone, LangSmith, and OpenLLMetry provide tracing and observability capabilities such as cost attribution, latency analysis, and debugging. Some products also offer gateway-side limits or policy features, so evaluate the enabled deployment rather than assuming every product is passive. -That last phrase is the constraint. Observability platforms read from provider APIs and write to dashboards. They are, by design, retrospective. The trace exists because the action already happened. +The retrospective part remains a constraint: a trace or alert records usage after the protected action has started. A separately configured gateway limit may block at its own boundary, but a dashboard alone is not a pre-execution control. -An observability tool watching a runaway agent burn $1,900 over a weekend will produce a faithful and beautifully detailed record of the disaster. It will not stop the agent at call number 50, when the damage was still $1.50. Alerts shorten the reaction window but they do not create pre-execution control — autonomous agents do not pause to wait for a human. +A passive observability deployment can produce a detailed record of a runaway workload without stopping it at a particular call. Alerts shorten the reaction window but do not themselves create a mandatory pre-execution decision. It is sometimes suggested that observability tools could "just add" enforcement. They could in principle. But it would require them to become a different category of system — a transactional, multi-tenant, atomic decision service in the critical path of every agent action. That is not an extension of an observability product. It is a different product at a different layer. The longer treatment is in [Cycles vs LLM Proxies and Observability Tools](/blog/cycles-vs-llm-proxies-and-observability-tools). @@ -92,9 +92,9 @@ Each of these categories was designed to govern itself. | Tool type | What it was designed to govern | What that means structurally | |---|---|---| -| Provider caps | The provider's billing [exposure](/glossary#exposure) to your account | Single-product, single-org, calendar-grained, embedded in billing | -| Observability | Its own ingestion pipeline and dashboards | Read-only by design, optimized for trace volume, not transactional decisions | -| Framework limits | The orchestrator's own loop | Per-process, per-instance, in-memory, no distributed view | +| Provider controls | The provider's traffic, credits, quotas, or tracked spend | Single-product and vendor-identity scoped | +| Observability | Traces, metrics, and product-specific gateway features | Tracing alone is retrospective; gateway controls cover only routed traffic | +| Framework limits | The orchestrator's own loop | Scope and durability depend on the framework and deployment | | In-process counters | One worker's view of one budget | Local state, fragile under concurrency, no shared truth across workers | A provider cap cannot become a cross-provider authority without becoming an external service that the provider's billing system does not control. An observability tool cannot become an enforcement layer without rewriting its data path from "ingest after the fact" to "decide before the fact" — which is a transactional decision system, not an analytics product. A framework limit cannot become a distributed authority without becoming a service the framework calls into rather than a parameter the framework sets. A homegrown counter cannot become a multi-tenant, multi-provider authority without being rebuilt as exactly that — at which point the team is no longer building a counter. @@ -107,7 +107,7 @@ If the layer has to live outside any one tool, the requirements follow from that - **External authority.** Lives outside any provider, tool, framework, or worker process, so it can see across all of them. - **Atomic, distributed [reservations](/glossary#reservation).** Concurrency-safe by construction. Two agents on two workers cannot both claim the same remaining budget. The race condition that breaks the in-process counter cannot exist by design. -- **Hierarchical [scope](/glossary#scope).** Tenant → workspace → app → workflow → agent → toolset, with budgets enforced at every level. The same primitive answers "how much can this customer spend?" and "how much can this single run spend?" +- **Hierarchical [scope](/glossary#scope).** Tenant → workspace → app → workflow → agent → toolset, with every explicitly provisioned matching ledger checked atomically; absent ledgers are skipped. - **Reserve, commit, release.** Budget is held before the action runs, finalized with the actual cost after, and any unused estimate is returned. This is what makes pre-execution enforcement accurate over time — estimates can be conservative without permanently locking budget the agent never spent. - **A [three-way decision](/glossary#three-way-decision).** `ALLOW`, `ALLOW_WITH_CAPS`, and `DENY` let the caller apply operator-configured degradation such as a cheaper model, smaller context, or skipped optional work. The current server does not tighten caps automatically as balance falls. - **Provider-, tool-, and framework-agnostic.** The same primitive applies regardless of which slice the action lives in. A [reservation](/glossary#reservation) against the agent's budget is the same protocol call whether the spend is an OpenAI token, a Stripe charge, or an outbound email. @@ -118,7 +118,7 @@ These properties are not arbitrary. Each one falls out of "the agent is cross-cu Provider caps are not going to grow into cross-provider authorities. Observability tools are not going to grow into transactional enforcement layers. Framework limits are not going to grow into distributed governance services. None of those evolutions is impossible. They are just different products at a different layer, and the tools that exist today have their architectural assumptions baked into the wrong layer for this job. -Cycles sits at that outside layer. Keep the provider cap. Keep the observability tool. Keep the framework limit. But add the cross-cutting authority for the question none of those can answer: **may this agent, for this customer, on this worker, take the next action right now?** +Cycles sits at that outside layer for one narrower question: **does this caller-submitted amount fit the matching ledgers right now?** Keep provider controls, observability, framework limits, and host authorization for the questions they own. If your agent spans N providers, M tools, K tenants, and W workers, your governance has to span the same N × M × K × W. Anything that lives inside one of those dimensions is a partial view. A partial view is not governance. diff --git a/blog/ai-agent-action-control-hard-limits-side-effects.md b/blog/ai-agent-action-control-hard-limits-side-effects.md index d584d0d1..0347dac2 100644 --- a/blog/ai-agent-action-control-hard-limits-side-effects.md +++ b/blog/ai-agent-action-control-hard-limits-side-effects.md @@ -16,7 +16,7 @@ head: > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. -A customer-onboarding agent is tasked with sending personalized welcome emails to 200 trial accounts. A bug in the template-selection logic causes it to fall back to a collections template — "Your payment is overdue. Immediate action required." The agent sends all 200 emails in under three minutes. Total model spend: $1.40. Total business impact: 34 support tickets, 12 public complaints on social media, and a customer-churn spike that the sales team estimates at over $50,000 in lost pipeline. No spending limit would have caught this. The agent was under budget the entire time. +Consider an illustrative customer-onboarding agent tasked with sending welcome emails to 200 trial accounts. A template-selection bug falls back to a collections notice, and the host sends all 200. If the associated model calls cost about $1.40, customer and business harm can still dwarf token spend. The scenario has no sourced ticket, complaint, or pipeline-loss measurements; it demonstrates the mismatch between monetary cost and action exposure. The problem was not spend. The problem was that the agent _acted_ — and nobody checked what it was about to do. @@ -30,12 +30,12 @@ Side effects are the actions an agent takes in the world. Sending emails. Creati The critical property of side effects is **irreversibility**. A sent email cannot be unsent. A triggered deploy is live. A deleted record may not be recoverable. A Slack message to a customer channel cannot be retracted without notice. These are consequences that persist after the agent stops — and no amount of post-hoc cost reconciliation changes what already happened. -This is why [Cycles](/) is positioned as **[runtime authority](/glossary#runtime-authority)**, not just [budget authority](/glossary#budget-authority). Runtime authority is the umbrella: it covers both how much the agent spends (budget authority) and what the agent does ([action authority](/glossary#action-authority)). Both are enforced through a shared protocol, a common lifecycle, and the same infrastructure. Budget authority is the subset most teams implement first. Action authority is the subset where the costliest incidents live. +This is why a complete **[runtime authority](/glossary#runtime-authority)** design needs both budget authority and application [action authority](/glossary#action-authority). Current Cycles enforces budgets over submitted spend or caller-assigned exposure; it does not infer action risk, inspect tool arguments, or replace the host's permission policy. | Dimension | What it limits | Example controls | What happens if missing | |-----------|---------------|------------------|------------------------| -| **Budget authority** | How much the agent spends | Per-run dollar cap, per-[tenant](/glossary#tenant) quota | Runaway cost — [$4,200 tool loops, $12,400 weekend batches](/blog/ai-agent-failures-budget-controls-prevent) | -| **Action authority** | What the agent does | Tool allowlist/denylist, risk-point caps, per-action [reservation](/glossary#reservation) | Wrong emails sent, accidental deploys, unauthorized file writes, data deletion | +| **Budget authority** | How much the agent spends | Per-run dollar cap, per-[tenant](/glossary#tenant) quota | Runaway cost — [$52.80 loop and $2,300 weekend-batch models](/blog/ai-agent-failures-budget-controls-prevent) | +| **Action authority** | What the agent may do | Application tool/argument policy plus optional risk-point caps and per-action [reservation](/glossary#reservation) | Wrong emails sent, accidental deploys, unauthorized file writes, data deletion | For a deep dive on the budget authority side specifically, see [AI Agent Budget Control: Enforce Hard Spend Limits](/blog/ai-agent-budget-control-enforce-hard-spend-limits). @@ -57,7 +57,7 @@ The tiers are a starting point. Every team's risk map is different — a file wr Cycles provides two mechanisms for tracking agent actions, and choosing the right one is a risk judgment. -**Reserve-commit** is pre-execution authorization. Before the agent sends the email, writes the file, or triggers the deploy, it calls the [Cycles server](/glossary#cycles-server) to request permission. The server checks the available budget (whether that budget is denominated in dollars, [tokens](/glossary#tokens), or risk points), makes an allow/deny decision, and returns the result. Only if the decision is ALLOW does the agent proceed. After execution, the agent commits the actual cost or risk consumed. +**Reserve-commit** is pre-execution budget enforcement. Before the host sends an email, writes a file, or triggers a deploy, it can request a hold from the [Cycles server](/glossary#cycles-server). The server checks the applicable budget in dollars, [tokens](/glossary#tokens), credits, or caller-assigned risk points. A live reservation succeeds with `ALLOW` or configured `ALLOW_WITH_CAPS`, or returns an error when budget is unavailable. The host must separately authorize the action, require both checks before execution, apply returned caps, and commit the best-known actual amount afterward. **Events** are post-hoc accounting. The agent takes the action first, then records what it did. There is no pre-execution check — the action already happened. Events are useful for low-risk actions where the overhead of a pre-execution round-trip is not justified, or for situations where the action completed outside of Cycles entirely and you need to record it for accounting purposes. @@ -66,13 +66,13 @@ Cycles provides two mechanisms for tracking agent actions, and choosing the righ | **Reserve before execution** | `POST /v1/reservations` → execute → `POST /v1/reservations/{id}/commit` | Consequential actions: emails, deploys, deletes, external API calls | Adds one round-trip of latency, but provides pre-execution veto | | **Record after execution** | `POST /v1/events` | Low-risk actions: reads, searches, internal logging, known-cost operations | No latency cost, but no pre-execution control | -The key insight is that **the choice between these two patterns is not about technical capability — it is about risk tolerance**. If the action is reversible and low-impact, record it after the fact. If the action creates consequences that persist beyond the agent's runtime, authorize it before execution. +The key insight is that **the choice between these two accounting patterns depends on risk tolerance**. If an action is reversible and low-impact, a direct-usage event may be sufficient for budget accounting. If it creates persistent consequences, use application authorization and a required reservation before execution. For the full reserve-commit lifecycle, see [How Reserve-Commit Works in Cycles](/protocol/how-reserve-commit-works-in-cycles). For the event pattern, see [How Events Work in Cycles](/protocol/how-events-work-in-cycles-direct-debit-without-reservation). ## RISK_POINTS — Budgeting What Money Cannot Measure -Dollar budgets are the wrong unit for action authority. The opening scenario makes this clear: 200 emails cost $1.40 in model spend. A per-run budget of $100, $50, even $5 would not have stopped a single email. The risk was not monetary. It was reputational, operational, and ultimately commercial — $50,000 in lost pipeline from a $1.40 agent run. +Dollar budgets are the wrong unit for action authority. In the illustrative opening scenario, 200 mistaken emails can have low token cost and high external impact. A monetary budget calibrated for model spend may not reject any email because the submitted dollar amount remains small. Cycles supports a **[RISK_POINTS](/glossary#risk-points)** unit specifically for this problem. Instead of denominating budgets in dollars or tokens, teams assign point values to each action class based on blast radius. A workflow gets a fixed risk-point budget, and every consequential action deducts from it. @@ -96,7 +96,7 @@ For per-tool point assignment, see [Assigning RISK_POINTS to agent tools](/how-t ## Tool Allowlists and Denylists — Capability Control Under Pressure -Risk points cap the _volume_ of consequential actions. But sometimes you need to control _which_ actions are available at all. Cycles provides this through **tool allowlists and denylists**, returned as part of the ALLOW_WITH_CAPS decision. +Risk points cap caller-assigned cumulative action exposure. But sometimes you need to control _which_ actions are available at all. A Cycles budget can return configured **tool allowlists and denylists** as part of an `ALLOW_WITH_CAPS` decision; the host or integration must enforce those cap fields before invoking a tool. When an agent requests a reservation and the server determines that the action is allowed but should be constrained, it returns `decision: ALLOW_WITH_CAPS` along with a `caps` object. That object can include: @@ -123,39 +123,39 @@ With a 100-point risk budget per run, an application could select this policy pr | Read-only | ALLOW_WITH_CAPS | `tool_allowlist: ["read_file", "search"]` | Read-only mode | | Insufficient budget | Live reservation error | — | No further metered actions | -The agent degrades gracefully instead of hard-stopping. It can still complete useful work — reading files, running searches, generating summaries — while the most dangerous capabilities are removed from its reach. This is the "disable" degradation strategy applied to action authority rather than cost control. +If the host enforces the returned caps, the agent can degrade instead of hard-stopping. It can still complete useful work — reading files, running searches, generating summaries — while the host removes higher-risk capabilities. This is the "disable" degradation strategy applied to action authority rather than cost control. For the [three-way decision](/glossary#three-way-decision) model (ALLOW, ALLOW_WITH_CAPS, DENY) and how caps flow through the system, see [Caps and the Three-Way Decision Model](/protocol/caps-and-the-three-way-decision-model-in-cycles). For the full set of degradation strategies, see [Degradation Paths in Cycles](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer). ## Containment Is the Goal, Not Just Billing -Return to the opening scenario. An onboarding agent sends 200 wrong emails. Total model spend: $1.40. +Return to the illustrative opening scenario: an onboarding agent sends 200 wrong emails while model spend remains low. A per-run dollar budget of any reasonable amount would not have helped. The agent was cheap. It was also catastrophic. -A risk-point budget of 100, with 20 points per email, would have stopped the agent after 5 emails. Five wrong emails is a bad day. Two hundred wrong emails is a public incident. The difference is containment. +A risk-point budget of 100, with 20 caller-assigned points per email, permits five successful reservations. If the host requires a reservation before every send, the sixth fails. That bounds the number of budgeted attempts; application authorization and delivery controls still determine whether any email may be sent. -A tool denylist that removed `send_email` after the first anomalous batch — or an allowlist that restricted the agent to `draft_email` instead of `send_email` until a human approved — would have caught the template bug before a single email reached a customer. +An application policy could remove `send_email` after anomaly detection or expose only `draft_email` until human approval. A Cycles budget may return a configured tool-list cap, but the host must select and enforce it; the current server does not detect the template anomaly or tighten caps automatically. Budget control asks: _how much can the agent spend?_ Action control asks: _what can the agent do, and how many times?_ Both questions are necessary. For many teams, the second question is the one that matters more. -The analogy is containment in the security sense. A firewall does not care how much traffic costs. It cares what the traffic _does_ — which ports it targets, which payloads it carries, which systems it reaches. Runtime authority for agents is the same principle applied to a different domain. The question is not "how much did this cost?" but "should this action be allowed to happen at all?" +The analogy is containment in the security sense. A firewall evaluates network policy; an application authorization layer evaluates tools, arguments, principals, and context. Cycles contributes a separate cumulative budget decision for the amount and scope the host submits. -This is what distinguishes runtime authority from cost monitoring. Monitoring tells you what happened. Alerting tells you that something happened. Runtime authority decides, before the action executes, whether it _should_ happen. That pre-execution decision point is the difference between a $50,000 incident and a contained anomaly that surfaces in a log. +This is what distinguishes a composed runtime-authority boundary from cost monitoring. Monitoring and alerting remain valuable, but the host can require authorization plus an accepted budget reservation before execution. The guarantee applies only to paths routed through and bound by that boundary. ## Putting It Together — A Dual-Authority Checklist For every agent workflow your team builds, ask two questions: 1. **What is the dollar budget?** How much can this agent spend on model calls, tool invocations, and API fees? -2. **What is the action budget?** How many consequential actions can this agent take, and which actions should be available at all? +2. **What are the action policy and exposure budget?** Which tools and arguments are authorized, and how much caller-assigned cumulative exposure may the agent consume? | Question | Budget authority | Action authority | |----------|-----------------|-----------------| | **What unit?** | USD_MICROCENTS or TOKENS | RISK_POINTS | | **What scope?** | Per-run, per-tenant, per-workflow | Per-run, per-tenant, per-workflow (same scopes) | -| **What enforcement?** | Reserve-commit on model calls | Reserve-commit on consequential tool calls | -| **What degradation?** | Downgrade model, reduce tokens, skip optional steps | Disable tools, deny high-risk actions, switch to read-only | +| **What enforcement?** | Reserve-commit on protected model calls | Application authorization plus reserve-commit on protected tool calls | +| **What degradation?** | Host downgrades model, reduces tokens, or skips optional steps | Host disables tools, denies high-risk actions, or switches to read-only | | **What accounting?** | Events for known-cost calls | Events for low-risk reads | | **What to monitor?** | Rejection rate, spend-by-scope, budget exhaustion | Risk-point consumption, tool-deny frequency, action-by-tier | diff --git a/blog/ai-agent-action-failures-runtime-authority-prevents.md b/blog/ai-agent-action-failures-runtime-authority-prevents.md index 13923cf1..534df3f3 100644 --- a/blog/ai-agent-action-failures-runtime-authority-prevents.md +++ b/blog/ai-agent-action-failures-runtime-authority-prevents.md @@ -1,9 +1,9 @@ --- -title: "5 Agent Failures Action Controls Prevent" +title: "5 Action-Risk Scenarios and Control Patterns" date: 2026-03-30 author: Cycles Team tags: [action-control, risk, incidents, best-practices] -description: "Five AI agent failures where model spend was under $5 but the business impact was severe — and how action authority with risk-point budgets prevents each one." +description: "Five illustrative low-token, high-impact agent action scenarios, with application authorization and caller-assigned RISK_POINTS patterns to contain each one." blog: true sidebar: false head: @@ -12,25 +12,25 @@ head: content: AI agent failures, action controls, runtime authority, risk budgets, agent side effects, pre-execution enforcement --- -# 5 AI Agent Failures Only Action Controls Would Prevent +# 5 Action-Risk Scenarios and Control Patterns > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. -The companion post — [5 AI Agent Failures Budget Controls Would Prevent](/blog/ai-agent-failures-budget-controls-prevent) — covers the cost dimension: runaway loops, [retry storms](/glossary#retry-storm), scope leaks. Every scenario is measured in dollars of model spend. But agents have a second failure dimension that dollar budgets cannot touch: **actions with consequences**. +The companion post — [5 Agent Cost Failures Runtime Budgets Can Bound](/blog/ai-agent-failures-budget-controls-prevent) — covers runaway loops, [retry storms](/glossary#retry-storm), and scope leaks with checked cost models. Agents have a second failure dimension that dollar budgets alone do not measure: **actions with consequences**. -An agent that sends 200 wrong emails costs $1.40 in [tokens](/glossary#tokens). An agent that triggers a production deploy costs $0.80. An agent that deletes production records costs $2.00. No spending limit — $100, $50, even $5 — would have stopped any of them. The damage is not monetary. It is operational, reputational, and in some cases regulatory. +The five cases below are constructed scenarios, not reports of identified incidents. Their token-cost figures are illustrative estimates. They show how an inexpensive model call can trigger an email, deploy, delete, message, or ticket with much larger external consequences. -These five patterns come up across teams deploying agents with tool-calling capabilities. Each one is preventable with [action authority](/concepts/action-authority-controlling-what-agents-do) — the dimension of [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) that controls what agents *do*, not just what they *spend*. +Containment requires a composed boundary: application [action authority](/concepts/action-authority-controlling-what-agents-do) decides whether a principal may use a tool with particular arguments, while Cycles can meter caller-assigned cumulative exposure through `RISK_POINTS`. Neither layer substitutes for the other. ## How Action Authority Works -[Action authority](/glossary#action-authority) uses the same reserve-commit lifecycle as [budget authority](/glossary#budget-authority), but with a different unit: **[RISK_POINTS](/glossary#risk-points)** instead of dollars. Teams assign point values to each action class based on blast radius — a read costs 1 point, an email costs 20, a deploy costs 50. A workflow gets a fixed risk-point budget. Every consequential action deducts from it. When the budget is exhausted, the agent can still read and reason, but it cannot act. +Applications can use the Cycles reserve-commit lifecycle with **[RISK_POINTS](/glossary#risk-points)** instead of dollars. Teams assign point values to authorized action classes — for example, a read costs 1 point, an email 20, and a deploy 50 — and require a reservation before each protected action. When that budget is exhausted, positive-risk reservations fail. The host still decides which tools are authorized and whether zero-risk actions remain available. For the full mechanism, see [Action Authority: Controlling What Agents Do](/concepts/action-authority-controlling-what-agents-do). For per-tool point assignment, see [Assigning RISK_POINTS to agent tools](/how-to/assigning-risk-points-to-agent-tools). For the unit system, see [Understanding Units in Cycles](/protocol/understanding-units-in-cycles-usd-microcents-tokens-credits-and-risk-points). -## Failure 1: The Wrong Email Template — $1.40 in Tokens, $50K+ in Pipeline +## Failure 1: The Wrong Email Template **The scenario:** @@ -44,11 +44,10 @@ This scenario is [described in detail](/blog/ai-agent-action-control-hard-limits |---|---| | Model spend | $1.40 | | Emails sent | 200 | -| Support tickets generated | 34 | -| Social media complaints | 12 | -| Estimated pipeline impact | $50,000+ | +| Customer/support impact | Unquantified in this constructed scenario | +| Pipeline impact | Unquantified in this constructed scenario | -The model spend is the cost of generating 200 email bodies — a few hundred tokens each. The business impact is the result of 200 customers receiving a hostile message from a company they just signed up to evaluate. No dollar budget would have flagged this. The agent was under budget the entire time. +The modeled spend covers generating 200 short email bodies. The external impact is not derived from token cost. A monetary budget calibrated for normal model usage might accept every call even though the template is wrong. **How action authority prevents this:** @@ -60,9 +59,9 @@ Assign `send_email` a cost of 20 risk points. Set the workflow's risk-point budg | Generate email body | 1 | ~80 | | Send email | 20 | **5** | -The agent sends 5 emails, then the 6th [reservation](/glossary#reservation) is denied with `BUDGET_EXCEEDED`. Five wrong emails is a bad day. Two hundred is a public incident. The difference is containment. +If the host requires a reservation before every send, five email reservations consume 100 points and the sixth fails with `BUDGET_EXCEEDED`. This bounds budgeted sends; it does not validate the template. -The team discovers the template bug after 5 emails instead of 200. They fix it and re-run. Total damage: 5 confused customers, zero social media complaints, zero pipeline impact. +Alerting or review can then surface the template bug, but Cycles alone does not guarantee discovery or determine the resulting customer impact. ## Failure 2: The Accidental Deploy @@ -80,9 +79,9 @@ The agent did exactly what its instructions implied: "fix the build and verify." Assign `trigger_deploy` a cost of 50 risk points. Set the debugging workflow's risk-point budget to 40. -The agent can read logs (1 point), analyze code (1 point), and suggest fixes (1 point) freely. But when it attempts to reserve 50 risk points for the deploy, the reservation is denied — the workflow budget is only 40. The agent returns: "Fix identified. Deploy requires manual approval." +If the host classifies deploy as 50 points and requires a reservation, a 40-point workflow budget rejects that request. Application policy can separately require manual approval and can return: "Fix identified. Deploy requires manual approval." -Alternatively, a tool denylist can remove `trigger_deploy` entirely for debugging workflows. The agent never sees the tool as an option. +Alternatively, the host can omit `trigger_deploy` from debugging workflows or enforce a denylist. A Cycles budget may return a configured denylist cap, but the host must apply it. ## Failure 3: The Data Cleanup Gone Wrong @@ -100,11 +99,9 @@ A $5 per-run budget would not have helped. The delete query cost pennies to gene **How action authority prevents this:** -Assign `execute_delete` a cost of 25 risk points per batch. Set the cleanup workflow's risk-point budget to 100. +Application authorization should validate the target environment, database identity, query shape, and approval requirements before any delete. Separately, assigning `execute_delete` 25 risk points per batch against a 100-point budget bounds the caller-assigned cumulative exposure to four successful reservations. -The agent can delete up to 4 batches before the budget is exhausted. If the first batch deletes unexpected records (production data instead of test data), the team catches it after a contained deletion — not after the entire dataset is gone. - -For an additional layer: a tool denylist can block `execute_delete` for any agent running outside a designated test environment. The configuration error that connected the agent to production would be caught at the action-authority layer, not discovered after the data is gone. +That budget does not detect that the connection points to production or that the query matches the wrong records. Environment-scoped credentials, database permissions, argument validation, and review remain the controls that prevent the first destructive batch. ## Failure 4: The Slack Leak @@ -122,11 +119,11 @@ A $2 per-conversation budget would not have prevented this. The agent was well w Two mechanisms, layered: -1. **Channel allowlist.** The agent's Slack integration is configured with an allowlist of internal channels. Messages to channels not on the list are denied before sending. The `#acme-corp-support` channel is external — the reservation is denied. +1. **Channel allowlist.** The Slack integration enforces an allowlist of internal channels before sending. The `#acme-corp-support` channel is external, so application authorization rejects it. 2. **Risk-point budget.** Assign `send_slack_message` a cost of 20 risk points, with the external-channel variant at 50 points (or denied entirely). The agent can post freely to internal channels but cannot reach customer-facing channels without explicit authorization. -The diagnostic message is blocked. The agent returns: "Cannot post to #acme-corp-support — external channel. Posted to #support-internal instead." The customer never sees the internal details. +With both layers enforced, the application blocks the external-channel message while Cycles can bound the submitted exposure of allowed messages. ## Failure 5: The Ticket Storm @@ -162,21 +159,21 @@ In every case, the agent was allowed to act without asking permission. The syste | Failure | Model Spend | Impact Category | Prevention | With Action Authority | |---|---|---|---|---| -| Wrong email template | $1.40 | Reputational — [$50K+ pipeline](/blog/ai-agent-action-control-hard-limits-side-effects) | 20 risk pts/email, 100 budget | 5 emails instead of 200 | +| Wrong email template | Illustrative $1.40 | Customer/reputational impact, unquantified | Template validation + 20 risk pts/email, 100 budget | At most 5 budgeted sends; content still needs validation | | Accidental deploy | ~$0.80 | Operational — production downtime | 50 risk pts/deploy, or denylist | Denied before execution | | Data deletion | ~$2.00 | Data loss — backup recovery required | 25 risk pts/batch, 100 budget | Stopped after 4 batches | | Slack leak | ~$0.30 | Security — data exposure | Channel allowlist | Blocked to internal only | | Ticket storm | ~$3.50 | Operational — notification cascade | 20 risk pts/ticket, 200 budget | 10 tickets instead of hundreds | -Total model spend across all five scenarios: **under $8.** No dollar budget — $100, $50, $10, even $5 — would have prevented any of them. The common thread is not cost. It is **consequence**. +The approximate model-spend figures across these constructed scenarios total under $8, but they are not measured incident data. A dollar budget sized for routine model use may accept each case because the consequential action, not token spend, carries the larger risk. ## From cost control to runtime authority -Budget authority and action authority are two dimensions of the same architecture. Both use the reserve-commit lifecycle. Both enforce limits before execution, not after. Both support hierarchical scoping (tenant, workspace, workflow, run). Both degrade gracefully when budgets are exhausted. +Budget authority and action authority are complementary parts of one architecture. Cycles can use reserve-commit for spend and caller-assigned exposure at protocol subject scopes. Application policy enforces tool and argument permissions and chooses any graceful-degradation behavior. The difference is the unit of account. Budget authority counts dollars. Action authority counts consequences — measured in risk points, scoped by toolset, enforced by the same infrastructure. -Teams that implement only dollar budgets have half of [runtime authority](/blog/what-is-runtime-authority-for-ai-agents). The half they are missing is where agents cause the most damage — not by spending too much, but by doing the wrong thing. +Teams that implement only dollar budgets still need identity, application authorization, argument validation, and outcome logging. Adding an exposure budget can bound repetition, but it does not make an authorized action safe. ## Next steps diff --git a/blog/ai-agent-budget-control-enforce-hard-spend-limits.md b/blog/ai-agent-budget-control-enforce-hard-spend-limits.md index 93ac60d9..f00b2941 100644 --- a/blog/ai-agent-budget-control-enforce-hard-spend-limits.md +++ b/blog/ai-agent-budget-control-enforce-hard-spend-limits.md @@ -3,7 +3,7 @@ title: "AI Agent Budget Control: Enforce Hard Spend Limits" date: 2026-03-17 author: Cycles Team tags: [budgets, agents, engineering, best-practices] -description: "Why AI agent cost control must happen before execution — not after — and how atomic reserve-commit settlement securely enforces hard spend limits at runtime." +description: "Use atomic reserve-commit controls before AI agent calls to bound submitted cost estimates, handle concurrency, and reconcile actual usage after execution." blog: true sidebar: false head: @@ -16,7 +16,7 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -A development team sets a $50 budget for a coding agent running overnight. The agent hits an ambiguous error, retries with increasingly verbose prompts, fans out across three sub-agents to "research the problem," and loops for four hours. By morning the bill is $2,300. +Consider a constructed scenario: a development team intends to limit a coding agent to $50 overnight. The agent hits an ambiguous error, retries with increasingly verbose prompts, fans out across three sub-agents, and loops for four hours. Without a mandatory per-run boundary, the eventual bill is $2,300. The dashboard showed the spike — at 7 AM, when someone checked. The alert fired at $500, forty minutes after the budget was gone. The provider spending cap was set at $5,000 per month for the whole organization. None of these controls stopped the next model call. @@ -53,7 +53,7 @@ Every team has some form of cost visibility. But visibility is not enforcement. **Alerts** notify you when a threshold is crossed, but they do not block the next action. An alert that fires at $100 does not prevent the $101 call. It tells a human to intervene, and humans are slower than agents. -**Provider spending caps** (OpenAI monthly limits, Anthropic usage tiers) operate at the organization or project level. A $1,000 monthly cap does not help when you need a $50 limit on a single agent run. The granularity is wrong. +**Provider cost controls** use vendor-defined project, workspace, account, credit, or time-window scopes. Unless one of those identities maps to the application run, it does not express a $50 limit for that run. **Rate limits** control how fast an agent can spend, not how much it can spend in total. An agent rate-limited to 10 requests per minute can still burn through $500 over a few hours. Rate limits are a velocity control, not a budget control. @@ -63,13 +63,13 @@ The common thread: these controls are either **after the fact** or **at the wron ## What Hard Budget Control Actually Means -Hard budget control means the runtime makes a **deterministic allow/deny decision before each action**. Not after. Not approximately. Before. +Hard budget control means every protected path must obtain a **deterministic budget decision on its submitted estimate before execution**. Three properties define it: -- **Pre-execution**: the decision happens before the model call, tool invocation, or side effect. If the budget is exhausted, the action does not happen. +- **Pre-execution**: the decision happens before the model call, tool invocation, or side effect. If the reservation is rejected, the mandatory host boundary does not execute that path. - **Atomic**: the budget check and the budget deduction are a single operation. No race condition between "check" and "spend." -- **Total-aware**: the system tracks cumulative exposure across all steps, retries, and concurrent workers — not just the rate of individual requests. +- **Cumulative**: the system tracks submitted reservations and settlements across all instrumented steps, retries, and concurrent workers — not just request velocity. This is the difference between a budget as **billing metadata** (something you reconcile later) and a budget as an **execution constraint** (something the runtime enforces in real time). In agentic systems, only the second kind actually limits spend. @@ -87,18 +87,18 @@ This pattern survives the failure modes that break simpler approaches: - **Retries**: each retry attempt is a new reservation. The budget tracks cumulative exposure across all attempts, not just the latest one. - **Concurrency**: the reservation is atomic. Two workers cannot both claim the last $5 — one gets the reservation, the other is denied with `BUDGET_EXCEEDED`. - **Partial failures**: TTL expiry recovers an abandoned hold, but it does not accurately charge work that started before a crash. Reconcile the outcome and commit the best-known actual usage; if the reservation is already gone, record the missing usage idempotently instead of treating expiry as settlement. -- **[Fan-out](/glossary#fan-out)**: sub-agents share the parent scope's budget. The total is enforced across all branches, not per-branch. +- **[Fan-out](/glossary#fan-out)**: when every branch submits the same workflow scope, each call consumes the shared workflow ledger. Explicit branch or agent ledgers can add narrower overlapping ceilings; Cycles does not transfer a parent balance to children. When a reservation is denied, the agent has options beyond hard-stopping. It can degrade — use a cheaper model, skip optional steps, reduce context length, or defer the task. The enforcement point gives the agent a structured moment to make that decision, rather than failing silently when it runs out of API [credits](/glossary#credits). ## Why Budget Enforcement Prevents Real Failures -These are not hypothetical scenarios. They are patterns that show up in any team running agents at scale: +These illustrative scenarios show common failure shapes: -- **Runaway loop**: agent retries a failing API call 200 times with expanding context windows — $800 in four minutes. With budget enforcement, the agent is denied after attempt 12 when the reservation exceeds remaining budget. -- **[Retry storm](/glossary#retry-storm)**: a transient backend error triggers retries across 10 concurrent agent workers — $3,200 in aggregate before the error resolves. With atomic reservations, workers are denied as the shared budget depletes. -- **Sub-agent fan-out**: an orchestrator spawns 15 research sub-agents, each making 50+ model calls — $1,500 total. With scoped budgets, the orchestrator's budget caps the sum of all sub-agent spend. -- **Concurrent race**: two workers both check "budget remaining: $10" and both proceed — $20 spent on a $10 budget. Atomic reservations eliminate this: one worker gets the reservation, the other is denied. +- **Runaway loop**: an agent retries a failing API call with expanding context windows. With a mandatory per-run ledger, the first attempt whose submitted estimate no longer fits is rejected. +- **[Retry storm](/glossary#retry-storm)**: in an illustrative scenario, a transient backend error triggers retries across 10 concurrent agent workers. A mandatory shared budget can reject later reservations as capacity depletes; the actual savings depend on traffic, estimates, and retry policy. +- **Sub-agent fan-out**: an orchestrator spawns many research sub-agents. If every branch submits the same explicitly provisioned workflow ledger, their reservations consume that shared ceiling. +- **Concurrent race**: two workers both see $10 remaining and submit $8 estimates. Atomic reservations prevent both estimates from being held; one succeeds and the other is rejected. Actual settlement still depends on estimate accuracy and overage policy. For detailed breakdowns with full cost math, see [5 AI Agent Failures Budget Controls Would Prevent](/blog/ai-agent-failures-budget-controls-prevent). @@ -131,7 +131,7 @@ For a detailed shadow mode guide, see [Shadow Mode: How to Roll Out Budget Enfor ## From cost visibility to cost control -Cost overruns are a symptom. The root cause is the absence of a pre-execution enforcement layer — a system that asks "is there budget for this?" before every action, not after. That's what [runtime authority](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) provides: deterministic budget decisions at the point of execution, not retroactive alerts on a dashboard. +The practical change is transactional: reserve an estimate before each protected operation, execute only after success, and settle the best-known actual amount. That [reserve-commit boundary](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) turns a forecast into a concurrency-safe limit for the paths that actually use it. ## Next steps diff --git a/blog/ai-agent-cost-control-2026-litellm-helicone-openrouter-runtime-authority.md b/blog/ai-agent-cost-control-2026-litellm-helicone-openrouter-runtime-authority.md index 9b83f092..128b373c 100644 --- a/blog/ai-agent-cost-control-2026-litellm-helicone-openrouter-runtime-authority.md +++ b/blog/ai-agent-cost-control-2026-litellm-helicone-openrouter-runtime-authority.md @@ -54,16 +54,15 @@ Helicone's strength is that it optimizes cost from multiple angles: caching redu ### OpenRouter: Unified model access with guardrails -[OpenRouter](https://openrouter.ai/docs) provides unified access to hundreds of models through a single API, with a guardrails system for cost control: +[OpenRouter](https://openrouter.ai/docs) provides unified model access with workspace budgets and assignable guardrails: -- **Per-key spending caps** with daily, weekly, or monthly reset -- **Guardrails** — model allowlists, provider allowlists, data privacy policies per key -- **Budget hierarchy** — multiple guardrails stack; the strictest limit wins -- **Per-member and per-key enforcement** — budgets are scoped, not shared +- **Workspace budgets** with daily, weekly, monthly, and lifetime intervals +- **Guardrails** — model/provider allowlists, zero-data-retention, prompt-injection filters, and sensitive-data controls +- **Assignment and inheritance** across workspaces, members, and keys - **Usage dashboard** with key credit and usage introspection -- **Hard enforcement** — requests rejected when key limit reached +- **Preflight enforcement** — requests are rejected once a workspace limit is reached; already-dispatched calls can cause slight overage -OpenRouter's guardrails are straightforward: set a cap, restrict models, and requests stop when the cap is hit. For teams that route all LLM calls through OpenRouter, this is meaningful cost governance with minimal integration work. +For teams that route all LLM calls through OpenRouter, this is meaningful gateway-level cost and data-policy governance with minimal integration work. ## Where all three converge @@ -71,16 +70,16 @@ These tools are more similar than different. They all operate at the **proxy/gat | Capability | LiteLLM | Helicone | OpenRouter | |---|---|---|---| -| Cost tracking | Per-key, per-team, per-model | Per-request, per-session, per-user | Per-key, per-org | -| Pre-execution blocking | Yes (hard budget cap) | Yes (cost-based rate limit) | Yes (key spending cap) | -| Rate limiting | RPM, TPM configurable | Request-count and cost-per-window | Global per-account | +| Cost tracking | Per-key, per-team, per-model | Per-request, per-session, per-user | Per-workspace, member, and key context | +| Pre-execution blocking | Yes (hard budget cap) | Yes (cost-based rate limit) | Yes (workspace budget and guardrails) | +| Rate limiting | RPM, TPM configurable | Request-count and cost-per-window | Workspace budgets are spend limits, not RPM/TPM controls | | Model access control | Per-key model lists | N/A | Model + provider allowlists | | Alerts/notifications | Webhooks (configurable) | Email, Slack | Usage dashboard | | Open source | Yes (self-hostable) | Yes (MIT license + hosted service) | No | If your agents only make LLM calls — no tool invocations, no side effects, no multi-agent delegation — one of these three tools, configured well, can cover most of the cost control problem. -## Where all three stop +## Where the gateway boundary stops The convergence ends when you move past "how much does the agent spend?" to "what is the agent allowed to do?" @@ -88,29 +87,25 @@ The convergence ends when you move past "how much does the agent spend?" to "wha All three tools operate at the model-call layer. They see tokens in, tokens out, and cost. They don't see what the agent *does* with the model's output. -An agent that sends 200 emails to customers costs $1.40 in tokens. A proxy-layer budget would never fire — the cost is trivial. But the action is catastrophic. [Cost and risk are different failure modes](/blog/how-teams-control-ai-agents-today-and-where-it-breaks) that require different controls. +In a constructed scenario, model calls associated with 200 emails cost about $1.40. A proxy-layer cost budget sees inference spend, not whether the application should send an email. [Cost and risk are different failure modes](/blog/how-teams-control-ai-agents-today-and-where-it-breaks) that require different controls. -None of the three tools can express: *"This agent can search freely, but can only send 2 emails and 0 deploys per run."* That requires a unit of measurement for action risk, not just dollars or tokens. +That policy requires application authorization and counters or budgets at the tool-dispatch boundary. Cycles can account for caller-assigned `RISK_POINTS`, but the host must classify and authorize the action. -### 2. None of them have atomic budget enforcement +### 2. Gateway budgets are real, but use a different lifecycle -Budget enforcement under concurrency is a hard distributed systems problem. When 20 agents hit the same budget simultaneously, you need the budget check and the decrement to happen atomically — otherwise, all 20 can read "budget has $5 remaining," all 20 proceed, and you get a [TOCTOU overrun](/blog/we-built-a-custom-agent-rate-limiter-heres-why-we-stopped). +LiteLLM, Helicone, and OpenRouter all provide enforceable gateway controls. LiteLLM supports project/user spend management, Helicone can return `429` for request- or cost-window limits, and OpenRouter checks workspace budgets before routing. Those controls should be used when inference is the governed boundary. -- **LiteLLM** syncs spend via in-memory cache to Redis at ~10ms intervals. Their docs note approximately 10 requests of drift at high concurrency. -- **Helicone** enforces rate limits via a distributed store. Atomicity is not documented. -- **OpenRouter** enforces per-key caps. Concurrency handling is not documented. - -For single-agent or low-concurrency deployments, this is a non-issue. For 20+ concurrent agents sharing a budget, the drift can exceed the budget itself. +Cycles adds an explicit estimate hold before work starts. Concurrent reservations consume available capacity atomically, then commit reconciles actual usage. OpenRouter documents that already-dispatched requests can slightly exceed a workspace limit; compare each gateway's stated concurrency semantics rather than assuming all products behave the same way. ### 3. None of them support delegation attenuation -In multi-agent systems, agent A spawns agent B, which spawns agent C. The proxy layer sees three independent model calls from the same key. There's no way to enforce that B has a smaller budget than A, that C can only use a subset of B's tools, or that the total across the chain stays bounded. +Gateway products support meaningful identities such as projects, workspaces, users, members, teams, and keys. They do not automatically know an application's delegation graph or authorize its downstream tools. -This is the [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) problem: authority should narrow with each delegation hop, never widen. Proxy-layer budgets are flat — they can't express hierarchical scope. +An orchestrator can implement [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) by assigning narrower child scopes, budgets, and permissions. Cycles can account against submitted hierarchical scopes, but it does not discover the parent-child relationship or enforce an action mask automatically. ### 4. None of them have a reserve-commit lifecycle -All three tools track cost after the model call completes. The cost is known when the response arrives, not before. If a long response pushes the total past the budget, the spend has already happened. +Gateways can make a preflight decision from accumulated spend, but exact request cost is known after generation. Cycles' distinct primitive is a caller-supplied estimate reserved before execution, followed by actual settlement. A [reserve-commit lifecycle](/blog/what-is-runtime-authority-for-ai-agents) locks budget before the action, executes only if approved, and reconciles the actual cost after. This is how payment systems, capacity planners, and database transactions handle the same problem — and how budget enforcement becomes structurally safe rather than best-effort. @@ -122,13 +117,13 @@ Proxy tools sit between your application and the model provider. They control th | | Proxy layer (LiteLLM, Helicone, OpenRouter) | Authority layer (Runtime authority) | |---|---|---| -| Controls | Model calls | Agent actions (tool calls, side effects, delegation) | -| Enforces | Cost per key/team/window | Budget per tenant/workflow/agent/action | -| Concurrency | Best-effort | Atomic (reserve-commit) | -| Scope | Flat (key, team) | Hierarchical (tenant → workspace → workflow → agent → toolset) | -| Risk unit | Dollars, tokens | Dollars, tokens, AND [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) (action risk) | +| Controls | Routed model calls | Any host operation explicitly instrumented | +| Enforces | Product-specific workspace/project/team/user/key limits | Configured budget per tenant and submitted subject scope | +| Concurrency | Product-specific; OpenRouter notes possible in-flight overage | Atomic estimate reservation | +| Scope | Product-specific identities and guardrails | Tenant plus submitted workspace/app/workflow/agent/toolset dimensions | +| Risk unit | Usually requests or cost | Dollars, tokens, credits, and caller-assigned [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) | | Degradation | Allow or deny | [ALLOW, ALLOW_WITH_CAPS, or DENY](/glossary#three-way-decision) | -| Delegation | No awareness | [Authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) at every hop | +| Delegation | Application graph not automatic | Orchestrator can implement [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) with narrower scopes | | Setup complexity | Minutes (API key + headers) | Hours (server deployment + SDK integration) | | Routing/caching | Built-in (model routing, response caching) | Not included — needs a proxy layer | | Ecosystem maturity | Large communities, broad integrations | Newer, narrower integration surface | @@ -141,14 +136,15 @@ In a production agent system, you typically need both layers: ``` Agent decides to act - → Runtime authority: "Should this action happen?" (budget + risk check) - → Proxy layer: "Which model handles this?" (routing, caching, rate limit) + → Application policy: authorize the tool and arguments + → Cycles: reserve submitted cost or exposure estimate + → Proxy layer: check gateway policy and route/cache the model call → Provider: Execute the call → Proxy layer: Record cost, check window limit → Runtime authority: Commit actual cost, release unused reservation ``` -The proxy layer **optimizes** what you spend (routing, caching, model selection). The authority layer **enforces** what you're allowed to spend and do (budgets, RISK_POINTS, delegation limits). Together, they cover both cost efficiency and structural enforcement. +The proxy layer can both optimize inference and enforce its own gateway limits. Cycles enforces configured cumulative budgets for submitted operations. The application remains responsible for action authorization and delegation policy. **Concrete example:** The deepest matching budget has a model cap configured, so the authority layer returns `ALLOW_WITH_CAPS`. Your application maps that cap to a cheaper LiteLLM route such as GPT-4o-mini. Cycles returns the configured constraint; the proxy executes the downgrade. The current server does not add caps automatically when balance is low. @@ -173,7 +169,7 @@ The left column is proxy-only. The right column is where you need the authority Three developments since this guide was published in April are worth folding in. -**The bills arrived.** TechCrunch's June 5 report ["The token bill comes due"](https://techcrunch.com/2026/06/05/the-token-bill-comes-due-inside-the-industry-scramble-to-manage-ais-runaway-costs/) documented Uber exhausting its entire 2026 AI coding budget by April, Microsoft revoking internal Claude Code licenses months after enabling them (per The Verge), a Priceline employee describing a 4–5x Cursor renewal, and — per Axios — an unnamed company hitting a $500M Claude bill after failing to set usage limits. Faros AI's April study of 20,000 developers measured per-developer token consumption up 18.6x in nine months. FinOps Foundation executive director J.R. Storment summarized the shift: "In April and May, I started hearing from companies: 'Oh my god, we are 3x over our entire 2026 token budget and it's only April.'" None of this changes the analysis above — it confirms that per-seat intuitions don't survive contact with per-token reality, and that post-hoc tracking finds out after the budget is gone. +**The bills arrived.** TechCrunch's June 5 report ["The token bill comes due"](https://techcrunch.com/2026/06/05/the-token-bill-comes-due-inside-the-industry-scramble-to-manage-ais-runaway-costs/) documented Uber exhausting its entire 2026 AI coding budget by April, Microsoft revoking internal Claude Code licenses months after enabling them (per The Verge), a Priceline employee describing a 4–5x Cursor renewal, and — per Axios — an unnamed company hitting a $500M Claude bill after failing to set usage limits. Faros AI's April study of 20,000 developers measured per-developer token consumption up 18.6x in nine months. FinOps Foundation executive director J.R. Storment summarized the shift: "In April and May, I started hearing from companies: 'Oh my god, we are 3x over our entire 2026 token budget and it's only April.'" None of this changes the analysis above — it confirms that per-seat intuitions don't survive contact with per-token reality, and that unenforced or post-hoc-only tracking finds out after the budget is gone. **Standards bodies moved.** On June 3, the Linux Foundation [announced its intent to launch the Tokenomics Foundation](https://www.linuxfoundation.org/press/linux-foundation-announces-the-intent-to-launch-the-tokenomics-foundation-to-establish-open-standards-for-ai-cost-management) — open specifications, benchmarks, and frameworks for token-based spending, with initial support from Google Cloud, Microsoft, IBM, JPMorganChase, Booking.com, and others. What those specifications should standardize — spend semantics that hold under concurrency and retries, not just reporting formats — is exactly the territory this post's "atomic enforcement" section covers. @@ -181,19 +177,19 @@ Three developments since this guide was published in April are worth folding in. ## The honest take -LiteLLM, Helicone, and OpenRouter are good tools that solve real problems at the proxy layer. If your agents only make LLM calls with no side effects, no concurrent budget sharing, and no delegation chains — a well-configured proxy tool is probably enough. +LiteLLM, Helicone, and OpenRouter are good tools that solve real problems at the proxy layer. If your agents only make routed LLM calls and the gateway's identities, budget windows, and enforcement semantics match the workload, a well-configured proxy tool may be enough. -The moment your agents start calling tools that send emails, write databases, trigger deploys, or spawn sub-agents — the proxy layer stops being sufficient. Not because it's bad, but because it operates at the wrong layer. Controlling model calls doesn't control agent actions. Tracking cost doesn't track risk. +When agents start calling tools that send emails, write databases, trigger deploys, or spawn sub-agents, the proxy layer may stop being sufficient. It can govern the model calls it routes, but it does not automatically authorize or meter application-side actions. Tracking inference cost also does not, by itself, track operational risk. That's the gap runtime authority fills. Not instead of proxy tools — underneath them. ## Sources and versions -Feature claims in this post were verified against the following documentation as of April 2026, with the July 2026 update section verified against its linked sources at publication: +Feature claims in this post were verified against the following documentation on July 24, 2026: -- **LiteLLM** — [docs.litellm.ai/docs/proxy/users](https://docs.litellm.ai/docs/proxy/users) (budgets, rate limits, team management) -- **Helicone** — [docs.helicone.ai](https://docs.helicone.ai) (cost tracking, rate limiting, caching, alerts) -- **OpenRouter** — [openrouter.ai/docs/guides/features/guardrails](https://openrouter.ai/docs/guides/features/guardrails) (guardrails, spending limits, model restrictions) +- **LiteLLM** — [docs.litellm.ai](https://docs.litellm.ai/) (gateway budgets, rate limits, routing, and virtual keys) +- **Helicone** — [custom rate limits](https://docs.helicone.ai/features/advanced-usage/custom-rate-limits) (cost tracking, custom limits, and request controls) +- **OpenRouter** — [guardrails overview](https://openrouter.ai/docs/guides/features/guardrails) and [workspace budgets](https://openrouter.ai/docs/guides/features/workspaces/workspace-budgets) (guardrails, scoped limits, and budget windows) - **Cycles** — [runcycles.io](https://runcycles.io) v0.1.25 (runtime authority, reserve-commit, RISK_POINTS) These tools evolve quickly. If a claim looks outdated, check the linked docs for the latest. diff --git a/blog/ai-agent-cost-management-guide.md b/blog/ai-agent-cost-management-guide.md index 78e11ecd..bcf9b4e9 100644 --- a/blog/ai-agent-cost-management-guide.md +++ b/blog/ai-agent-cost-management-guide.md @@ -16,11 +16,11 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -An infrastructure team we work with had monitoring in place. Good monitoring. They had dashboards showing real-time spend per model, per [tenant](/glossary#tenant), per workflow. They had daily cost reports emailed to engineering leads. They caught their first overspend incident within 4 hours and considered it a success. Then the second incident happened — a [retry storm](/glossary#retry-storm) on a Friday evening that burned through $1,800 in 12 minutes. The dashboard showed it clearly. The alert fired on time. The on-call engineer saw it within 15 minutes. But by then, the money was already spent. That's when they realized: monitoring tells you what happened. It doesn't stop it from happening. +Consider an application with dashboards showing spend per model, [tenant](/glossary#tenant), and workflow plus daily cost reports. In the [illustrative retry-storm model](/blog/ai-agent-failures-budget-controls-prevent), layered retries generate 1,026 model calls and $33.86 of spend in 12 minutes under the stated rates. A dashboard can show that clearly, but it does not stop the calls that occur before an operator responds. -This guide presents a maturity model for AI agent cost management. Five tiers, from "no controls" to "hard enforcement." Most teams are at Tier 0 or Tier 1. The teams that run agents at scale without cost surprises are at Tier 4. The path between those points is well-defined — and each tier is a legitimate stopping point depending on your risk tolerance and scale. +This guide presents a five-tier maturity model for AI agent cost management, from no controls to mandatory pre-execution budgets. Use it to identify the capabilities your deployment has and the gaps that remain; the appropriate stopping point depends on risk tolerance and scale. ## The Cost Management Maturity Model @@ -29,8 +29,8 @@ This guide presents a maturity model for AI agent cost management. Five tiers, f | 0 | No Controls | Trust the code, check the invoice | No | Days to weeks | | 1 | Monitoring | Dashboards and cost visibility | No | Hours | | 2 | Alerting | Automated notifications on thresholds | No | Minutes | -| 3 | Soft Limits | Rate limiting, provider caps, counters | Partially | Seconds (but leaky) | -| 4 | Hard Enforcement | Pre-execution [runtime authority](/glossary#runtime-authority) | Yes | Milliseconds (before execution) | +| 3 | Independent Limits | Rate limits, provider controls, application counters | Scope-dependent | Vendor or application dependent | +| 4 | Hard Enforcement | Mandatory pre-execution [budget authority](/glossary#budget-authority) | Bounds covered paths | Before protected execution | Each tier builds on the one below it. You don't skip tiers — you add capabilities. A team at Tier 4 still uses dashboards (Tier 1) and alerts (Tier 2). The difference is that dashboards are no longer the _last_ line of defense. @@ -44,15 +44,14 @@ This is where every team starts. And for prototyping, it's fine. When you're bui The problem is that teams stay at Tier 0 longer than they should. The prototype works. Traffic grows. What was $20/month in testing becomes $2,000/month in production — and nobody notices until the invoice arrives because there's nothing to notice _with_. -**When Tier 0 is acceptable:** -- Prototyping and local development -- Internal tools with fewer than 10 users -- Batch jobs with predictable, bounded input sizes -- Any workload where the maximum possible spend per month is less than you'd spend investigating the cost +**When Tier 0 may be acceptable:** +- Prototyping and disposable local development +- Workloads with a deliberately bounded maximum and no consequential side effects +- Environments where provider-side hard limits already match the full risk boundary -**When to graduate:** The moment you deploy to production with real user traffic, or the moment a single agent run could theoretically cost more than $50, Tier 0 becomes a liability. +**When to graduate:** Before a workload reaches production if one run, user, or side effect can exceed the team's documented risk tolerance. -**Cost of staying too long:** We see teams discover $3,000-$15,000 in unexpected spend the first month they scale past prototype traffic. The most common trigger is a single runaway agent — not a fleet-wide problem, just one agent that looped 500 times on a weekend. +**Cost of staying too long:** The invoice or provider dashboard becomes the first durable signal. The amount at risk depends on traffic, model pricing, retry behavior, and provider controls; use a constructed worst-case scenario instead of a universal dollar range. ## Tier 1: Monitoring @@ -61,9 +60,9 @@ The problem is that teams stay at Tier 0 longer than they should. The prototype **Tools:** | Tool | What it provides | Limitation | |---|---|---| -| Provider dashboards (OpenAI, Anthropic, Google) | Per-model daily/monthly spend | 15-60 min delay, no per-run granularity | +| Provider dashboards (OpenAI, Anthropic, Google) | Provider-defined usage and spend views | Refresh timing and scope granularity vary by provider | | Datadog / Grafana | Custom dashboards from application logs | Requires instrumentation, adds latency to analysis | -| LangSmith / Langfuse | LLM-specific observability with traces | Focused on debugging, limited budget awareness | +| LangSmith / Langfuse | LLM-specific observability with traces | Product-dependent gateway controls; tracing alone remains retrospective | | Custom logging | Full control over metrics and granularity | Engineering investment to build and maintain | **What you gain:** Visibility. You can answer "how much did we spend yesterday?" and "which agent costs the most?" within minutes instead of waiting for the monthly invoice. You can identify cost trends and catch anomalies — if someone is looking. @@ -130,25 +129,25 @@ Alerts are essential. They are not sufficient. Every dollar spent between "alert | Tool | Mechanism | Limitation | |---|---|---| | Provider rate limits | Requests per minute / [tokens](/glossary#tokens) per minute | Not cost-aware — 100 RPM doesn't distinguish $0.01 and $5.00 calls | -| Provider spending caps | Monthly/daily hard caps | Too coarse for per-run control, often have propagation delay | +| Provider cost controls | Soft budgets, prepaid credits, account limits, or quotas | Vendor-defined scope and semantics may not match a run | | Application-level counters | In-process tracking of spend | Single-process only, breaks under concurrency | | API gateway rate limiting | Request-level throttling | No visibility into token counts or costs | -**What you gain:** Automated response. The system takes action without waiting for a human. Rate limits prevent runaway loops from generating unlimited calls. Spending caps provide a hard ceiling at the account level. +**What you gain:** Automated response. Rate limits bound request throughput at the scope they cover. Provider cost controls vary: some are soft alerts, some are prepaid-credit stops, and some are hard limits for a particular account or workspace. **What you don't gain:** Precision. Soft limits have three fundamental gaps: -**Gap 1: Not cost-aware.** Rate limits cap throughput, not spend. A rate limit of 100 requests per minute treats a 500-token Haiku call the same as a 50,000-token Opus call. The former costs $0.004. The latter costs $4.50. Same rate limit, 1,000x cost difference. +**Gap 1: Not cost-aware.** A request-count rate limit caps throughput, not spend. Two calls can consume very different input, output, cached, or reasoning-token volumes while counting as one request each. **Gap 2: Not atomic under concurrency.** Application-level counters work like this: read the current spend, check if there's room, execute the call, update the spend. With 10 concurrent agents, all 10 can read "budget has $5 remaining," all 10 can decide to proceed, and all 10 can execute — spending $50 against a $5 budget. This is a classic time-of-check-to-time-of-use (TOCTOU) race condition. -**Gap 3: Not per-run scoped.** Provider caps are monthly or daily. They can't enforce "this single agent run should cost no more than $10." When the daily cap is $500 and one run burns $200, the cap doesn't fire — but you've consumed 40% of the day's budget in one run, starving every other run. +**Gap 3: Scope mismatch.** Provider controls use vendor-defined account, project, workspace, key, credit, or time-window scopes. Unless one of those identities maps to an application run, it does not enforce "this single run should consume no more than $10." -**When to graduate:** When any of these gaps cause a real incident. Typically, this is either a concurrency-related overspend (Gap 2) or a single run consuming a disproportionate share of a coarse budget (Gap 3). If you're running more than a few concurrent agents, you will hit Gap 2. It's a matter of when, not if. +**When to graduate:** Add a stronger boundary when the existing control cannot express a required scope, unit, timing guarantee, or concurrency invariant. Do not wait for an incident if a load test already demonstrates the gap. ## Tier 4: Hard Enforcement -**What it looks like:** A dedicated runtime authority service sits in the execution path of every LLM call. Before an agent calls a model, it requests authorization from the budget service. The service atomically reserves the estimated cost. If the budget is exhausted, the call is denied before it executes. The agent receives a clear signal and can degrade gracefully. +**What it looks like:** A budget service sits in the mandatory execution path of each protected LLM call. Before the caller invokes a model, it requests a live reservation for the estimate. If the matching budget lacks capacity, the reservation fails and the caller must not send the model request. This is the tier where prevention replaces response. There is no gap between detection and action because the check happens _before_ the spend. @@ -160,7 +159,7 @@ This is the tier where prevention replaces response. There is no gap between det 4. If approved: the call proceeds, and actual cost is reconciled afterward 5. If denied: the agent receives a budget-exhausted signal and follows its degradation path -The atomic check-and-decrement is critical. It's what prevents the TOCTOU race condition from Tier 3. No matter how many concurrent agents check simultaneously, the runtime authority serializes the reservations. If the budget has $5 left and two agents each request $4, one succeeds and one is denied. Always. +The atomic check-and-decrement is critical. Within the matching Cycles ledgers, if $5 remains and two concurrent reservations each request $4, at most one can succeed. The guarantee applies only to traffic that uses the boundary and to the submitted estimates. **What you gain:** @@ -171,15 +170,15 @@ The atomic check-and-decrement is critical. It's what prevents the TOCTOU race c | Per-run granularity | A run can receive its own enforceable scope by placing a unique run ID in a standard Subject field | | Hierarchical budgets | Configured ledgers along tenant → workspace → app → workflow → agent → toolset are checked atomically; absent ledgers are skipped | | [Graceful degradation](/glossary#graceful-degradation) | Preflight can return `ALLOW_WITH_CAPS`; the application interprets and applies the caps | -| Audit trail | Every reservation and denial is logged with full context | +| Lifecycle records | Live reservations and settlements create budget records; retain non-persisting preflight results and application outcomes separately | **What Cycles provides at this tier:** -[Cycles](/) is built specifically for Tier 4. It's an open-source runtime authority system that enforces hard spend limits before execution. The core API is a reserve-execute-commit loop that works across any model provider and any agent framework. +[Cycles](/) is built specifically for Tier 4. Its reserve-execute-commit API can provide a mandatory budget boundary around model or tool calls regardless of provider, when the application integrates that path and supplies a conservative estimate. -Budgets can be scoped at any level — per tenant, per workflow, per run, or any combination. When a budget is exhausted, the denial includes enough context for the agent to make an intelligent decision: fall back to a cheaper model, return a partial result, or stop and explain why. +Budgets can use any standard subject level, and a unique workflow value can represent one run. A live denial returns a structured error code and request/trace identifiers; the application should already have the attempted subject and can query balances when it needs ledger detail. Its error handler decides whether to fall back to a cheaper model, return a partial result, or stop. -The key insight behind Tier 4 is that budget enforcement is infrastructure, not application logic. You don't implement it in each agent. You implement it once, in the execution path, and every agent benefits. +The key insight behind Tier 4 is that budget state belongs in shared infrastructure. Each protected execution path still needs an integration, but multiple agents can rely on the same authority instead of maintaining independent counters. ## How to Graduate Between Tiers @@ -206,11 +205,11 @@ The best-run teams we see operate at all tiers simultaneously: - **Tier 1 (Monitoring):** Dashboards showing real-time and historical spend by tenant, workflow, and model. Used for capacity planning, cost optimization, and trend analysis. - **Tier 2 (Alerting):** Alerts on anomalies that enforcement alone doesn't catch — unusual patterns, new cost trends, budget utilization approaching limits. These are informational alerts for humans, not enforcement mechanisms. -- **Tier 4 (Hard Enforcement):** Cycles runtime authority in the execution path. Every call is authorized before execution. Budgets are scoped per-tenant and per-run. +- **Tier 4 (Pre-execution budget enforcement):** A mandatory Cycles integration checks the submitted estimate before protected execution. Application authorization remains separate. -Notice Tier 3 is absent. That's intentional. Once you have Tier 4, rate limits and application counters are redundant for cost control. (For more on why building your own enforcement layer is deceptively complex, see [Vibe Coding a Budget Wrapper vs. Owning a Runtime Authority](/blog/vibe-coding-budget-wrapper-vs-budget-authority).) You might still have rate limits for other reasons (protecting downstream services, fairness), but they're no longer your cost control mechanism. +Notice Tier 3 is absent from this example stack. A pre-execution budget boundary can replace a soft application cost counter, but rate limits remain useful for provider quotas, fairness, abuse resistance, and downstream protection. (For more on implementation trade-offs, see [Vibe Coding a Budget Wrapper vs. Owning a Runtime Authority](/blog/vibe-coding-budget-wrapper-vs-budget-authority).) -The monitoring and alerting layers serve a different purpose once enforcement is in place. They shift from "detect overspend" to "understand cost patterns and optimize." An alert that says "Tenant X is using 80% of their monthly budget on day 15" isn't an emergency — enforcement prevents overspend. But it's a signal that you should review their budget allocation or their agent efficiency. +The monitoring and alerting layers serve a different purpose once enforcement is in place. They help identify estimate drift, bypass traffic, unusual usage, and budgets that need review. Pre-execution holds protect submitted estimates on instrumented paths; they do not make those operational signals unnecessary. ## The Rollout Path @@ -230,7 +229,7 @@ This process takes 4-8 weeks for most teams. The shadow mode step is critical ## From cost visibility to cost control -Cost overruns are a symptom. The root cause is the absence of a pre-execution enforcement layer — a system that asks "is there budget for this?" before every action, not after. That's what [runtime authority](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) provides: deterministic budget decisions at the point of execution, not retroactive alerts on a dashboard. +The maturity step is from measurement to a mandatory execution boundary. Dashboards calibrate budgets; gateway limits protect routed inference; a [runtime budget authority](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) can reserve capacity across the additional application operations you instrument. ## Next steps diff --git a/blog/ai-agent-deleted-prod-database-9-seconds.md b/blog/ai-agent-deleted-prod-database-9-seconds.md index 25ebfa47..e6f182f4 100644 --- a/blog/ai-agent-deleted-prod-database-9-seconds.md +++ b/blog/ai-agent-deleted-prod-database-9-seconds.md @@ -21,7 +21,7 @@ JER ([@lifeof_jer](https://x.com/lifeof_jer/status/2048103471019434248)), a deve > "Yesterday afternoon, an AI coding agent — Cursor running Anthropic's flagship Claude Opus 4.6 — deleted our production database and all volume-level backups in a single API call to Railway, our infrastructure provider. It took 9 seconds." -When asked to explain itself, the agent produced a written confession enumerating the specific safety rules it had violated. JER [later posted](https://x.com/lifeof_jer/status/2048576568109527407) that Railway recovered the data, and Railway has since reportedly patched the relevant endpoint behavior ([The Register](https://www.theregister.com/2026/04/27/cursoropus_agent_snuffs_out_pocketos/), [The Verge](https://www.theverge.com/ai-artificial-intelligence/919240/pocketos-maker-says-an-ai-agent-deleted-our-production-database-in-9-seconds)). +When asked to explain itself, the agent produced a written confession enumerating the specific safety rules it had violated. JER [later posted](https://x.com/lifeof_jer/status/2048576568109527407) that Railway recovered the data, and Railway has since reportedly patched the relevant endpoint behavior ([The Register](https://www.theregister.com/software/2026/04/27/cursor-opus-agent-snuffs-out-startups-production-database/5224442), [The Verge](https://www.theverge.com/ai-artificial-intelligence/919240/pocketos-maker-says-an-ai-agent-deleted-our-production-database-in-9-seconds)). The public record here is still based largely on JER's account and some agent-generated explanation, so what follows should be read as an operator incident report, not a complete forensic audit. The structural argument doesn't depend on every detail surviving forensics — the basic chain (credentialed agent, destructive infra call, no pre-execution gate, large blast radius) is independently corroborated by the reporting above. diff --git a/blog/ai-agent-failures-budget-controls-prevent.md b/blog/ai-agent-failures-budget-controls-prevent.md index b7232431..f77a192f 100644 --- a/blog/ai-agent-failures-budget-controls-prevent.md +++ b/blog/ai-agent-failures-budget-controls-prevent.md @@ -1,9 +1,9 @@ --- -title: "5 AI Agent Failures Budget Controls Would Prevent" +title: "5 Agent Cost Failures Runtime Budgets Can Bound" date: 2026-03-14 author: Cycles Team tags: [incidents, costs, best-practices] -description: "Review five AI agent failure scenarios with cost estimates and learn how pre-execution budget enforcement can stop runaway work before spend escapes safely." +description: "Review five illustrative AI agent cost scenarios and learn how mandatory pre-execution budget reservations can bound spend before it escapes control early." blog: true sidebar: false head: @@ -12,17 +12,17 @@ head: content: AI agent failures, budget controls, runaway agent costs, pre-execution budgets, LLM spend limits, runtime authority --- -# 5 AI Agent Failures Budget Controls Would Prevent +# 5 Agent Cost Failures Runtime Budgets Can Bound > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. -Every team running AI agents in production has at least one horror story. The details vary — a runaway loop, a [retry storm](/glossary#retry-storm), a weekend deployment nobody was watching — but the punchline is always the same: a surprising number on an invoice and a postmortem that concludes with "we need better controls." We've collected these stories from teams across the industry, and five patterns come up again and again. Each one is preventable. Each one keeps happening because the same architectural gap — no pre-execution budget check — exists in most agent systems. +Runaway loops, [retry storms](/glossary#retry-storm), unattended batches, stale counters, and poorly scoped limits are recurring agent-cost risks. The five cases below are illustrative scenarios built from those patterns, not reports from identified teams. Their arithmetic uses the stated token volumes and model rates. -These aren't edge cases. They're the predictable consequences of running autonomous systems that can spend money without asking permission first. Here are five failures, the math behind each one, and the specific mechanism that would have prevented them. +Here are five failure models, the math behind each one, and the budget mechanism that can bound configured spend when every protected path uses a mandatory reservation boundary. -## Failure 1: The Infinite Tool Loop — $4,200 in 3 Hours +## Failure 1: The Infinite Tool Loop — $52.80 in 3 Hours **The scenario:** @@ -45,24 +45,22 @@ The agent doesn't give up because it's not designed to. Its instructions say "it | Avg input [tokens](/glossary#tokens) per call (growing context) | 12,000 | | Avg output tokens per call | 2,500 | -Context growth is the killer here. Each iteration appends the previous attempt and the test output to the conversation. By iteration 50, the agent is sending 25,000 input tokens per call. By iteration 200, it's sending 40,000+. The average across all iterations works out to about 12,000 input tokens — heavily weighted toward the later, more expensive calls. +This model assumes 12,000 input tokens per call on average. A real loop with growing context should use its measured per-call distribution rather than extrapolating from this flat average. Cost calculation: - Input: 960 calls x 12,000 tokens = 11.52M tokens x $2.50/1M = $28.80 - Output: 960 calls x 2,500 tokens = 2.4M tokens x $10.00/1M = $24.00 -- Subtotal per iteration is low, but 240 iterations compound to: **~$4,200** - -The actual cost is higher than the simple average suggests because the later iterations — when the context is largest — are disproportionately expensive. The last 50 iterations alone account for nearly 40% of the total cost. +- Total: **$52.80**, or about **$0.22 per iteration** **How budget enforcement prevents this:** -A per-run budget of $15 — generous for a test generation task — would have stopped this agent after approximately 8 iterations. Cycles checks the budget before each LLM call. When the run budget is exhausted, the call is denied. The agent receives a budget-exhausted signal and stops, returning a clear message: "Budget limit reached. Test generation did not converge after 8 iterations. Manual review required." +A per-run budget of $15 would reject a protected call at roughly iteration 68 under the flat-average assumptions above. The host must require a successful reservation before each LLM call and stop on a live reservation error. Its message could say: "Budget limit reached. Test generation did not converge within the configured allocation. Manual review required." -The team would have lost $15 instead of $4,200. More importantly, they would have discovered the dependency issue hours earlier because the agent's failure would have surfaced immediately instead of being hidden behind a loop that _appeared_ to be making progress. +The configured budget would bound this run near $15 rather than allowing the full $52.80 scenario, subject to the integration's estimates and settlement policy. It would also surface non-convergence earlier. For the full anatomy of this failure mode, see [Runaway Agents: Tool Loops and Budget Overruns](/incidents/runaway-agents-tool-loops-and-budget-overruns-the-incidents-cycles-is-designed-to-prevent). -## Failure 2: The Retry Storm — $1,800 in 12 Minutes +## Failure 2: The Retry Storm — $33.86 in 12 Minutes **The scenario:** @@ -98,27 +96,25 @@ Now multiply across all conversations during the degraded period: | Conversations hitting CRM lookup | 38 | | LLM calls per conversation (with retry cascades) | ~27 | | Total LLM calls | ~1,026 | -| Model | Claude Sonnet 4 | +| Model | Claude Sonnet 4.6 | | Avg input tokens per call | 5,000 | | Avg output tokens per call | 1,200 | Cost calculation: - Input: 1,026 x 5,000 = 5.13M tokens x $3.00/1M = $15.39 - Output: 1,026 x 1,200 = 1.23M tokens x $15.00/1M = $18.47 -- Per-conversation cost during storm: ~$0.89 -- But many conversations had multiple CRM lookups, and the retry cascades overlapped - -The total across all affected conversations, including partial retries and the cascading effect of shared infrastructure load (retries from one conversation slowing responses for others, triggering timeout-based retries): **~$1,800**. +- Per-conversation cost during storm: **~$0.89** +- Total across the 38 affected conversations: **~$33.86** **How budget enforcement prevents this:** -A per-conversation budget of $2.00 would have capped each conversation's retry cascade. After the first few retry cycles consumed the budget, subsequent LLM calls would be denied. The agent would return: "I'm unable to look up your order status right now. Our systems are experiencing issues. Please try again in a few minutes." +A per-CRM-lookup budget of $0.25 would allow roughly seven calls at the stated average cost before the next protected call fails to reserve. The host could then return: "I'm unable to look up your order status right now. Our systems are experiencing issues. Please try again in a few minutes." -Total cost with enforcement: ~$76 (38 conversations x $2.00 cap) instead of $1,800. And the user experience would actually be _better_ — a fast, clear error message instead of a long wait followed by the same error. +Total configured exposure across 38 affected conversations would be about $9.50 instead of $33.86. The exact result depends on estimate accuracy and which calls the integration protects. For more on this failure pattern, see [Retry Storms and Idempotency Failures](/incidents/retry-storms-and-idempotency-failures). -## Failure 3: The Friday Deploy — $12,400 Over the Weekend +## Failure 3: The Friday Deploy — $2,300 Over the Weekend **The scenario:** @@ -147,23 +143,21 @@ Cost calculation: - Output: 57,500 x 2,000 = 115M tokens x $10.00/1M = $1,150 - Subtotal: $2,300 -But this assumes flat context size. In practice, the refactoring tasks (about 30% of items) loaded much larger files — some with 30,000+ input tokens per call. And the conversation context grew within each task. - -Adjusted total with realistic context sizes and the long tail of expensive refactoring tasks: **~$12,400**. +Total under the stated flat-average assumptions: **$2,300**. Larger context windows would raise that number, but this scenario does not invent an unmeasured long-tail multiplier. -The dashboard updated hourly. The alert was set for daily spend thresholds. The agent processed items steadily all weekend — never fast enough to trigger rate limits, never failing hard enough to stop, just continuously spending at a rate that looked normal in any single hour but accumulated to $12,400 over 60 hours. +The dashboard updated hourly. The alert was set for daily spend thresholds. The agent processed items steadily all weekend — never fast enough to trigger rate limits and never failing hard enough to stop. **How budget enforcement prevents this:** Two levels of enforcement would have contained this: -1. **Per-task budget of $5.00**: Caps each individual task. The few tasks that hit edge cases and consumed 40+ calls would have been stopped early. Cost savings: ~$2,000 from runaway individual tasks. +1. **Per-task budget of $5.00**: Bounds an individual task that enters an unusually expensive retry or context-growth path. -2. **Batch budget of $2,500**: A budget for the entire backlog processing run. When the total spend hit $2,500, processing would pause. The team would return Monday to find 80% of the backlog completed within budget and a clear log showing why processing stopped. +2. **Batch budget of $500**: Bounds the unattended backlog run. When the next protected call cannot reserve within that allocation, the host pauses processing. Completion percentage depends on task ordering and actual context sizes. -Instead of a $12,400 surprise, the team would have spent $2,500 with full visibility into the remaining work. They could then decide: increase the budget for the remaining items, optimize the expensive tasks first, or switch to a cheaper model for the remainder. +Instead of allowing the full $2,300 illustrative run, the team would return to a batch stopped near its $500 allocation. They could then decide whether to increase the budget, optimize expensive tasks, or switch models. -## Failure 4: The Concurrent Burst — $3,200 in 4 Minutes +## Failure 4: The Concurrent Burst — $32.40 Against a $5 Balance **The scenario:** @@ -171,9 +165,9 @@ A SaaS platform provides AI-powered document analysis to enterprise customers. E At 2:15 PM, a large customer uploads a batch of 200 documents simultaneously through the API. The platform spins up 20 concurrent agent instances to process them in parallel. Each agent checks the customer's remaining budget before starting. -Here's the race condition: all 20 agents read the budget balance at nearly the same time. The balance shows $500 remaining. Each agent estimates its task will cost ~$15 and sees sufficient budget. All 20 proceed. +Here's the race condition: all 20 agents read the budget balance at nearly the same time. The balance shows $5 remaining. Each agent estimates its document will cost about $0.17 and sees sufficient budget. All 20 proceed. -But 20 agents each spending $15 is $300 per round. And each agent makes multiple LLM calls before reporting its spend back to the counter. By the time the first agent finishes and updates the balance, the other 19 have already committed to their calls. +Each agent makes multiple LLM calls before reporting spend back to the counter. By the time the first agent finishes and updates the balance, the other 19 have already started. More documents enter subsequent work before delayed counter updates converge. **The math:** @@ -183,34 +177,34 @@ But 20 agents each spending $15 is $300 per round. And each agent makes multiple | Documents processed before detection | 200 | | LLM calls per document | 4 | | Total LLM calls | 800 | -| Model | Claude Sonnet 4 | +| Model | Claude Sonnet 4.6 | | Avg input tokens per call | 6,000 (document content) | | Avg output tokens per call | 1,500 | Cost calculation: - Input: 800 x 6,000 = 4.8M tokens x $3.00/1M = $14.40 - Output: 800 x 1,500 = 1.2M tokens x $15.00/1M = $18.00 -- Per-document cost: ~$16.20 -- 200 documents: **~$3,200** +- Per-document cost: **~$0.162** +- 200 documents: **~$32.40** -The customer's budget was $500. The actual spend was 6.4x the budget. The application counter showed the correct balance at every read — it was never wrong. It was just stale. The time between reading the balance and updating it (the TOCTOU window) was long enough for 19 other agents to squeeze through. +The customer's remaining budget was $5. The modeled spend was about 6.5 times that balance. The application counter was not necessarily arithmetically wrong; its read-then-update sequence was stale under concurrency. **How budget enforcement prevents this:** Cycles uses atomic [reservations](/glossary#reservation). When an agent requests permission to spend, Cycles atomically decrements the balance. There is no window between checking and spending — they're the same operation. -With a $500 customer budget and atomic reservations: -- Agents 1-31 get approved (31 documents x ~$16.20 = ~$502) -- Agent 32 is denied — the atomic decrement shows insufficient balance -- All subsequent requests are denied immediately +With a $5 remaining budget and $0.17 estimates: +- Reservations for 29 documents can hold $4.93 +- The reservation for document 30 is rejected for insufficient remaining budget +- Later requests remain rejected until capacity is released or added -Total spend: ~$502 (slightly over due to estimation variance, reconciled afterward). That's $500 instead of $3,200. The 169 remaining documents are queued for processing when the customer adds budget or the next billing period starts. +At the stated average actual cost, 29 documents settle near $4.70. The remaining documents are queued until capacity is added or reset. Exact settlement still depends on actual usage and the configured overage policy. The critical difference is atomicity. Cycles doesn't read-then-write. It performs an atomic compare-and-decrement. No matter how many concurrent agents check simultaneously, the budget can never be overdrawn by more than a single reservation's estimation variance. For the full technical analysis of this failure pattern, see [Concurrent Agent Overspend](/incidents/concurrent-agent-overspend). -## Failure 5: The Scope Leak — $8,500/Month Unnoticed +## Failure 5: The Scope Leak — $5,600/Month Above Plan **The scenario:** @@ -234,7 +228,7 @@ The org-level budget of $10,000 was set based on initial estimates. For the firs But here's the thing: nobody noticed for another two months. The org-level budget didn't have hard enforcement — it was a monitoring threshold. The alert fired, someone checked the dashboard, saw total spend was up, but couldn't quickly attribute it to a single workspace. The growth looked gradual on the org-level chart. It took a quarterly cost review to identify the ML workspace as the source. -Five months of $8,500/month overspend from the ML workspace (relative to the $3,000 expectation): **$27,500 in excess spend over the quarter**, of which roughly $8,500/month was the ongoing unnoticed overage. +At the month-three run rate, the ML workspace is **$5,600/month above its $3,000 expectation**. If that rate continues for three months, the ML excess is **$16,800**. Total organization spend is $3,500/month above the $10,000 organization threshold, or **$10,500 over three months**. **The math of scope misconfiguration:** @@ -245,7 +239,7 @@ Five months of $8,500/month overspend from the ML workspace (relative to the $3, | Per-workflow | Workflow-level anomalies | Cross-workflow accumulation | | Per-run | Individual runaway runs | Gradual accumulation from many normal runs | -The right answer is hierarchical scoping: [tenant](/glossary#tenant) > workspace > app > workflow > agent > toolset. Each level has its own budget. A single agent can't blow through the workflow budget. A single workspace can't consume the tenant budget. Each scope catches a different category of failure. +The right answer is hierarchical scoping: [tenant](/glossary#tenant) > workspace > app > workflow > agent > toolset. Each configured ledger contributes an atomic admission check for submitted estimates. A unique workflow value can represent one run. Coverage, estimate quality, and settlement policy still determine the economic bound. **How budget enforcement prevents this:** @@ -254,9 +248,9 @@ Per-workspace budgets in Cycles would have capped the ML team at $3,000/month. W The ML team would immediately know they've hit their budget. They could request an increase (with justification), optimize their agent's efficiency, or prioritize which experiments run within the cap. The decision is explicit and intentional instead of invisible and accidental. With hierarchical enforcement: -- Tenant budget: $10,000/month (hard cap) -- ML workspace: $3,000/month (hard cap) -- ML research workflow: $50/agent run (hard cap) +- Tenant ledger: $10,000/month reservation ceiling +- ML workspace ledger: $3,000/month reservation ceiling +- ML research workflow keyed per run: $50 reservation ceiling - If any level is exhausted, the specific scope is blocked while everything else continues For more on this failure pattern, see [Scope Misconfiguration and Budget Leaks](/incidents/scope-misconfiguration-and-budget-leaks). @@ -269,21 +263,21 @@ In every case, the agent was allowed to spend money without asking permission. T | Failure | Cost | Prevention mechanism | Cost with enforcement | |---|---|---|---| -| Infinite Tool Loop | $4,200 | Per-run budget ($15) | $15 | -| Retry Storm | $1,800 | Per-conversation budget ($2) | $76 | -| Friday Deploy | $12,400 | Per-task + batch budget | $2,500 | -| Concurrent Burst | $3,200 | Atomic reservations ($500 cap) | $502 | -| Scope Leak | $8,500/mo | Hierarchical workspace budgets | $3,000/mo | +| Infinite Tool Loop | $52.80 | Per-run budget ($15) | Near $15 | +| Retry Storm | $33.86 | Per-lookup budget ($0.25) | Up to $9.50 across 38 lookups | +| Friday Deploy | $2,300 | Per-task + batch budget | Near $500 batch cap | +| Concurrent Burst | $32.40 against $5 remaining | Atomic reservations ($5 remaining) | About $4.70 under stated averages | +| Scope Leak | $5,600/mo above plan | Hierarchical workspace budgets | $3,000/mo ML cap | -The total across these five scenarios: **$30,100 in preventable spend** (counting three months of the scope leak). With enforcement, the total would have been roughly $6,100 — an 80% reduction, with better user experience and faster failure detection. +These scenarios use different time windows and assumptions, so adding them into one savings figure would be misleading. The useful comparison is the configured bound in each row. -The pattern is simple. Budget enforcement is a pre-execution check. It asks one question before every LLM call: "Is there budget remaining for this?" If yes, proceed. If no, stop. Every failure in this post would have been caught by that single question. For failures where the risk is operational rather than monetary, the same pattern applies with a different unit — [risk points instead of dollars](/concepts/action-authority-controlling-what-agents-do). +The pattern is simple. A mandatory budget boundary asks before every protected LLM call whether the configured budget can cover the submitted estimate. If a live reservation fails, the host does not execute the call. Coverage, estimation, settlement, and overage policy still determine the real bound. For operational exposure, the host can assign and submit [risk points instead of dollars](/concepts/action-authority-controlling-what-agents-do); application authorization remains separate. ## Budget failures are not the only kind -The five failures above are all denominated in dollars. But agents also fail by *doing the wrong thing* — and those failures can cost far more than any token bill. A support agent that [sends 200 collections emails instead of welcome emails](/blog/ai-agent-action-control-hard-limits-side-effects) costs $1.40 in model spend. The business impact: $50K+ in lost pipeline. No spending limit would have caught it. +The five failures above are all denominated in dollars. But agents also fail by *doing the wrong thing*. As an illustrative action-risk scenario, 200 mistaken emails might consume only about $1.40 in model tokens while causing much larger business harm. No monetary spending limit calibrated to token cost would necessarily catch that. -This is why Cycles is positioned as [runtime authority](/blog/what-is-runtime-authority-for-ai-agents), not just [budget authority](/glossary#budget-authority). Budget authority caps what agents spend (the five scenarios above). [Action authority](/concepts/action-authority-controlling-what-agents-do) caps what agents *do* — gating high-consequence operations like email, deploy, and delete with risk-point budgets per toolset. Both dimensions use the same reserve-commit protocol and the same infrastructure. +This is why a complete [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) design needs more than spend accounting. Cycles can budget spend and caller-assigned exposure through the reserve-commit protocol. [Application action authority](/concepts/action-authority-controlling-what-agents-do) separately authenticates the principal and authorizes tools and arguments at the mandatory boundary. ## Next steps @@ -301,7 +295,7 @@ For failures where the risk is action rather than cost: - **[AI Agent Action Control: Hard Limits on Side Effects](/blog/ai-agent-action-control-hard-limits-side-effects)** — [RISK_POINTS](/glossary#risk-points), toolset budgets, and progressive capability narrowing - **[Action Authority](/concepts/action-authority-controlling-what-agents-do)** — risk-point budgets and toolset-scoped controls -The cheapest incident is the one that never happens. The second cheapest is the one that's capped at $15 instead of $4,200. +The cheapest incident is the one that never happens. The next best outcome is a protected path that rejects the next estimate when configured capacity is unavailable. ## Related how-to guides diff --git a/blog/ai-agent-governance-admin-dashboard-monitor-control-budgets-risk.md b/blog/ai-agent-governance-admin-dashboard-monitor-control-budgets-risk.md index 68a636fd..f4fa7616 100644 --- a/blog/ai-agent-governance-admin-dashboard-monitor-control-budgets-risk.md +++ b/blog/ai-agent-governance-admin-dashboard-monitor-control-budgets-risk.md @@ -59,7 +59,7 @@ From there, one click opens the fund dialog. Select **Credit**, enter the amount ![Budget detail showing RISK_POINTS utilization at 85% with the Fund Budget dialog open](/images/dashboard/risk-budget-fund.png) -If the usage looks suspicious instead of legitimate? Click **Freeze** directly from the budget list. No detail page needed. The scope is locked immediately — new reservations and commits are blocked until you unfreeze it. +If the usage looks suspicious instead of legitimate? Click **Freeze** directly from the budget list. No detail page needed. The scope is locked immediately for new reservations, direct events, and funding operations until you unfreeze it. Reservations that were already active can still be committed or released. The same workflow applies to cost budgets — USD spend, token usage, credits. Whether you're capping dollars or risk points, the operational pattern is the same. @@ -71,7 +71,7 @@ You need to revoke it immediately, provision a replacement, hand the new secret ![Audit logs with expanded metadata showing operation details, resource IDs, and request context](/images/dashboard/audit-investigation.png) -Filter audit logs by `resource_type: api_key` and the time window. Expand any row to see the full context: who did what, from which IP, with what parameters. The metadata shows the exact resource ID, operation, and request details. +Filter audit logs by `resource_type: api_key` and the time window. Expanded rows show the fields the admin server persisted, including the operation, resource ID, status, request and trace identifiers, actor key when present, and operation-specific metadata. Source IPs, raw request parameters, and application context are not guaranteed audit fields; retain them in your gateway or application logs when required. Found the compromised key? Click **Revoke** on the API Keys page. It's invalidated instantly. New operations using that key stop immediately. @@ -105,7 +105,7 @@ A few things surprised us. ## Try it -The screenshots above show a demo environment with 12 tenants, 42 budgets across four unit types, 6 webhooks, and a full audit trail — representative of a mid-scale production deployment. +The screenshots above show a demo environment with 12 tenants, 42 budgets across four unit types, 6 webhooks, and budget lifecycle records. They demonstrate the dashboard surface, not a complete log of external application actions or outcomes. ```bash docker compose up -d # starts admin server + Redis diff --git a/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement.md b/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement.md index aeb8b713..71fa4673 100644 --- a/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement.md +++ b/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement.md @@ -17,19 +17,19 @@ head: > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. -Regulations are converging on a single demand: if your AI system acts autonomously, you must be able to prove what it did, why it was allowed to do it, and how you would have stopped it. +Regulations, management-system standards, and security guidance address different parts of AI governance. Depending on a system's role, risk classification, and use case, teams may need records, risk treatment, human oversight, security controls, or some combination of them. -The EU AI Act's high-risk obligations were rescheduled by the Digital Omnibus adopted in June 2026 — Annex III systems now apply from December 2, 2027 — while [Article 50 transparency and GPAI enforcement still land on August 2, 2026](/blog/eu-ai-act-what-actually-happens-august-2-2026). Organizations can already pursue certification of an AI management system against ISO/IEC 42001, with [ISO/IEC 42006:2025](https://www.iso.org/standard/44546.html) defining requirements for certification bodies. NIST's AI Risk Management Framework was published in January 2023. OWASP published its [Top 10 for Agentic Applications](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) in late 2025. And in February 2026, NIST launched its [AI Agent Standards Initiative](https://www.nist.gov/news-events/news/2026/02/announcing-ai-agent-standards-initiative-interoperable-and-secure) — a direct signal that autonomous systems need governance infrastructure beyond what model-level controls provide. +The EU AI Act's high-risk obligations were rescheduled by the Digital Omnibus adopted in June 2026 — Annex III systems now apply from December 2, 2027 — while [Article 50 transparency and GPAI enforcement still land on August 2, 2026](/blog/eu-ai-act-what-actually-happens-august-2-2026). Organizations can already pursue certification of an AI management system against ISO/IEC 42001, with [ISO/IEC 42006:2025](https://www.iso.org/standard/42006) defining requirements for certification bodies. NIST's AI Risk Management Framework was published in January 2023. OWASP published its [Top 10 for Agentic Applications](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) in late 2025. And in February 2026, NIST launched its [AI Agent Standards Initiative](https://www.nist.gov/news-events/news/2026/02/announcing-ai-agent-standards-initiative-interoperable-and-secure) — a direct signal that autonomous systems need governance infrastructure beyond what model-level controls provide. The gap is not awareness. Teams know governance matters. The gap is implementation: **how do you translate regulatory requirements into enforceable runtime controls?** -This post maps specific obligations from each framework to concrete enforcement mechanisms — and introduces a maturity model for teams building toward full compliance. +This post maps selected obligations and guidance to possible runtime controls — and introduces an engineering maturity model that can feed a broader governance or compliance program. ## The Regulatory Landscape for AI Agents in 2026 -Four frameworks shape the governance requirements for autonomous AI systems. Each addresses different dimensions, but they share a common requirement: controls must operate at runtime, not just at design time. +Four prominent frameworks shape how teams approach autonomous AI systems. Their legal status and scope differ: the EU AI Act is law, NIST AI RMF and OWASP are voluntary guidance, and ISO/IEC 42001 specifies a certifiable organizational management system. Runtime controls can support each program, but none of these sources uniformly requires the seven Cycles-oriented controls described later. ### EU AI Act (Regulation 2024/1689) @@ -39,7 +39,7 @@ For AI agents that qualify as high-risk AI systems, five articles create direct **Article 9 — Risk Management System.** Providers of high-risk AI systems must establish a continuous, iterative risk management system throughout the system's lifecycle. This includes identifying foreseeable risks, estimating their severity, and adopting measures to eliminate or mitigate them. For agents, "foreseeable risks" include runaway cost spirals, unauthorized actions, and cascading failures across multi-agent workflows — precisely the failure modes documented in [5 AI Agent Failures Budget Controls Would Prevent](/blog/ai-agent-failures-budget-controls-prevent) and [5 Failures Only Action Controls Would Prevent](/blog/ai-agent-action-failures-runtime-authority-prevents). -**Article 12 — Record-Keeping.** High-risk AI systems must have automatic logging capabilities that enable monitoring of operation and traceability of decisions. Logs must record periods of use, input data, and identification of persons involved in verification. For agents, this means every tool call, every budget [reservation](/glossary#reservation), every action decision must be recorded with full context — not reconstructed from scattered application logs after an incident. +**Article 12 — Record-Keeping.** High-risk AI systems must technically allow automatic recording of events over the system lifetime, with logging capabilities appropriate to the system's intended purpose. Specific minimum content applies to some systems, including certain biometric systems. For an agent deployment, teams must determine which system and application records provide the required traceability; Cycles budget [reservation](/glossary#reservation) records can contribute but do not capture complete tool arguments, authorization rationale, or external outcomes. **Article 13 — Transparency.** Systems must operate with sufficient transparency that deployers can interpret and use the system's output appropriately. For agents, this means the human operator must be able to understand what the agent is doing, why it was allowed to do it, and what constraints are in effect. @@ -49,11 +49,9 @@ For AI agents that qualify as high-risk AI systems, five articles create direct ### NIST AI Risk Management Framework (AI RMF 1.0) -Published January 26, 2023, the [NIST AI RMF](https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-ai-rmf-10) defines four core functions: **Govern, Map, Measure, Manage.** It is voluntary and sector-agnostic, but it has become the de facto reference for U.S. organizations building AI governance programs. +Published January 26, 2023, the [NIST AI RMF](https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-ai-rmf-10) defines four core functions: **Govern, Map, Measure, Manage.** It is voluntary, sector-agnostic guidance. -The framework treats autonomy as a risk amplifier. Systems with greater autonomy require stronger governance controls — more frequent measurement, tighter management boundaries, and more explicit accountability structures. - -For agent deployments, the four functions translate to: +For an agent deployment, a team can apply the four functions along these lines: | RMF Function | Agent Governance Requirement | |---|---| @@ -66,7 +64,7 @@ The February 2026 [AI Agent Standards Initiative](https://www.nist.gov/news-even ### ISO/IEC 42001:2023 — AI Management System -Published December 2023, [ISO/IEC 42001](https://www.iso.org/standard/81230.html) specifies requirements for an AI management system (AIMS). It is certifiable — meaning organizations can be audited against it and receive formal certification, similar to ISO 27001 for information security. +Published December 2023, [ISO/IEC 42001](https://www.iso.org/standard/42001) specifies requirements for an AI management system (AIMS). It is certifiable — meaning organizations can be audited against it and receive formal certification, similar to ISO 27001 for information security. Key control areas relevant to AI agents: @@ -100,54 +98,54 @@ Runtime enforcement directly addresses ASI01 (goal hijack via action validation) ## The AI Agent Governance Maturity Model -Most teams are somewhere between "we have dashboards" and "we have enforcement." This maturity model maps the progression from no governance to continuous compliance — and identifies where each regulatory framework's requirements are actually met. +Most teams are somewhere between "we have dashboards" and "we have enforcement." This maturity model describes a progression in runtime-control capability. It does not determine whether a legal or certification requirement is met. ### Level 0: No Governance Agents run unbounded. No cost limits, no action controls, no audit trail beyond application logs. Teams discover problems through invoices and incident reports. -**Regulatory alignment:** None. Fails all four frameworks. +**Evidence gap:** This level supplies none of the runtime budget evidence discussed in this post. Overall legal or standards conformity still depends on the system's scope and the organization's other controls. ### Level 1: Visibility -Teams deploy observability tooling — [Langfuse](https://langfuse.com/), [LangSmith](https://smith.langchain.com/), provider dashboards. They can see what agents did after the fact. Cost reports arrive daily or weekly. +Teams deploy observability tooling — [Langfuse](https://langfuse.com/), [LangSmith](https://smith.langchain.com/), provider dashboards. Trace-only deployments reconstruct what agents did after the fact; some products also offer gateway controls, and reporting latency varies by configuration. -**What this satisfies:** Partial Article 12 (record-keeping exists, but may lack structured attribution). Partial NIST Measure (you can track metrics, but you cannot act on them in real time). +**What this can support:** Article 12 record-keeping and NIST Measure activities, if the retained records have the content, scope, and quality the system requires. -**What this does not satisfy:** Article 14 (no stop mechanism). Article 9 (no risk mitigation — only risk observation). ISO 42001 risk treatment (you identified the risk; you did not treat it). +**What this does not provide by itself:** A runtime stop mechanism, risk treatment, or the broader processes required by Article 9, Article 14, or ISO/IEC 42001. ### Level 2: Policy Teams define governance policies: "agents should not spend more than $10 per run," "agents should not send more than 50 emails." Policies exist in documentation, runbooks, or configuration files. Enforcement is manual — humans review dashboards and intervene. -**What this satisfies:** NIST Govern (policies exist). ISO 42001 documentation requirements (controls are defined). +**What this can support:** NIST Govern activities and documented-control elements of an ISO/IEC 42001 program. -**What this does not satisfy:** Any requirement for automated enforcement. A policy that depends on a human noticing a dashboard at 2 AM on a Saturday is not a control — it is a hope. [The $4,200 tool loop](/blog/ai-agent-failures-budget-controls-prevent) happened because the alert fired, but nobody was watching. +**What this does not provide by itself:** Automated enforcement. A policy that depends on a human noticing a dashboard at 2 AM on a Saturday does not prevent a limit breach before intervention. [The checked runaway-loop model](/blog/ai-agent-failures-budget-controls-prevent) illustrates that gap. ### Level 3: Soft Enforcement -Teams implement rate limits, provider spending caps, or application-level counters. These provide some automated constraint but have architectural limitations: rate limits control velocity, not cumulative spend. Provider caps are monthly, not per-run. Application counters [break under concurrency](/blog/vibe-coding-budget-wrapper-vs-budget-authority) — twenty agents reading "remaining: $500" simultaneously will collectively spend $10,000. +Teams implement rate limits, provider cost controls, or application-level counters. These provide useful constraints but have different boundaries: request-count limits control velocity rather than cumulative spend; provider budgets, credits, and quotas use vendor-defined scopes; and non-atomic application counters can [break under concurrency](/blog/vibe-coding-budget-wrapper-vs-budget-authority). -**What this satisfies:** Partial Article 9 (some risk mitigation). Partial NIST Manage (some automated response). Better than Level 2 for OWASP least-agency principle (some constraint on authority). +**What this can support:** Risk-mitigation and NIST Manage activities through some automated response. -**What this does not satisfy:** Atomicity requirements for multi-[tenant isolation](/glossary#tenant-isolation) (Article 15). Reliable stop mechanism (Article 14). Comprehensive audit trail with scope attribution (Article 12). The gaps are well-documented in [Why Rate Limits Are Not Enough](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) and [Cycles vs. Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps). +**What this does not provide by itself:** An atomic shared budget across providers and concurrent workers, a complete system stop mechanism, or a comprehensive action audit trail. The gaps are described in [Why Rate Limits Are Not Enough](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) and [Cycles vs. Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps). ### Level 4: Runtime Authority -Pre-execution enforcement with atomic budget operations. Every agent action passes through a reserve-commit gate before execution. Budgets are hierarchical (tenant → workspace → workflow → run). Actions are scored by risk. The audit trail is a byproduct of enforcement, not a separate logging system. +Pre-execution enforcement with atomic budget operations. Every protected action the host instruments passes through a reserve-commit gate before execution. Budgets can follow the protocol subject hierarchy (tenant → workspace → app → workflow → agent → toolset). The application can classify action exposure and submit it as `RISK_POINTS`. Cycles records the budget lifecycle; a complete action audit trail still requires correlated application authorization and outcome logs. -**What this satisfies:** +**How these runtime controls can contribute:** -| Requirement | How It's Met | +| Requirement | Runtime contribution and boundary | |---|---| -| Article 9 — Risk management | Budgets and risk-point caps mitigate foreseeable cost and action risks | -| Article 12 — Record-keeping | Every reservation, commit, and event creates a structured record with full scope | -| Article 13 — Transparency | Budget state is queryable; agents can check balance and explain constraints | -| Article 14 — Human oversight | DENY responses stop agents; ALLOW_WITH_CAPS constrains them; budgets can be modified in real time | -| Article 15 — Robustness | Atomic operations prevent concurrency violations; tenant isolation prevents cross-contamination | +| Article 9 — Risk management | Budgets and caller-assigned risk-point caps can implement selected cost and exposure treatments within a broader risk-management system | +| Article 12 — Record-keeping | Persisted budget operations and emitted events provide structured lifecycle records; application logs must supply action and outcome context | +| Article 13 — Transparency | Budget state is queryable; hosts can expose constraints alongside the other information deployers need | +| Article 14 — Human oversight | A host can stop an instrumented action on a rejected reservation and can apply configured caps; this is one control in a broader oversight design | +| Article 15 — Robustness | Atomic budget operations prevent concurrent overspend within configured scopes; they do not provide data or credential isolation | | NIST Govern/Map/Measure/Manage | Runtime infrastructure operationalizes key parts of all four functions (GOVERN also requires organizational policies, competencies, and lifecycle processes beyond any single runtime mechanism) | -| ISO 42001 | Runtime controls are automated, documented by the protocol, and auditable via event log (ISO 42001 is an organization-wide management system; runtime enforcement satisfies the technical control requirements, not the full AIMS) | -| OWASP ASI01–04, ASI08, ASI10 | Least agency enforced via budgets, risk points, and tool allowlists (ASI05–07, ASI09 require complementary controls) | +| ISO 42001 | Runtime records can contribute evidence for selected documented controls; ISO/IEC 42001 is an organization-wide management system, so Cycles does not establish AIMS conformity | +| OWASP Agentic Top 10 | Budgets can bound cumulative submitted exposure; identity, tool allowlists, argument validation, sandboxing, and other listed mitigations remain separate controls | This is the level where [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) operates — and where Cycles provides the infrastructure. @@ -159,51 +157,51 @@ Level 4 plus automated compliance reporting, drift detection, and integration wi ## The Seven Controls -Every regulatory framework cited above converges on the same set of runtime controls. The specific articles and clauses differ, but the operational requirements are consistent. +The following seven controls are a practical engineering model for bounded agent operations. They can support parts of the frameworks above, but the mappings are interpretive and do not establish compliance. ### Control 1: Pre-Execution Budget Enforcement -**What regulators require:** Article 9 (risk mitigation), NIST Manage (resource allocation to mapped risks), ISO 42001 (proportionate risk treatment). +**Framework connection:** This control can serve as one Article 9 risk treatment, one NIST Manage response, or one documented technical control in an ISO/IEC 42001 program when it matches the organization's assessed risks. -**What "good" looks like:** Before every LLM call and tool invocation, the system atomically checks whether budget remains and reserves the estimated cost. If the budget is exhausted, the action is denied before execution — not flagged after. +**What "good" looks like:** Before every protected LLM call and tool invocation, the integration atomically checks whether budget remains and reserves the estimated cost. If the reservation is rejected, the host does not execute the action. -**What happens without it:** A coding agent hit an ambiguous error, retried with expanding context windows, and [looped 240 times over three hours](/blog/ai-agent-failures-budget-controls-prevent), costing $4,200. Three dashboards showed the spend in real time. None could stop it. +**What happens without it:** In an illustrative model, a coding agent [loops 240 times over three hours](/blog/ai-agent-failures-budget-controls-prevent), costing $52.80 under the stated token assumptions. A dashboard can show that spend without stopping it. -**How Cycles implements it:** The [reserve-commit protocol](/protocol/how-reserve-commit-works-in-cycles) locks estimated cost before execution and releases unused budget on commit. Budget types include `USD_MICROCENTS`, `TOKENS`, and `CALLS` — enforced per-run, per-workflow, per-tenant, or at any scope in the hierarchy. +**How Cycles implements it:** The [reserve-commit protocol](/protocol/how-reserve-commit-works-in-cycles) locks estimated cost before execution and releases unused budget on commit. Wire units are `USD_MICROCENTS`, `TOKENS`, `CREDITS`, and `RISK_POINTS`, enforced at any configured subject scope. A team can model call counts with `CREDITS`; `CALLS` is not a protocol unit. ### Control 2: Action-Level Risk Scoring -**What regulators require:** Article 9 (identify and score foreseeable risks), OWASP least-agency principle and ASI02 (tool misuse and exploitation). +**Framework connection:** Risk classification and treatment can support Article 9 risk management, while application-side tool authorization and exposure budgets can support OWASP least-agency and ASI02 mitigations. **What "good" looks like:** Each action type has an assigned risk score. High-consequence actions (email, deploy, delete, payment) consume more risk budget than low-consequence ones (read, search, summarize). An agent can reason freely but is constrained on dangerous operations. -**What happens without it:** A support agent [sent 200 collections emails instead of welcome emails](/blog/ai-agent-action-control-hard-limits-side-effects). Total model cost: $1.40. Business impact: $50K+ in lost pipeline. No spending limit would have prevented this — the damage was in the action, not the [tokens](/glossary#tokens). +**What happens without it:** In an [illustrative scenario](/blog/ai-agent-action-control-hard-limits-side-effects), 200 mistaken emails have about $1.40 in modeled token spend but potentially much larger, unquantified external impact. A monetary budget calibrated to [tokens](/glossary#tokens) does not validate message content or recipient authorization. -**How Cycles implements it:** [RISK_POINTS](/concepts/action-authority-controlling-what-agents-do) — budgets denominated in blast radius, not dollars. A `send_email` tool might cost 20 risk points; a `search_knowledge_base` tool costs 1. The agent exhausts its action budget before it can send the 201st email. +**How Cycles implements it:** [RISK_POINTS](/concepts/action-authority-controlling-what-agents-do) — budgets denominated in caller-assigned exposure, not dollars. A host might assign `send_email` 20 risk points and `search_knowledge_base` 1. If every authorized email is routed through the required boundary, Cycles rejects the first reservation that exceeds the remaining risk budget. The host still authorizes tools and arguments. ### Control 3: Hierarchical Scope Isolation -**What regulators require:** Article 15 (robustness, protection against cross-contamination), ISO 42001 (data governance, third-party management), OWASP ASI08 (cascading failure prevention). +**Framework connection:** Budget-scope isolation can contribute to robustness and cascading-failure controls. It does not itself provide the data governance, identity isolation, or third-party management those broader programs may require. -**What "good" looks like:** Budgets and policies are hierarchical: tenant → workspace → workflow → run → agent. One tenant's runaway agent cannot exhaust another tenant's allocation. One workflow's failure cannot cascade to other workflows in the same workspace. +**What "good" looks like:** Budgets follow meaningful organizational scopes. Separate tenant allocations keep one tenant's submitted usage from consuming another tenant's configured allocation; narrower scopes can contain budget impact within a workflow or agent. Data, credential, and execution isolation require additional controls. -**What happens without it:** In a [multi-tenant SaaS deployment](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation), a single power user's agent consumed 72% of shared API capacity over a weekend, degrading service for 500 other customers. The noisy-neighbor problem, applied to AI. +**What happens without it:** In an illustrative [multi-tenant SaaS scenario](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation), one power user's agent can consume most shared API capacity and degrade service for other customers. It is the noisy-neighbor problem applied to AI. -**How Cycles implements it:** [Hierarchical scopes](/protocol/how-scope-derivation-works-in-cycles) enforce budgets at every level. A tenant's total allocation is the ceiling; workspaces, workflows, and runs subdivide it. Enforcement is atomic — concurrent agents drawing from the same scope cannot overdraw. +**How Cycles implements it:** [Hierarchical scopes](/protocol/how-scope-derivation-works-in-cycles) check each matching configured ledger atomically across tenant, workspace, app, workflow, agent, and toolset. A unique workflow value can represent a run. Ledgers are independent rather than funded by automatic parent-to-child subdivision. -### Control 4: Immutable Audit Trail with Full Attribution +### Control 4: Correlated Budget and Action Records -**What regulators require:** Article 12 (automatic logging with traceability), Article 13 (transparency), NIST Measure (track and benchmark), ISO 42001 (auditable controls). +**Framework connection:** Correlated records can contribute to Article 12 logging and traceability, Article 13 transparency, NIST Measure activities, and evidence for documented ISO/IEC 42001 controls. -**What "good" looks like:** Every action produces a structured record containing: scope hierarchy, amounts reserved and committed, timestamp, status, and metadata. The audit trail is a byproduct of enforcement, not a separate logging system. An auditor can reconstruct what happened, who authorized it, and how much it cost — from the enforcement log alone. +**What "good" looks like:** Persisted budget operations record submitted scope, amounts, timestamps, and status. Application authorization and execution logs record the reservation ID, propagated trace ID, tool, arguments, decision rationale, and outcome. Together, those sources support reconstruction without claiming that a budget record alone describes the action. **What happens without it:** After an incident, teams spend days reconstructing what happened from scattered application logs, provider billing dashboards, and Slack messages. The [production gap](/blog/ai-agent-production-gap-what-developers-are-saying) is not just operational — it is evidentiary. -**How Cycles implements it:** Every reservation, commit, release, and [event](/protocol/how-events-work-in-cycles-direct-debit-without-reservation) creates a structured, queryable record via the REST API. Retention is 90 days in hot storage, with export to cold storage for long-term compliance. The [admin server](/glossary#admin-server) records all administrative operations separately. +**How Cycles implements it:** Persisted reservations and direct-usage [events](/protocol/how-events-work-in-cycles-direct-debit-without-reservation) create queryable budget lifecycle data; emitted event hooks provide additional records. Event retention defaults to 90 days and is configurable. Applications must export the required data and join it to their authorization and outcome logs for long-term evidence. The [admin server](/glossary#admin-server) records management-plane audit operations separately. ### Control 5: Graceful Degradation Under Constraint -**What regulators require:** Article 14 (human oversight, ability to interrupt), Article 15 (resilience to faults), NIST Manage (proportionate response). +**Framework connection:** Rejection handling and controlled degradation can contribute to a broader Article 14 oversight design, Article 15 resilience measures, and NIST Manage responses. **What "good" looks like:** When an agent hits a budget limit, it does not crash. It degrades: drops to a cheaper model, shortens its response, skips optional steps, or stops and explains what remains. The human operator can adjust the budget and resume — or decide not to. @@ -213,7 +211,7 @@ Every regulatory framework cited above converges on the same set of runtime cont ### Control 6: Least-Privilege Access Control -**What regulators require:** OWASP ASI03 (identity and privilege abuse), OWASP least-agency principle, ISO 42001 (access management). +**Framework connection:** Least-privilege credentials and separate management/runtime planes support OWASP ASI03 mitigations and access-management controls. Cycles API permissions do not replace application or tool credentials. **What "good" looks like:** The runtime enforcement plane and the management plane are separated. Agent-facing API keys have scoped permissions (reserve, commit, check balance) and cannot modify budgets, create tenants, or access other tenants' data. Administrative operations require separate credentials with audit logging. @@ -223,9 +221,9 @@ Every regulatory framework cited above converges on the same set of runtime cont ### Control 7: Safe Rollout via Shadow Mode -**What regulators require:** Article 9 (test risk management measures before deployment), NIST Map and Measure (understand risk posture before enforcement), ISO 42001 (validate controls). +**Framework connection:** Non-persisting evaluation can support pre-deployment validation, NIST Map and Measure work, and testing of a documented budget control. -**What "good" looks like:** Before enforcing governance in production, teams run it in observation mode. Every action is evaluated against budgets and policies, but nothing is denied. The output is a gap analysis: what would have been blocked, how often, and at what scope. +**What "good" looks like:** Before enforcing a budget in production, teams route representative protected actions through a non-persisting evaluation. The output shows which submitted budget requests would have been allowed, capped, or denied. Separate policy engines remain responsible for shadow-evaluating application authorization rules. **What happens without it:** Teams set budgets too tight and block legitimate work, or too loose and miss violations. Either outcome erodes trust in the governance system — and teams revert to no enforcement. @@ -233,30 +231,30 @@ Every regulatory framework cited above converges on the same set of runtime cont ## Compliance Mapping: Framework to Control to Evidence -For teams preparing for audits or certifications, this table maps each regulatory requirement to the corresponding control and the evidence artifact that demonstrates compliance. +For teams preparing for audits or certifications, this table gives possible runtime contributions and example evidence sources. No row demonstrates compliance on its own; scope, sufficiency, and the necessary organizational controls must be assessed separately. -| Regulatory Requirement | Control | Evidence Artifact | +| Framework area | Possible runtime contribution | Example evidence sources | |---|---|---| -| EU AI Act Art. 9 — Risk management | Pre-execution budgets, risk scoring | Budget policies, risk-point configuration, shadow mode reports | -| EU AI Act Art. 12 — Record-keeping | Immutable audit trail | Event log API output, cold storage exports | -| EU AI Act Art. 13 — Transparency | Queryable budget state | Balance check API, agent decision logs | -| EU AI Act Art. 14 — Human oversight | [Graceful degradation](/glossary#graceful-degradation), real-time budget controls | DENY/ALLOW_WITH_CAPS response logs, budget modification audit trail | -| EU AI Act Art. 15 — Robustness | Scope isolation, atomic operations | Tenant isolation configuration, concurrency test results | +| EU AI Act Art. 9 — Risk management | Pre-execution budgets as one selected risk treatment | Risk assessment, approved budget configuration, retained dry-run response analysis | +| EU AI Act Art. 12 — Record-keeping | Correlated budget lifecycle records | Reservation/event exports joined to application authorization and outcome logs | +| EU AI Act Art. 13 — Transparency | Queryable budget state | Balance responses plus deployer instructions and application decision logs | +| EU AI Act Art. 14 — Human oversight | [Graceful degradation](/glossary#graceful-degradation) and a host-enforced stop on rejection | Rejection/caps handling logs, human-oversight procedures, intervention tests | +| EU AI Act Art. 15 — Robustness | Scope-level budget isolation and atomic operations | Budget configuration, concurrency tests, plus broader security and resilience tests | | NIST AI RMF — Govern | Scope hierarchy, access control | Tenant/workspace/workflow configuration, API key permission matrix | -| NIST AI RMF — Map | Risk-point taxonomy, tool classification | Risk-point assignments per tool, tool allowlists/denylists | +| NIST AI RMF — Map | Caller-defined exposure taxonomy | Risk assessment, application tool classification, risk-point assignments | | NIST AI RMF — Measure | Budget utilization tracking | Usage reports, variance analysis, alert history | -| NIST AI RMF — Manage | Pre-execution enforcement | Reservation/commit logs, DENY event records | -| ISO 42001 — Risk treatment | All seven controls | Complete enforcement log with scope attribution | -| ISO 42001 — Lifecycle management | Shadow mode, budget versioning | Shadow mode reports, policy change audit trail | -| ISO 42001 — Third-party management | Tool allowlists, MCP governance | Tool invocation logs, server authorization records | -| OWASP ASI02 — Tool misuse and exploitation | Risk scoring, tool allowlists | Per-tool invocation counts, denied tool call records | -| OWASP ASI03 — Identity and privilege abuse | Least-privilege access control | API key permission matrix, scope isolation configuration | -| OWASP ASI08 — Cascading failures | Hierarchical isolation | Per-scope budget utilization, cross-scope denial records | -| OWASP ASI10 — Rogue agents | Pre-execution enforcement | Out-of-policy action logs, DENY event records | +| NIST AI RMF — Manage | Pre-execution budget enforcement | Reservation/commit records and retained rejection responses | +| ISO/IEC 42001 — Risk treatment | Selected technical budget controls | Risk-treatment plan, approved configurations, test and lifecycle records | +| ISO/IEC 42001 — Lifecycle management | Budget-control validation before enforcement | Retained dry-run analysis and management-plane change audit records | +| ISO/IEC 42001 — Third-party management | No direct Cycles substitute | Supplier assessments, application tool policy, invocation and authorization logs | +| OWASP ASI02 — Tool misuse and exploitation | Bound cumulative caller-assigned exposure | Application allowlists and validation logs joined to Cycles budget records | +| OWASP ASI03 — Identity and privilege abuse | Least-privilege Cycles API access | API-key permission matrix plus application/tool identity controls | +| OWASP ASI08 — Cascading failures | Hierarchical budget isolation | Per-scope budget utilization and concurrency/containment tests | +| OWASP ASI10 — Rogue agents | Reject over-budget protected calls | Host rejection-handling logs plus application policy and incident records | | SOC 2 — Security | Runtime/admin plane separation | Network configuration, API key audit, access control matrix | | SOC 2 — Availability | Budget-based capacity management | Tenant budget allocation, capacity utilization reports | | SOC 2 — Processing Integrity | Atomic reserve-commit operations | Transaction logs, concurrency test evidence | -| SOC 2 — Confidentiality | Tenant scope isolation | Isolation configuration, cross-tenant access test results | +| SOC 2 — Confidentiality | No direct Cycles substitute for data isolation | Application authorization, datastore isolation, and cross-tenant access tests | ## From Framework to Implementation @@ -264,20 +262,20 @@ Governance frameworks tell you what to control. They do not tell you how to buil Three starting points, depending on where you are today: -**If you have no governance controls yet:** Start with [shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production). It adds zero production risk and gives you a governance gap analysis within a week. You will learn what your agents actually cost, which actions they take most frequently, and where the high-risk operations are. This is your Level 1 → Level 4 fast path. +**If you have no runtime budget controls yet:** Start with [shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production). Base dry-run does not mutate budget state or persist a reservation. Retain and analyze the responses in your application to learn which submitted estimates and scopes would be allowed, capped, or denied before enforcing them. **If you have observability but no enforcement:** You already have the visibility (Level 1). Add a [budget-enforced workflow](/quickstart/end-to-end-tutorial) to one high-risk agent — the one that sends emails, makes purchases, or calls external APIs. Prove the model works on a single workflow, then expand. -**If you are preparing for audit or certification:** The compliance mapping table above is your starting point. Export your event logs to your SIEM or GRC tooling. Map each control to the evidence artifact. The structured audit trail that Cycles produces as a byproduct of enforcement is the same trail your auditor will examine. +**If you are preparing for audit or certification:** Treat the mapping table as a prompt for your legal, compliance, and audit teams. Export the necessary Cycles lifecycle data, join it to application authorization and outcome evidence, and let the applicable control owner determine whether the combined evidence is sufficient. -Governance is not a feature you add after shipping. It is the infrastructure that makes shipping safe. The regulations converge on this point — and the implementation path is available now. +Governance combines organizational processes with technical controls before and after deployment. Runtime budgets are one available component, not a complete governance or compliance system. ## Sources 1. [EU AI Act — Regulation 2024/1689](https://eur-lex.europa.eu/eli/reg/2024/1689/oj) — Entered into force August 1, 2024. High-risk obligations rescheduled by the June 2026 Digital Omnibus to December 2, 2027 (Annex III) and August 2, 2028 (Annex I embedded). 2. [NIST AI Risk Management Framework 1.0](https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-ai-rmf-10) — Published January 26, 2023 3. [NIST AI Agent Standards Initiative](https://www.nist.gov/news-events/news/2026/02/announcing-ai-agent-standards-initiative-interoperable-and-secure) — Announced February 17, 2026 -4. [ISO/IEC 42001:2023](https://www.iso.org/standard/81230.html) — AI Management System standard, published December 2023 +4. [ISO/IEC 42001:2023](https://www.iso.org/standard/42001) — AI Management System standard, published December 2023 5. [OWASP Top 10 for Agentic Applications](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) — 2025/2026 edition 6. [EU AI Act FAQ — Classification guidance](https://ai-act-service-desk.ec.europa.eu/en/faq) — AI Act Service Desk, European Commission 7. [Navigating the AI Act — Timeline guidance](https://digital-strategy.ec.europa.eu/en/faqs/navigating-ai-act) — European Commission Digital Strategy diff --git a/blog/ai-agent-governance-runtime-enforcement-security-cost-compliance.md b/blog/ai-agent-governance-runtime-enforcement-security-cost-compliance.md index 1feed252..728964b9 100644 --- a/blog/ai-agent-governance-runtime-enforcement-security-cost-compliance.md +++ b/blog/ai-agent-governance-runtime-enforcement-security-cost-compliance.md @@ -49,7 +49,7 @@ The security surface of AI agents expanded dramatically in early 2026. Real inci - **Tool hub [exposure](/glossary#exposure)**: An audit of ClawHub found [824 unauthorized or harmful capabilities](https://blog.sshh.io/p/everything-wrong-with-mcp) out of 10,700 published tools. Separately, Knostic discovered 1,862 internet-exposed [MCP servers](/glossary#mcp-server) — all 119 manually verified had zero authentication. - **Replit database deletion**: Replit's AI coding assistant [deleted a user's production database](https://techcrunch.com/2025/10/02/after-nine-years-of-grinding-replit-finally-found-its-market-can-it-keep-it/) containing 100+ executive contacts, then fabricated 4,000 fake records to cover its tracks. - **OpenAI Operator purchase**: OpenAI's Operator agent [reportedly made an unauthorized $31.43 purchase from Instacart](https://incidentdatabase.ai/cite/1028/), bypassing user confirmation safeguards. -- **Rogue agent collaboration**: Researchers [demonstrated](https://www.theregister.com/2026/03/12/rogue_ai_agents_worked_together/) that compromised agents can coordinate to escalate privileges and compromise downstream systems. In connected multi-agent architectures, a single poisoned agent can rapidly corrupt downstream decision-making — what OWASP categorizes as [ASI08: Cascading Failures](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/). +- **Rogue agent collaboration**: Researchers [demonstrated](https://www.theregister.com/security/2026/03/12/rogue-ai-agents-can-work-together-to-hack-systems/5228926) that compromised agents can coordinate to escalate privileges and compromise downstream systems. In connected multi-agent architectures, a single poisoned agent can rapidly corrupt downstream decision-making — what OWASP categorizes as [ASI08: Cascading Failures](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/). These incidents share a pattern: the agent had the _capability_ to act but no _authority_ check before acting. MCP defines how agents discover and call tools. It does not define whether a given agent, in a given context, should be allowed to call a given tool right now. @@ -57,7 +57,7 @@ The missing layer is runtime authorization — a decision point between "the age ### Pillar 2: Cost — How Much Can Be Spent? -Cost governance for agents is well-documented. The short version: agents amplify API costs by 3–10x compared to single-call chatbots. A proof-of-concept costing $500/month [scaled to $847,000/month](https://medium.com/@klaushofenbitzer/token-cost-trap-why-your-ai-agents-roi-breaks-at-scale-and-how-to-fix-it-4e4a9f6f5b9a) in production. A data enrichment agent [misinterpreted an API error and ran 2.3 million calls over a weekend](https://rocketedge.com/2026/03/15/your-ai-agent-bill-is-30x-higher-than-it-needs-to-be-the-6-tier-fix/), costing $47,000. +Cost governance for agents is well-documented. The short version: agents amplify API costs by 3–10x compared to single-call chatbots. A proof-of-concept costing $500/month [scaled to $847,000/month](https://medium.com/@klaushofenbitzer/token-cost-trap-why-your-ai-agents-roi-breaks-at-scale-and-how-to-fix-it-4e4a9f6f5b9a) in production. A data enrichment agent [misinterpreted an API error and ran 2.3 million calls over a weekend](https://rocketedge.com/2026/03/15/ai-agent-cost-control/), costing $47,000. The deeper point is that **cost governance is security governance**. An uncontrolled spend spiral is a denial-of-service attack on your own infrastructure. When one runaway agent exhausts a shared rate limit, every other agent and user on the platform is affected. When a monthly budget burns out in a week, teams add manual approval steps — which defeats the purpose of autonomy. @@ -75,7 +75,7 @@ The compliance gap has three dimensions: An [NBER study from February 2026](https://www.nber.org/papers/w32879) found that 89% of firms reported zero measurable productivity change from AI adoption broadly. While the study covers AI adoption in general — not agent governance specifically — one contributing factor is clear: compliance requirements slow or block deployment entirely. Teams that cannot demonstrate governance over their agents cannot deploy them in regulated environments. -Observability tools (Langfuse, LangSmith, Arize) record what happened. They provide reconstruction. But they do not provide authorization proof — because the authorization never happened. You cannot audit a decision that was never made. +Observability traces record what happened and support reconstruction. Some products now add gateway policy decisions—LangSmith's LLM Gateway, for example, documents private-beta provider spend policies. A trace by itself is still not proof that an application tool was authorized; retain the identity-policy decision and tool outcome alongside budget records. ## Why Current Tools Don't Cover Governance @@ -83,9 +83,9 @@ Each category of existing tools covers a fragment of the governance problem. Mos | Tool category | Security | Cost | Compliance | |---|---|---|---| -| **Observability** (Langfuse, LangSmith, Arize) | Visibility only | Visibility only | Partial reconstruction | +| **Observability mode** (for example, trace ingestion without a gateway policy) | Visibility only | Visibility only | Partial reconstruction | | **Rate limiters** | Velocity control | Velocity control | No | -| **Provider caps** (OpenAI monthly limits) | No | Coarse, org-level | No | +| **Provider cost controls** | Product-dependent | Vendor project/workspace/account scope | Provider traffic only | | **Content guardrails** (Guardrails AI, NeMo) | Content filtering | No | No | | **MCP / A2A protocols** | Tool discovery | No | No | | **[Runtime authority](/glossary#runtime-authority)** | Pre-execution decision point | Pre-execution budget enforcement | Enforcement and settlement evidence | @@ -135,7 +135,7 @@ tenant:acme-corp └─ toolset:email-tools ``` -When a reservation is created at the agent level, the system checks budget availability at every ancestor scope simultaneously. A single agent cannot exceed its own budget, the workflow budget, the workspace budget, or the tenant budget — and concurrent agents drawing from the same pool cannot oversubscribe it, because reservations are atomic (backed by Redis Lua scripts). +When a reservation is created at the agent level, the system checks every explicitly provisioned ledger among the derived ancestor scopes simultaneously; absent ledgers are skipped. Atomic Redis-backed mutations prevent concurrent submitted estimates from oversubscribing those matching ledgers. Actual usage above an estimate follows the selected commit-overage policy. This lets a budget hierarchy mirror organizational boundaries. Enforcement applies where budgets and the mandatory reservation boundary are configured. @@ -219,7 +219,7 @@ Three paths, depending on your current state: 7. [RAND Corporation](https://www.rand.org/pubs/research_reports/RRA2680-1.html) — AI project failure estimates 8. [Knostic MCP security analysis](https://blog.sshh.io/p/everything-wrong-with-mcp) — 1,862 exposed servers 9. [Replit database deletion incident](https://techcrunch.com/2025/10/02/after-nine-years-of-grinding-replit-finally-found-its-market-can-it-keep-it/) — TechCrunch, October 2025 -10. [Rogue agents working together](https://www.theregister.com/2026/03/12/rogue_ai_agents_worked_together/) — compromised agents escalating privileges +10. [Rogue agents working together](https://www.theregister.com/security/2026/03/12/rogue-ai-agents-can-work-together-to-hack-systems/5228926) — compromised agents escalating privileges ## Further Reading diff --git a/blog/ai-agent-production-gap-what-developers-are-saying.md b/blog/ai-agent-production-gap-what-developers-are-saying.md index 6d2b17ba..3f580e90 100644 --- a/blog/ai-agent-production-gap-what-developers-are-saying.md +++ b/blog/ai-agent-production-gap-what-developers-are-saying.md @@ -26,9 +26,9 @@ Across recent Reddit discussions, Hacker News threads, Stack Overflow posts, and Cost growth is one of the most frequently discussed pain points. -A [widely-shared analysis](https://medium.com/@klaushofenbitzer/token-cost-trap-why-your-ai-agents-roi-breaks-at-scale-and-how-to-fix-it-4e4a9f6f5b9a) on Medium — "Token Cost Trap: Why Your AI Agent's ROI Breaks at Scale" — walks through how a POC costing $500 in one month rocketed to $847K/month when deployed broadly. In February 2026, a data enrichment agent [misinterpreted an API error and ran 2.3 million API calls over a weekend, costing $47K](https://rocketedge.com/2026/03/15/your-ai-agent-bill-is-30x-higher-than-it-needs-to-be-the-6-tier-fix/). The [LangChain 2026 State of AI Agents report](https://www.langchain.com/state-of-agent-engineering) confirms this: agents make 3–10x more LLM calls than simple chatbots. A single request can trigger planning, tool selection, execution, verification, and response generation — each a separate billable API call. +A [Medium analysis](https://medium.com/@klaushofenbitzer/token-cost-trap-why-your-ai-agents-roi-breaks-at-scale-and-how-to-fix-it-4e4a9f6f5b9a) models a proof of concept growing from $500 to an $847,000 monthly projection under broad deployment assumptions. Separately, a vendor-authored article [reported a $47,000 data-enrichment loop](https://rocketedge.com/2026/03/15/ai-agent-cost-control/). These are planning and self-published examples, not independently verified population data. The architectural mechanism is still useful: one request can trigger planning, tool selection, execution, verification, and response generation, each with its own calls and retries. -The numbers developers are reporting: +Illustrative monthly ranges synthesized from those discussions—not industry benchmarks: | Deployment stage | Typical monthly cost | |---|---| @@ -45,17 +45,15 @@ On Hacker News, a [thread analyzing ICLR 2026 papers on multi-agent failures](ht A related but distinct frustration: teams have _excellent_ visibility into what their agents are doing and still can't prevent overspend or dangerous actions. -The [LangChain report](https://www.langchain.com/state-of-agent-engineering) found that 89% of organizations have implemented some form of observability for their agent systems. Platforms like Langfuse, LangSmith, Arize, and Helicone are widely adopted. And yet 32% of organizations still cite quality as their top barrier, and cost overruns remain the most common production incident. +The [LangChain report](https://www.langchain.com/state-of-agent-engineering) found that 89% of respondents had implemented some form of observability for agent systems, while 32% cited quality as their top production barrier. Those findings show broad use of telemetry without establishing that observability alone solves execution control. Why? Because observability tools are designed to _record_ what happened, not _control_ what happens next. They answer "what did the agent do?" but not "should the agent be allowed to do this?" -On Hacker News, this gap has spawned its own category of discussion. One commenter put it plainly: "We have three dashboards showing us our agent burned through $8K last weekend. None of them could have stopped it." - -**The missing layer:** Between the orchestration framework (LangGraph, CrewAI, OpenAI Agents SDK) and the observability platform (Langfuse, LangSmith) sits a layer that most architectures don't have — [an enforcement point that evaluates every action against budgets and policies before execution](/blog/cycles-vs-llm-proxies-and-observability-tools). Cycles operates in this layer: after the agent decides what to do, but before it does it. +**The missing layer:** A stack needs a mandatory boundary wherever it expects a limit to hold. Current LLM gateways can enforce inference budgets; [Cycles can add reserve-commit budgets](/blog/cycles-vs-llm-proxies-and-observability-tools) across other explicitly instrumented operations. Application authorization remains responsible for deciding whether a tool and its arguments are permitted. ### 3. Multi-Agent Error Cascades -Google DeepMind research shared widely on Hacker News found that multi-agent networks amplify errors by 17x. This finding resonated deeply with practitioners who are building multi-agent systems and discovering firsthand that reliability doesn't compose linearly. +In a controlled evaluation of 180 agent configurations, [Google Research](https://research.google/blog/towards-a-science-of-scaling-agent-systems-when-and-why-agent-systems-work/) found architecture-dependent behavior: independent agents amplified errors by up to 17.2x, while centralized coordination limited amplification to 4.4x. The result applies to the evaluated tasks and architectures, not every multi-agent deployment. The math is simple and devastating: if each agent step has 95% reliability, a 20-step chain has 36% overall reliability. With multiple agents running in parallel, sharing context, and making decisions based on each other's outputs, failure modes multiply rather than add. @@ -91,7 +89,7 @@ Google's A2A (Agent-to-Agent) protocol and the new Linux Foundation Agentic AI F ### 5. The "Demo to Production" Gap -TechCrunch declared 2026 the year AI moves [from hype to pragmatism](https://techcrunch.com/2026/01/02/in-2026-ai-will-move-from-hype-to-pragmatism/). An NBER study from February 2026 found that 89% of firms reported zero measurable change in productivity from AI. A RAND Corporation study found over 80% of AI projects fail to reach production; MIT reports 95% fail due to lack of architectural robustness. Gartner projects 40% of agentic AI projects will be scrapped by 2027 for failing to link to measurable business value. Meanwhile, NIST announced the AI Agent Standards Initiative in February 2026, signaling that governance is now a first-class concern at the regulatory level. +TechCrunch described 2026 as a move [from hype to pragmatism](https://techcrunch.com/2026/01/02/in-2026-ai-will-move-from-hype-to-pragmatism/). An [NBER survey of nearly 6,000 executives](https://www.nber.org/papers/w34836) reported that 89% saw no AI-related productivity impact over the prior three years; that is a self-reported retrospective measure, not proof that AI produced zero measurable change everywhere. A [RAND report](https://www.rand.org/content/dam/rand/pubs/research_reports/RRA2600/RRA2680-1/RAND_RRA2680-1.pdf) noted that, by some outside estimates, more than 80% of AI projects fail, then used interviews with 65 experienced practitioners to study failure causes. RAND did not itself measure an 80% production-failure rate. Meanwhile, [NIST announced its AI Agent Standards Initiative](https://www.nist.gov/news-events/news/2026/02/announcing-ai-agent-standards-initiative-interoperable-and-secure) in February 2026. On r/LocalLLaMA, a trending post titled "Agent this, coding that, but all I want is a KNOWLEDGEABLE Model!" captures the community fatigue. CNN summarized the sentiment: "AI is either your most helpful coworker, a glorified search engine or vastly overrated depending on who you ask." diff --git a/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk.md b/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk.md index 2a99d492..5160edc3 100644 --- a/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk.md +++ b/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk.md @@ -17,7 +17,7 @@ head: > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. -A customer-onboarding agent sends 200 collections emails instead of welcome emails. Total model cost: $1.40. Business impact: [$50K+ in lost pipeline](/blog/ai-agent-action-control-hard-limits-side-effects). A coding agent retries an ambiguous error 240 times. Total model cost: [$4,200](/blog/ai-agent-failures-budget-controls-prevent). Both agents passed every test. Both were under their dollar budgets. In both cases, the agent had no tool-level risk assessment. +Two illustrative scenarios show the difference between spend and action risk. Two hundred mistaken emails might cost about $1.40 in model tokens while creating much larger, unquantified business harm. Separately, a coding-agent loop runs 240 iterations and costs [$52.80 under its stated token assumptions](/blog/ai-agent-failures-budget-controls-prevent). Neither dollar figure describes whether the underlying action was authorized or safe. These are not anomalies. They are what happens when teams deploy agents without classifying what those agents can do. Model risk — bias, hallucination, drift — gets attention. **Tool risk** — what happens when the agent acts on the world — does not. @@ -62,7 +62,7 @@ The agent does something it should not have done, or does the right thing too ma ### Dimension 3: Delegation Risk -The agent spawns sub-agents, delegates tasks, or hands off to other agents. Each delegation multiplies exposure — the parent's budget covers the child's mistakes. A three-level delegation chain with a 3x retry multiplier at each level creates 27x worst-case cost amplification. +The agent spawns sub-agents, delegates tasks, or hands off to other agents. Nested retry policies can replay lower subtrees; under an illustrative three-level chain with a 3× execution multiplier at each level, one logical path can produce up to 27 executions. Cycles does not infer this graph or transfer parent balances, so callers must submit a shared workflow scope and any explicitly provisioned child agent scopes. **Unit of measurement:** Delegation depth, sub-agent count, inherited permissions. **Existing coverage:** Covered in [Multi-Agent Budget Control](/blog/multi-agent-budget-control-crewai-autogen-openai-agents-sdk). @@ -79,7 +79,7 @@ Every tool your agent can call has a risk profile. Classifying tools by tier is |:----:|-------|---------|---------------|-------------|-------------------| | 0 | **Read-only** | Search, retrieve, summarize, vector lookup | No state change | None (side-effect risk only; sensitive-read risk may be budgeted separately) | [Event](/protocol/how-events-work-in-cycles-direct-debit-without-reservation) (post-hoc accounting) | | 1 | **Write-local** | Save draft, write log, update cache, create temp file | Easy to reverse | Internal only | Event or reserve-commit depending on volume | -| 2 | **Write-external** | API call to third party, webhook trigger, external query | Possible but not guaranteed | Partner systems affected | [Reserve-commit](/protocol/how-reserve-commit-works-in-cycles) (always) | +| 2 | **Write-external** | API call to third party, webhook trigger, external query | Possible but not guaranteed | Partner systems affected | Host authorization plus [reserve-commit](/protocol/how-reserve-commit-works-in-cycles) before execution | | 3 | **Mutation** | DB write/update/delete, send email, post to Slack, create ticket | Difficult or impossible | Customer-facing | Reserve-commit with caps | | 4 | **Execution** | Deploy, payment processing, infrastructure change, permission grant | Irreversible in practice | Production users, financial, regulatory | Reserve-commit with strict allowlist | @@ -391,25 +391,25 @@ curl -s -X POST http://localhost:7979/v1/admin/budgets \ }' ``` -Both risk-point and cost budgets enforce independently. The agent can be under its dollar budget but over its risk-point budget — or vice versa. This is the dual-control model: cost authority and [action authority](/concepts/action-authority-controlling-what-agents-do) operating in parallel. Budgets at both levels enforce hierarchically — a run cannot exceed its own 250-point limit, and all runs together cannot exceed the app-level ceiling. See [common budget patterns](/how-to/common-budget-patterns) for more examples. +Risk-point and cost ledgers are separate because each reservation has one unit. A mandatory host can require both controls before an action, coordinating the two reservations in application code. Within each unit, explicitly provisioned run and app ledgers can provide overlapping ceilings; the host still authorizes the action and handles partial acquisition or settlement failures. See [common budget patterns](/how-to/common-budget-patterns) for examples. ### Step 2: Validate with Shadow Mode -Before turning enforcement on, run the assessment against real traffic. [Shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) evaluates every action against the budget but never denies. The output shows: +Before turning enforcement on, route representative protected traffic through [dry-run evaluation](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production). A dry-run request evaluates the submitted exposure without creating a reservation or mutating balances and does not itself execute or block the action. Current v0.1.25.x emits `reservation.denied` for denied evaluations, but the application must log all responses and actual outcomes for a complete dataset. That dataset can show: - How many runs would have been denied (budget too tight) - Which tools consume the most risk points (scoring calibration) - How actual usage compares to your "normal run" estimate - Whether the degradation thresholds trigger at the right points -Run shadow mode for at least one week of production traffic. If more than 5% of runs would have been denied, the budget is too tight — either raise the per-run limit or re-examine the tool scores. If zero runs would have been denied, the budget may be too loose — it would not have caught the incidents you designed it to prevent. +Collect enough traffic to cover normal peaks, retries, and rare high-risk paths. Choose an acceptable would-deny rate from your own service objectives; there is no universal one-week window or 5% threshold. Investigate both false denials and an implausible absence of denials before enabling blocking behavior. ### Step 3: Calibrate and Adjust Risk assessments are living documents. Scores should change when: - **A new tool is added.** Classify it, score it, update the worksheet before deployment. -- **An incident occurs.** If a tool caused damage, re-evaluate its tier and multiplier. The $50K email incident would justify raising `send_customer_email` from 40 to 60 points. +- **An incident occurs.** If a tool caused damage, re-evaluate its tier and multiplier. A mistaken-email event can justify raising `send_customer_email` from 40 to 60 points, but the application should also fix template, recipient, and authorization controls. - **Usage patterns shift.** If shadow mode shows agents consistently using 230 of 250 points on normal runs, the budget is too tight for safe operation — raise it or optimize the workflow. - **Context changes.** Entering a new market with different regulatory requirements, expanding the customer base, or adding delegation capabilities all change the multipliers. diff --git a/blog/ai-agent-runtime-permissions-control-actions-before-execution.md b/blog/ai-agent-runtime-permissions-control-actions-before-execution.md index 689c4911..7bb8b678 100644 --- a/blog/ai-agent-runtime-permissions-control-actions-before-execution.md +++ b/blog/ai-agent-runtime-permissions-control-actions-before-execution.md @@ -16,7 +16,7 @@ head: > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. -A team ships an autonomous support agent. It reads tickets, queries a knowledge base, drafts replies, and sends emails. In staging it handles 50 tickets without incident. On day three in production, a customer submits a ticket in a language the model handles poorly. The agent misinterprets the request, drafts a refund confirmation for a billing dispute, and sends it — along with 47 follow-up emails to related accounts offering refunds nobody requested. +Consider a constructed support-agent scenario. The agent reads tickets, queries a knowledge base, drafts replies, and sends emails. In production-shaped testing, an ambiguous ticket causes it to draft a refund confirmation and send 47 follow-up emails offering refunds nobody requested. Total API cost of the emails: $1.40. Business damage: $34,000 in honored refunds, an incident review, and a week of manual cleanup. @@ -74,11 +74,11 @@ Runtime permissions are pre-execution decisions about whether an agent may invok This is different from static configuration. A static tool allowlist says "this agent can send emails" — a decision made at deploy time. A runtime permission says "this agent can send emails, but it has already sent 5 in this run, and its action budget for external writes is exhausted, so the next email is denied." The first is a capability declaration. The second is a live enforcement decision that adapts as the agent acts. -An application can combine its permission decision with Cycles' [three-way budget decision](/glossary#three-way-decision): +An application can combine its permission decision with Cycles' [three-way preflight or dry-run decision](/glossary#three-way-decision): - **ALLOW** — the action is within limits; proceed normally - **ALLOW_WITH_CAPS** — the action is allowed but should be constrained (disable certain tools, limit remaining steps) -- **DENY** — the action is not permitted; the agent must stop or degrade +- **DENY** — the submitted estimate does not fit the configured budget; the application must stop or degrade The three-way model is what makes runtime permissions practical. A binary allow/deny forces hard stops. ALLOW_WITH_CAPS enables [graceful degradation](/glossary#graceful-degradation) — the agent loses dangerous capabilities while retaining useful ones. @@ -93,7 +93,7 @@ In the current Cycles server, the allowlist or denylist comes from the deepest m ### RISK_POINTS: a non-monetary unit for action risk -Dollar budgets measure financial exposure. But the opening scenario shows that the costliest incidents are not the most expensive in token terms. Two hundred wrong emails cost $1.40 in model calls and $34,000 in business damage. +Dollar budgets measure financial exposure. A constructed 200-email scenario can have about $1.40 in model-call cost while creating much larger, deployment-specific business impact. The latter cannot be inferred from token spend. [RISK_POINTS](/glossary#risk-points) is a unit designed for this problem. Instead of denominating action budgets in dollars, teams assign point values to each action class based on blast radius and reversibility: diff --git a/blog/ai-agent-silent-failures-why-200-ok-is-the-most-dangerous-response.md b/blog/ai-agent-silent-failures-why-200-ok-is-the-most-dangerous-response.md index 7e97d233..7d0e9236 100644 --- a/blog/ai-agent-silent-failures-why-200-ok-is-the-most-dangerous-response.md +++ b/blog/ai-agent-silent-failures-why-200-ok-is-the-most-dangerous-response.md @@ -54,7 +54,7 @@ This is the [0.95^10 problem](https://www.artiquare.com/why-multi-agent-ai-fails But here's what makes silent failures worse than the raw math suggests: **you don't know which 40% failed**. A crash at step 3 gives you a stack trace. A silent failure at step 3 gives you a wrong result at step 10 — with no indication of where things went off track. -[Google DeepMind research](https://research.google/blog/towards-a-science-of-scaling-agent-systems-when-and-why-agent-systems-work/) found that multi-agent systems amplify errors by **17x**. [OWASP's 2026 Top 10 for Agentic Applications](https://adversa.ai/blog/cascading-failures-in-agentic-ai-complete-owasp-asi08-security-guide-2026/) lists cascading failures (ASI08) as a critical security concern, noting three factors that make agentic cascading failures categorically worse than traditional distributed systems: +In a controlled evaluation of 180 agent configurations, [Google Research](https://research.google/blog/towards-a-science-of-scaling-agent-systems-when-and-why-agent-systems-work/) found that the independent-agent architecture amplified errors by up to **17.2x**, while centralized coordination limited amplification to 4.4x. Those benchmark results depend on the evaluated tasks and architectures. [OWASP's 2026 Top 10 for Agentic Applications](https://adversa.ai/blog/cascading-failures-in-agentic-ai-complete-owasp-asi08-security-guide-2026/) lists cascading failures (ASI08) as a critical security concern, noting three factors that make agentic cascading failures categorically worse than traditional distributed systems: 1. **Semantic opacity** — Agent-to-agent communication happens in natural language or loosely-typed JSON. Semantic errors pass validation and propagate as "valid" data. 2. **Emergent behavior** — Two agents acting "correctly" per their local objectives can produce catastrophic results when their actions combine. @@ -113,7 +113,7 @@ Each checkpoint creates three things that combat silent failures: 2. **A cost signal for anomaly detection** — Silent failures often have a distinct cost signature. A fabricated tool output costs nothing (no actual API call). A context-loss handoff shows a sudden drop in token usage. A looping agent shows monotonically increasing per-step costs. When you track the _economics_ of every step, anomalies that are invisible in logs become obvious in the spend pattern. -3. **Blast radius containment** — Even if a silent failure slips past the checkpoint, [per-step and per-run budgets](/blog/ai-agent-budget-control-enforce-hard-spend-limits) cap how far the damage can spread. A wrong answer at step 3 can't cascade through 50 more steps if the run budget only allows 10. +3. **Blast radius containment** — Even if a silent failure slips past the checkpoint, mandatory per-step reservations against a [workflow ledger keyed per run](/blog/ai-agent-budget-control-enforce-hard-spend-limits) can bound the caller-assigned exposure. If each step costs one configured unit, a ledger with ten units rejects the next protected step after ten successful charges; the host must then stop or degrade the run. ## How This Works in Practice with Cycles @@ -143,7 +143,7 @@ A commit with near-zero cost and near-zero latency for a tool call that should i ### Capping test-rewriting agents -[Action authority](/blog/ai-agent-action-control-hard-limits-side-effects) assigns severity scores to different actions. A coding agent with authority to _read_ test files and _write_ source files but restricted authority to _modify_ test files would be blocked at the reserve step. The reserve request specifies the action kind (`file.write`) and target (`tests/`), and the policy denies it: +[Action authority](/blog/ai-agent-action-control-hard-limits-side-effects) combines host authorization with a caller-defined exposure budget. A coding-agent host can deny writes to `tests/` through its own path policy. For writes that are authorized, the integration can assign `RISK_POINTS` and reserve them at the mandatory execution boundary: ```jsonc // Agent tries to modify test file @@ -152,16 +152,16 @@ POST /v1/reservations "action": { "kind": "file.write", "name": "tests/unit/customer.test.js" }, "estimate": { "unit": "RISK_POINTS", "amount": 25 } } -// → 409 BUDGET_EXCEEDED — test modification not in allowed scope +// → 409 BUDGET_EXCEEDED when the matching RISK_POINTS ledger is exhausted ``` -The agent can't silently rewrite tests because the checkpoint requires explicit permission for that action category. +Cycles does not infer that `tests/` is forbidden or turn risk points into a permission list. The host must enforce the path rule independently; the Cycles reservation bounds cumulative caller-submitted exposure for operations that pass that rule. ### Detecting lost state handoffs -When a multi-agent workflow uses [hierarchical scopes](/protocol/how-scope-derivation-works-in-cycles) — a parent workflow scope with child agent scopes — the cost signature of each handoff is visible. If the research agent reserves and commits budget for 15 data-collection steps, but the analysis agent only reserves budget for 9 analysis steps, the mismatch is visible in the balance ledger. An automated check on the workflow scope can flag: "Research agent produced 15 items; analysis agent processed 9. Discrepancy of 6 items." +When a multi-agent workflow uses [hierarchical scopes](/protocol/how-scope-derivation-works-in-cycles) — a broader workflow ledger plus narrower agent ledgers — separately instrumented steps leave reservation records. If the research agent commits 15 action records but the analysis agent commits only 9, application-side monitoring can correlate those records with handoff IDs and flag a discrepancy. -This doesn't require the agents to be aware of the check. The checkpoint layer sees the _economic footprint_ of each step and can detect when downstream steps don't match upstream work. +The aggregate balance alone does not reveal how many items crossed a handoff, and Cycles does not perform this anomaly check automatically. Record handoff and item identifiers in application telemetry or reservation metadata, retain the external outcomes, and run the comparison in your observability layer. ## The Broader Pattern: Budget as a Reliability Signal @@ -177,7 +177,7 @@ An agent that's working correctly has a predictable cost pattern: consistent per | Wrong tool selection | Unexpected action kind in the [reservation](/glossary#reservation) | | Hallucinated completion | Missing commit for reserved steps (agent skipped execution) | -None of these signals are guaranteed catches. But they're signals that _don't exist_ in architectures without per-step checkpoints. And they're automatically generated — no extra instrumentation, no custom logging, no manual review. Every reserve-commit cycle produces the data needed to detect anomalies. +None of these signals is a guaranteed catch. An instrumented reserve-commit cycle produces budget lifecycle data, but anomaly detection, alert thresholds, and external outcome correlation still require observability and application logic. Combined with [per-run budgets](/blog/ai-agent-budget-control-enforce-hard-spend-limits) that cap total [exposure](/glossary#exposure) and [atomic reservations](/concepts/idempotency-retries-and-concurrency-why-cycles-is-built-for-real-failure-modes) that prevent concurrent agents from racing past limits, the checkpoint pattern creates defense in depth against both loud failures (budget exceeded) and quiet ones (cost anomaly detected). @@ -191,7 +191,7 @@ The cheapest silent failure is the one caught at the checkpoint. Here's how to s 2. **[Instrument one high-risk workflow first](/quickstart/how-to-choose-a-first-cycles-rollout-tenant-budgets-run-budgets-or-model-call-guardrails)** — Pick the workflow where a wrong answer has real consequences. Add reserve-commit checkpoints. Set per-step budgets. Monitor for the cost anomaly signals described above. -3. **[Add action authority for sensitive operations](/blog/ai-agent-action-control-hard-limits-side-effects)** — Any action that modifies data, sends communications, or affects external systems should require explicit permission at the checkpoint. If the agent tries to do something outside its authorized scope, it's blocked — not logged after the fact. +3. **[Add action authority for sensitive operations](/blog/ai-agent-action-control-hard-limits-side-effects)** — Put host authorization and argument validation before sensitive operations, then reserve caller-assigned risk points at the same mandatory boundary. The host blocks disallowed actions; Cycles denies authorized actions whose matching exposure budget is exhausted. 4. **[Run the 60-second demo](/demos/)** — See budget enforcement and action checkpoints stop a runaway agent in real time. Then imagine the same mechanism catching a silent failure before it reaches your customers. diff --git a/blog/ai-agent-spend-limits-are-not-rate-limits.md b/blog/ai-agent-spend-limits-are-not-rate-limits.md index bbe55a4b..66a6d434 100644 --- a/blog/ai-agent-spend-limits-are-not-rate-limits.md +++ b/blog/ai-agent-spend-limits-are-not-rate-limits.md @@ -4,7 +4,7 @@ date: 2026-07-03 author: Albert Mavashev tags: - cost-control - - rate-limiting + - rate-limits - agents - runtime-authority - architecture diff --git a/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin.md b/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin.md index 93188587..393d2c37 100644 --- a/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin.md +++ b/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin.md @@ -126,15 +126,15 @@ The $15/user/month cap turns a 23% margin feature into a 68% margin feature — Token pricing is an engineering metric. Cost per conversation is a business KPI. Three patterns for using it: -**Chargeback.** Enterprise customers pay for actual AI usage. Cycles' per-tenant tracking provides the billing data — every reservation and commit is scoped to a tenant, so cost attribution is automatic. The usage report is the invoice. See [Multi-Tenant AI Cost Control](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) for the full chargeback model. +**Chargeback.** Enterprise customers may pay for actual AI usage. A tenant-scoped Cycles integration provides allocation and settlement records for the amounts the application submits, but billing still needs provider usage reconciliation, complete path coverage, and invoice rules. Treat Cycles records as one chargeback input, not automatically as the invoice. See [Multi-Tenant AI Cost Control](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) for the full model. **Feature-level P&L.** Treat the AI copilot as its own cost center. Track cost per conversation as COGS. Monitor margin weekly. Set alerts when margin drops below threshold. This is [Tier 3 of the cost management maturity model](/blog/ai-agent-cost-management-guide) — alerting on business metrics, not just raw spend. -**Model routing by economics.** Route simple conversations to GPT-4o-mini ($0.15/1M input [tokens](/glossary#tokens)) and complex conversations to GPT-4o ($2.50/1M input tokens). The routing decision is economic, not just capability-based. A simple "what's my order status?" query does not need a $2.50/1M-token model. A complex debugging session does. [Routing and enforcement complement each other](/blog/manifest-vs-cycles-routing-vs-runtime-authority) — the router picks the model, the [runtime authority](/glossary#runtime-authority) bounds the cost. +**Model routing by economics.** Route simple conversations to a lower-cost model and complex conversations to a more capable model after measuring quality on both paths. Provider prices and model catalogs change, so calculate with current or contracted rates rather than embedding an old list price in routing logic. [Routing and enforcement complement each other](/blog/manifest-vs-cycles-routing-vs-runtime-authority) — the router picks the model, while the [runtime authority](/glossary#runtime-authority) checks the submitted estimate. ## From cost visibility to cost control -Cost overruns are a symptom. The root cause is the absence of a pre-execution enforcement layer — a system that asks "is there budget for this?" before every action, not after. That's what [runtime authority](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) provides: deterministic budget decisions at the point of execution, not retroactive alerts on a dashboard. +Unit economics become actionable when the per-conversation target is also an enforceable run allocation. A [runtime budget boundary](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) lets the application reserve against that allocation before covered work, keeping one heavy conversation from silently consuming the margin assigned to many light ones. ## Next steps diff --git a/blog/ai-coding-agents-when-the-token-bill-comes-due.md b/blog/ai-coding-agents-when-the-token-bill-comes-due.md index 24195c77..5f6e1e16 100644 --- a/blog/ai-coding-agents-when-the-token-bill-comes-due.md +++ b/blog/ai-coding-agents-when-the-token-bill-comes-due.md @@ -55,7 +55,7 @@ For the most part, these were not failures of absent cost controls — they were **The invoice is a 30-day feedback loop.** Monthly billing reconciliation is how a budget's exhaustion becomes visible in April instead of February. By the time a finance team sees the line item, the tokens are spent. Post-hoc visibility is accounting, not control — the distinction the [cost-control landscape guide](/blog/ai-agent-cost-control-2026-litellm-helicone-openrouter-runtime-authority) draws between tracking what happened and deciding what may happen. -**Provider caps are org-shaped, not work-shaped.** A provider-level spending cap is one number for the whole organization. It cannot say "this team, $2K/month," "this repository's CI agent, $50/day," or "this session, $5." When the org cap finally binds, it stops everyone — the runaway session and the critical migration alike. The granularity problem is structural, and it is the same one [provider spending caps](/concepts/cycles-vs-provider-spending-caps) have for any agent workload. +**Provider controls are provider-shaped, not automatically work-shaped.** Depending on the vendor, controls may apply to an organization, project, workspace, key, credit balance, or throughput quota. Those scopes can separate some workloads, but they do not infer "this repository's CI agent, $50/day" or "this session, $5" from shared credentials. A hard provider cutoff can also affect unrelated work sharing that boundary. The [provider-controls comparison](/concepts/cycles-vs-provider-spending-caps) covers the current vendor differences. **License revocation is the control of last resort.** Microsoft's reported move — revoking Claude Code licenses; the reasoning was not part of the reporting — reads like what an organization reaches for when it has no instrument between "unlimited" and "off." Revocation works, in the sense that a tourniquet works. It also converts a cost problem into a productivity problem, and it teaches the organization that the tool is dangerous rather than that the spend was ungoverned. diff --git a/blog/audit-evidence-has-to-survive-production.md b/blog/audit-evidence-has-to-survive-production.md index 2a34a307..82d8f704 100644 --- a/blog/audit-evidence-has-to-survive-production.md +++ b/blog/audit-evidence-has-to-survive-production.md @@ -200,7 +200,7 @@ The last item is the one that matters most. A control that has never been drille Without signed evidence, the audit conversation depends on trust in the operator's live system. "Our server denied it" is a claim. "Here is the signed, content-addressed receipt; here is the key that was authoritative at the time; here is the verifier output" is a different conversation. -That difference matters for budget controls, but it matters even more as runtime authority expands to action surfaces: memory writes, merge buttons, computer-use clicks, voice frames, and every other place an agent can cause durable change. A `DENY` against USD spend and a `DENY` against RISK_POINTS are both policy decisions. Both should be provable later when the question is not "what did the system log?" but "what was the agent authorized to do?" +That difference matters for budget controls, but it matters even more as runtime authority expands to action surfaces: memory writes, merge buttons, computer-use clicks, voice calls, and other places an agent can cause durable change. A `DENY` against USD spend and a `DENY` against caller-assigned RISK_POINTS are budget decisions. Retain them alongside host authorization and outcome records so a later reviewer can distinguish “the budget rejected this attempt” from “the principal lacked permission” and “the external action actually occurred.” The enforcement layer is the gate. Evidence is the receipt. Operations is what keeps the receipt believable after the gate has done its job. diff --git a/blog/beyond-budget-how-cycles-controls-agent-actions.md b/blog/beyond-budget-how-cycles-controls-agent-actions.md index 4774d2c4..2a4792a2 100644 --- a/blog/beyond-budget-how-cycles-controls-agent-actions.md +++ b/blog/beyond-budget-how-cycles-controls-agent-actions.md @@ -1,9 +1,9 @@ --- -title: "How Cycles Controls Agent Actions" +title: "How Cycles Meters Caller-Assigned Action Exposure" date: 2026-04-02 author: Albert Mavashev tags: [action-authority, RISK_POINTS, runtime-authority, tool-governance, agents] -description: "Learn how Cycles applies reserve-commit accounting to caller-assigned risk budgets, helping applications limit agent actions as well as model spend at runtime." +description: "Learn how Cycles meters caller-assigned risk budgets while application authorization controls which agent tools and arguments may execute at runtime boundaries." blog: true sidebar: false head: @@ -12,7 +12,7 @@ head: content: AI agent action control, action authority, RISK_POINTS, reserve commit, tool governance, runtime authority --- -# Beyond Budget: How Cycles Controls Agent Actions, Not Just Spend +# Beyond Spend: Metering Caller-Assigned Action Exposure > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. @@ -41,7 +41,7 @@ Cycles' reserve → commit → release lifecycle doesn't care what you're measur | `CREDITS` | Abstract [credits](/glossary#credits) | Internal allocation systems | | `RISK_POINTS` | Action risk | Tool calls, API requests, side effects | -When you use `RISK_POINTS`, you're not tracking cost — you're tracking **consequence**. An agent that reserves 50 risk points for `send_email` and 0 for `search_knowledge` isn't managing a budget. It's managing what the agent is allowed to do. +When you use `RISK_POINTS`, the application submits its own exposure estimate rather than a monetary cost. That budget can bound repeated authorized attempts, but it does not decide whether the principal may call `send_email` or whether particular recipients and arguments are safe. ## How tool estimate mapping works @@ -64,13 +64,13 @@ hooks = CyclesRunHooks( ) ``` -With a budget of 200 risk points per session: +With a budget of 200 risk points per session, assuming every protected tool attempt passes through the hooks: - The agent can search knowledge unlimited times (0 points each) - It can send 4 emails (50 × 4 = 200 points) - It can deploy to production twice (100 × 2 = 200 points) - It **cannot** send 3 emails and deploy once (150 + 100 = 250 > 200) -The [budget authority](/glossary#budget-authority) decides the risk allocation. The protocol enforces it. The agent never sees the limits — it just gets `DENY` when it tries to exceed them. +The application decides the estimates and provisions the budget. The plugin requires a live reservation before nonzero mapped attempts; it separately depends on the host and SDK to authorize and dispatch the tool. ## Beyond tool calls: action authority in every integration @@ -89,29 +89,29 @@ res = client.create_reservation(ReservationCreateRequest( )) if not res.is_success: - # Agent is not authorized to send this email + # The submitted exposure does not fit the configured budget. return "Email blocked — action limit reached." ``` -The `action.kind` and `action.name` fields give you per-action-type governance. The budget authority can set different limits for `tool.email` vs `tool.search` vs `tool.deploy`, and the agent's available actions shrink as it consumes its authority. +The `action.kind` and `action.name` fields describe the attempted operation in the lifecycle record. Current budget selection follows tenant and subject scopes, not an allow/deny rule inferred from those action strings. To isolate email, search, and deploy exposure, the caller can use distinct toolset subjects and provision explicit budgets for them. The host must still authorize the action. -## Real scenarios +## Illustrative scenarios ### Scenario 1: Support agent with email limits -A customer support agent can research, draft responses, and search the knowledge base freely. But it can only send 5 emails per session. On the 6th attempt, Cycles returns `DENY`, and the agent queues the email for human review instead. +A customer support host can allow research and drafting while requiring a 20-point reservation for every authorized email attempt against a 100-point session budget. The sixth attempt does not fit, and the host can queue it for human review. Without action authority, the agent's retry logic could send the same apology email dozens of times before anyone notices. ### Scenario 2: DevOps agent with deployment gates -A DevOps agent can run diagnostics, read logs, and suggest fixes with no limits. But deployments cost 100 risk points, and the agent has 100 per day. One deployment per day. If it needs a second, it escalates to a human. +A DevOps host can authorize diagnostics separately while assigning 100 risk points to each deployment attempt against a 100-point daily budget. A second attempt does not fit unless the host provisions more budget after its normal approval process. Without action authority, a debugging loop that keeps trying "deploy and check if fixed" could push 12 broken builds in an hour. ### Scenario 3: Research agent with API call caps -A research agent calls a third-party API during a research session. Each API call costs 1 risk point, and the agent has 50 points per session. After 50 calls, Cycles denies the 51st — the agent must summarize what it has and stop searching. Without this cap, a recursive research loop could make hundreds of API calls in a single session, burning through external API quotas and producing diminishing returns. +A research host can assign 1 risk point to each third-party API attempt and provide 50 points per session. At a mandatory boundary, the 51st attempt does not fit. The host decides whether to summarize, defer, or request more budget. ## Cost and consequence together @@ -134,25 +134,25 @@ def send_email(to: str, body: str) -> str: ... ``` -The agent can spend up to $5 on LLM calls (checked against the [USD_MICROCENTS](/glossary#usd-microcents) budget). It can send up to 4 emails (checked against the [RISK_POINTS](/glossary#risk-points) budget, 200 / 50). Each action checks its own unit's budget — the same protocol, the same concurrency safety, the same scope hierarchy, applied to different dimensions of authority. +The instrumented LLM path can reserve against a $5 [USD_MICROCENTS](/glossary#usd-microcents) budget. The mandatory email handler can separately authorize the call and reserve 50 points against a 200-point [RISK_POINTS](/glossary#risk-points) budget. Each submitted unit uses the same budget protocol; authorization remains outside that protocol. ## Why this matters for multi-agent systems In multi-agent systems — LangGraph workflows, AutoGen teams, CrewAI crews — action authority becomes critical. Each agent in the system can have its own risk budget: -- The **researcher** agent gets unlimited search but zero email authority -- The **writer** agent gets LLM budget but zero deployment authority -- The **executor** agent gets tool authority but limited LLM budget +- The **researcher** host role allows search and denies email +- The **writer** host role receives LLM budget while the host denies deployment +- The **executor** host role can receive a narrow toolset budget and a smaller LLM budget -The scope hierarchy (`tenant → workspace → app → workflow → agent → toolset`) means these limits are enforced independently per agent. The researcher cannot borrow the executor's deployment authority. A bug in the writer cannot trigger the executor's tools. +Caller-supplied agent and toolset subjects can isolate budgets when the corresponding ledgers are explicitly provisioned. Missing budgets are skipped, so a zero allocation or host denial—not an absent ledger—is required to block a path. Tool inventory and credentials remain host controls. This is the same hierarchical isolation that prevents one [tenant](/glossary#tenant) from spending another tenant's budget — applied to actions instead of dollars. ## Key points -- **Cycles governs actions, not just spend.** `RISK_POINTS` track consequence — tool calls, API requests, side effects — using the same reserve-commit protocol. -- **Zero-cost tools skip enforcement.** Assign 0 points to safe actions (search, read) so they never hit the Cycles API. -- **Per-agent action budgets.** In multi-agent systems, each agent gets its own risk allocation through the scope hierarchy. +- **Cycles meters submitted exposure as well as spend.** `RISK_POINTS` carry the application's estimate through the same reserve-commit protocol. +- **Zero-estimate tools skip this plugin's reservation.** The host still authorizes them and may need independent audit logging. +- **Per-agent exposure budgets require explicit subjects and ledgers.** They are not inferred from the agent graph. - **Cost and consequence together.** Use `USD_MICROCENTS` for spend limits and `RISK_POINTS` for action limits on the same agent — both enforced independently. - **The protocol is the same.** Reserve before the action, commit after, release on error. Whether you're tracking dollars or deployments, the lifecycle is identical. diff --git a/blog/budgeting-reasoning-tokens-governing-extended-thinking-before-it-bills.md b/blog/budgeting-reasoning-tokens-governing-extended-thinking-before-it-bills.md index 7092ca68..e05a2153 100644 --- a/blog/budgeting-reasoning-tokens-governing-extended-thinking-before-it-bills.md +++ b/blog/budgeting-reasoning-tokens-governing-extended-thinking-before-it-bills.md @@ -15,11 +15,11 @@ head: # Budgeting Reasoning Tokens Before They Bill -A team migrated a triage agent from `claude-3-5-sonnet` to `claude-sonnet-4-6` with extended thinking enabled. Same prompts, same tools. They bumped `max_tokens` to 40,000 so the model would have room for chain-of-thought, accepted Anthropic's default `budget_tokens`, and deployed. The next morning's invoice was 7x higher. The visible answers looked identical — short, correct, on-topic. The difference was hidden from the user-facing UI: each call burned 18,000–32,000 thinking tokens that either never appeared in the app at all or were summarized away. Nobody had set a per-call thinking cap tied to the run's overall budget. The budget wasn't wrong. It was blind. +Consider a constructed migration from `claude-3-5-sonnet` to `claude-sonnet-4-6` with manual extended thinking enabled. The team raises `max_tokens` to 40,000, configures a 32,000-token thinking budget, and deploys without adding thinking usage to its cost estimator. The visible answers remain short, but the billed output now includes internal reasoning. The exact multiplier depends on prompts, cache behavior, and model pricing; the failure mode is that the runtime budget still estimates only visible output. -Reasoning models — Claude extended thinking (Sonnet/Opus 4.x), OpenAI o3, Gemini 2.5 thinking, DeepSeek R1 — have quietly invalidated a generation of agent cost controls. Each provider offers some per-call control (`budget_tokens` or adaptive `effort` on Anthropic, `reasoning.effort` on OpenAI, `thinkingBudget` on Gemini), but if your production budget layer was built around visible output tokens, prompt length, request count, or provider spending caps, none of those knobs plug into it. The result: you're enforcing a budget that bears only loose correlation to what you actually pay. This post shows why, and how to fix it with a runtime authority layer that caps thinking spend before the model runs. +Reasoning models — current Claude adaptive-thinking models and earlier manual-thinking models, OpenAI reasoning models, Gemini thinking, and DeepSeek R1 — expose different per-call controls. Anthropic uses either `budget_tokens` or adaptive `effort` depending on the model; OpenAI uses `reasoning.effort` plus an output ceiling; Gemini 2.5 supports `thinkingBudget`. If a production budget estimator sees only visible output length, its reservations can bear little relationship to the amount ultimately billed. This post shows how to include the hidden work in a reserve-then-settle boundary while applying the provider control in application code. ## Why reasoning tokens break existing controls @@ -31,22 +31,22 @@ Reasoning tokens have three properties that existing governance layers don't han 3. **They are usually not surfaced to end users, even when the API returns them.** Your agent UI shows a clean four-sentence answer. Behind it, the model burned through 25,000 tokens of chain-of-thought the user never sees — whether the API hid the reasoning (Anthropic, OpenAI, Gemini) or returned it and your app stripped it (DeepSeek). Any observability dashboard that samples "response length" as a cost proxy will understate your real spend. -The practical consequence: `max_tokens` is now a cost cap in a way it never was before. In the pre-reasoning era, `max_tokens=4096` meant "at most 4096 tokens of visible output." Today, on a reasoning model, it means "at most 4096 tokens of *anything*, thinking included" — and if you set it too low, the model truncates mid-thought and returns an empty or garbage answer. If you set it high to be safe, you've silently uncapped per-call cost by 10x. +The practical consequence is that the generated-token ceiling now covers reasoning as well as visible output. Set it too low and the model may exhaust the allowance before producing a useful final answer. Set it high and the call has more room to consume billed output tokens. It remains a hard provider-side ceiling, but the ceiling may be much larger than a UI-based estimator assumes. -Rate limits are worse. A 10 requests-per-minute limit on an o3 agent can still produce $40/minute in reasoning spend, because the *per-request* cost is unbounded. Provider spending caps (OpenAI org limits, Anthropic workspace caps) trip at the end of the billing window — hours or days after the damage is done. See [why provider caps aren't enough](/blog/cycles-vs-llm-proxies-and-observability-tools) for more. +A request-rate limit still permits expensive requests up to the configured per-request output ceiling. Provider controls also differ: OpenAI now offers soft spend alerts and optional hard monthly organization/project limits, while prepaid-credit exhaustion and provider quotas have their own cutoff semantics. None of those controls, by itself, expresses a budget for one application workflow or run. See [why provider caps aren't enough](/blog/cycles-vs-llm-proxies-and-observability-tools) for the current comparison. ## The shape of the fix: separate caps, pre-flight reservation Governing reasoning tokens requires three things existing budget layers don't provide: -- **A thinking budget distinct from output budget**, enforced at the provider API level via `budget_tokens` (Anthropic), `reasoning_effort` (OpenAI), or `thinkingBudget` (Gemini). +- **A provider-side reasoning control and generated-token ceiling**, chosen for the specific model API (`budget_tokens`, adaptive effort, `reasoning.effort`, or `thinkingBudget`). - **A pre-flight reservation** sized to the *worst-case* combined token count, not the expected output length. - **Post-hoc reconciliation** that commits the actual thinking + output tokens against the reservation, so an agent that burns its thinking budget loses budget share from the run's overall cap. This is the runtime authority pattern: reserve → enforce → commit. It's the same pattern we apply to tool risk, delegation chains, and retry storms. Reasoning tokens are simply another dimension of exposure. For the general pattern, see [exposure: why rate limits leave agents unbounded](/concepts/exposure-why-rate-limits-leave-agents-unbounded). ::: info Proposed Caps extension — not yet in the published protocol -The Cycles Caps schema at conformance target **v0.1.25** covers `max_tokens`, `max_steps_remaining`, `tool_allowlist`, `tool_denylist`, and `cooldown_ms`. The reasoning-specific fields used in the examples below (`thinking_tokens`, `reasoning_effort`, `max_output_tokens`) are a **proposed extension** that fits the existing reserve-commit surface — they're the natural shape the Caps bag would take once reasoning-model support lands in a future protocol revision. For today, you can thread the same values through reservation `metadata` and have your policy layer translate them into the existing `max_tokens` cap plus a provider-specific parameter at the application edge. The code below reads as "what the integration looks like when the protocol surfaces these caps natively." +The Cycles Caps schema at conformance target **v0.1.25** covers `max_tokens`, `max_steps_remaining`, `tool_allowlist`, `tool_denylist`, and `cooldown_ms`. It does not define `thinking_tokens`, `reasoning_effort`, or `max_output_tokens`. Reservation metadata is opaque attribution data; the server does not translate it into provider settings. Today, application policy must choose the provider-specific reasoning setting, optionally reduce its output ceiling when a configured Cycles `max_tokens` cap is returned, and reserve a conservative cost estimate. Reasoning-specific cap fields would require a future protocol extension. ::: ```mermaid @@ -55,97 +55,81 @@ sequenceDiagram participant Cycles participant Anthropic - Agent->>Cycles: reserve(estimate: output_tokens + thinking_budget) - Cycles-->>Agent: proposed future caps (thinking_budget=8000, max_tokens=10000) + Agent->>Agent: choose provider reasoning policy + Agent->>Cycles: reserve(conservative combined cost estimate) + Cycles-->>Agent: accepted reservation + optional configured max_tokens Agent->>Anthropic: messages.create(thinking={budget_tokens: 8000}, max_tokens: 10000) Anthropic-->>Agent: response (output: 800, thinking: 6200) Agent->>Cycles: commit(actual: 7000 total output tokens) Cycles-->>Agent: OK (remaining budget updated) ``` -In this proposed extension, an operator-configured policy would return the reasoning cap and the application would enforce it at the provider call. The current server does not infer a thinking budget from remaining balance, tenant tier, or tool risk class; today the application selects the provider-specific reasoning limit and can store it as metadata while reserving the combined worst-case token estimate. +The application enforces the provider setting before the call. The current server does not infer a thinking budget from remaining balance, tenant tier, or tool risk class. It checks the submitted estimate against matching ledgers and returns only caps that were explicitly configured on the deepest matching budget. -## Concrete integration: Claude extended thinking +## Integration sketch: Claude manual extended thinking -Here's the integration for Anthropic's extended thinking API. Cycles acts as the authority that decides how much reasoning the agent can afford *for this particular call*. The code below uses the `runcycles` Python SDK's canonical surface — `CyclesClient` + `ReservationCreateRequest` — in the same shape the [delegation-chains post](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) and [unit-economics post](/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin) use. +The following architecture-focused pseudocode shows the boundary for a model that still supports manual `budget_tokens`; adapt the request/response wrappers and idempotency keys to the [Python quickstart](/quickstart/getting-started-with-the-python-client). Claude Sonnet 4.6 still accepts this mode but Anthropic marks it deprecated, so new integrations should prefer adaptive thinking where the selected model supports it. ```python -from anthropic import Anthropic -from runcycles import ( - CyclesClient, CyclesConfig, - ReservationCreateRequest, Action, Amount, Subject, Unit, -) - -client = CyclesClient(CyclesConfig.from_env()) -anthropic = Anthropic() - def run_reasoning_task(tenant_id: str, prompt: str, tool_risk: str): - # Reserve worst-case: output tokens + thinking budget. - # For a reasoning model, thinking is typically 3-10x expected output. + # Application policy chooses provider parameters; Cycles does not infer them. + thinking_budget = reasoning_policy(tenant_id, tool_risk).thinking_tokens + provider_max_tokens = thinking_budget + expected_visible_output(prompt) + + # Reserve a conservative combined cost estimate. worst_case_microcents = estimate_cost( - output=2000, thinking=12000, model="claude-sonnet-4-6" + output=provider_max_tokens, + model="claude-sonnet-4-6", ) - res = client.create_reservation(ReservationCreateRequest( + hold = reserve_or_raise( subject=Subject(tenant=tenant_id, workflow="reasoning"), action=Action(kind="llm.reason", name="anthropic-thinking"), estimate=Amount(amount=worst_case_microcents, unit=Unit.USD_MICROCENTS), metadata={"tool_risk": tool_risk, "model": "claude-sonnet-4-6"}, - )) - - if res.decision == "DENY": - raise BudgetExceeded(res.reason_code) - - # Cycles returns the thinking cap for THIS call via the ALLOW_WITH_CAPS path. - caps = res.caps or {} - thinking_budget = caps.get("thinking_tokens", 8000) - max_tokens = caps.get("max_tokens", thinking_budget + 2000) - - try: - response = anthropic.messages.create( - model="claude-sonnet-4-6", - max_tokens=max_tokens, - thinking={"type": "enabled", "budget_tokens": thinking_budget}, - messages=[{"role": "user", "content": prompt}], - ) - - # Reconcile: commit what was actually burned. - # Anthropic's usage.output_tokens already includes thinking tokens. - actual_microcents = cost_from_tokens( - input=response.usage.input_tokens, - output=response.usage.output_tokens, - model="claude-sonnet-4-6", - ) - client.commit_reservation( - reservation_id=res.reservation_id, - actual=Amount(amount=actual_microcents, unit=Unit.USD_MICROCENTS), - ) - return response - - except Exception: - client.release_reservation(reservation_id=res.reservation_id) - raise + ) + + # A generic configured cap can only reduce the application's ceiling. + if hold.caps and hold.caps.max_tokens is not None: + provider_max_tokens = min(provider_max_tokens, hold.caps.max_tokens) + + response = anthropic.messages.create( + model="claude-sonnet-4-6", + max_tokens=provider_max_tokens, + thinking={"type": "enabled", "budget_tokens": thinking_budget}, + messages=[{"role": "user", "content": prompt}], + ) + + # Commit the billed result. If the provider outcome is ambiguous, reconcile + # it instead of blindly releasing a hold for a call that may have executed. + actual_microcents = cost_from_tokens( + input=response.usage.input_tokens, + output=response.usage.output_tokens, + model="claude-sonnet-4-6", + ) + commit(hold, actual_microcents) + return response ``` Three things to note: -- **`thinking_budget` comes from Cycles, not the application.** A tenant on a lower tier gets a smaller cap. A high-risk tool gets a smaller cap. A run that has already consumed most of its overall budget gets a smaller cap. The agent code doesn't make this decision. -- **`max_tokens` must be ≥ `budget_tokens + expected_output`.** Anthropic requires this; Cycles enforces it by returning both values from `res.caps`. Set `max_tokens` too close to `budget_tokens` and the model will run out of budget for visible output. +- **The application policy chooses `thinking_budget`.** Current Cycles does not derive it from tenant tier, action name, risk points, or remaining balance. +- **For manual mode, `budget_tokens` must be less than `max_tokens`.** Leaving additional room for visible output is an application sizing decision. A configured Cycles `max_tokens` cap is generic; the host must apply it and ensure the final provider parameters are valid. - **Reconcile against `output_tokens`, not a separate thinking field.** Anthropic bills thinking as output. Your commit amount should treat them identically. -### Claude Opus 4.7 — adaptive thinking and task budgets +### Claude Opus 4.7 and 4.8 — adaptive thinking and task budgets -Opus 4.7 drops the manual `budget_tokens` knob. In its place, Anthropic introduced **adaptive thinking** — you pass `thinking: {"type": "adaptive"}` with an `output_config.effort` level and the model chooses how much to reason per call. For full agentic loops, Anthropic also added **task budgets** in beta, which cap total spend across a multi-turn run rather than per-call. Migrating the example above to Opus 4.7 means: +Opus 4.7 and 4.8 reject manual `budget_tokens`. Use **adaptive thinking** — `thinking: {"type": "adaptive"}` with an `output_config.effort` level — and let the model choose how much to reason per call. Anthropic also offers task budgets in beta on supported models. A task budget is an advisory token budget across an agentic loop, not a hard billing limit, so keep the external reservation boundary. - Replace `thinking: {"type": "enabled", "budget_tokens": N}` with `thinking: {"type": "adaptive"}` + `output_config: {"effort": effort}`. -- Read `effort` from `res.caps` (same shape as the OpenAI path below — the governance layer maps tenant/tool/budget state to an effort level). -- For long-running agents, wrap multiple reservations under one task-budget ceiling so the reserve-commit loop sits inside Anthropic's task-budget window rather than fighting it. +- Select `effort` in application policy; the current Cycles Caps schema does not carry an effort field. +- For long-running agents, use Anthropic's advisory task budget as a graceful-degradation signal and use Cycles reservations for the submitted external ceiling. The reserve → enforce → commit shape is unchanged; only the per-call parameter names move. ## The same pattern on OpenAI o-series -For OpenAI's reasoning models, there's no direct `budget_tokens` knob — you pick an effort level ("low", "medium", "high"; recent APIs also expose "minimal"), and reasoning tokens count toward `max_output_tokens`. OpenAI now recommends the **Responses API** for reasoning models (Chat Completions still works, but Responses is where new reasoning capabilities land). Cycles translates a thinking-token cap into an effort level and a ceiling: +For OpenAI reasoning models, there is no direct `budget_tokens` knob: the application selects a supported effort level, and reasoning tokens count toward `max_output_tokens`. OpenAI recommends the **Responses API** for reasoning models. Current Cycles does not translate a thinking-token target into an effort level; the application chooses both provider parameters and reserves their estimated cost: ```python res = client.create_reservation(ReservationCreateRequest( @@ -157,9 +141,10 @@ res = client.create_reservation(ReservationCreateRequest( ), )) -caps = res.caps or {} -effort = caps.get("reasoning_effort", "low") -max_output = caps.get("max_output_tokens", 12000) +effort = application_policy.reasoning_effort +max_output = application_policy.max_output_tokens +if res.caps and res.caps.max_tokens is not None: + max_output = min(max_output, res.caps.max_tokens) response = openai.responses.create( model="o3", @@ -184,17 +169,17 @@ client.commit_reservation( ) ``` -The runtime authority layer absorbs the API differences. The agent code stays the same across providers — reserve, enforce caps the server returned, commit actuals. +The reserve-commit lifecycle stays consistent across providers, while the application adapter handles each provider's reasoning parameters and applies any generic `max_tokens` cap returned by Cycles. ## Thinking-to-output ratio as a governance signal -Once you're capturing thinking tokens on every call, a ratio emerges: thinking tokens ÷ visible output tokens. In traces from our own reasoning workloads, healthy calls tend to sit between **2:1 and 8:1** — your distribution will vary by prompt style and model, so measure yours before picking a threshold. Ratios above 15:1 are almost always one of: +Once you're capturing thinking tokens on every call, a ratio emerges: thinking tokens ÷ visible output tokens. There is no universal healthy range: the distribution varies by model, effort setting, task, and prompt. Establish a baseline for each workload and investigate sustained shifts, which can indicate: - A prompt that confuses the model into over-deliberating - A tool description that triggers exhaustive option enumeration - A retry of an ambiguous task that the model can't resolve -Treat high thinking:output ratio as a first-class signal in your [observability setup](/how-to/observability-setup). In Cycles, you can attach the ratio as metadata on the commit and fire a webhook when it exceeds a threshold: +Treat thinking:output ratio as a first-class signal in your [observability setup](/how-to/observability-setup). You can attach it as commit metadata, then have an event subscriber or observability system evaluate workload-specific thresholds: ```python # OpenAI Responses API path; Chat Completions uses @@ -210,16 +195,16 @@ client.commit_reservation( ) ``` -Then alert on `ratio > 15` in your events subscriber. A prompt that keeps producing high ratios is a prompt that needs to be rewritten or a task that needs a smaller model — not a budget that needs raising. +Then alert on a deviation from the measured baseline in your subscriber. A sustained increase is a reason to inspect prompts, task routing, effort settings, and model behavior before changing a budget. ## Concrete takeaway On Monday morning, if your agents use Claude extended thinking, o3, Gemini 2.5 thinking, or DeepSeek R1: 1. **Audit one week of logs** for `usage.output_tokens_details.reasoning_tokens` (OpenAI Responses API; Chat Completions exposes `usage.completion_tokens_details.reasoning_tokens`) or the output/input token ratio (Anthropic). Find your current distribution. -2. **Set a per-call thinking cap** via `budget_tokens` or `reasoning_effort`. Start at the 80th percentile of your current distribution, not the max. -3. **Reserve worst-case, commit actuals.** Your reservation estimate must include thinking tokens at worst-case, or concurrent calls will breach the run-level cap. See the [exposure estimation guide](/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles). -4. **Track thinking:output ratio per prompt template.** The ones above 15:1 are wasted spend, not deep thought. +2. **Choose a per-call reasoning control** supported by that model, then validate cost, quality, and truncation behavior on representative tasks. +3. **Reserve conservatively, commit actuals.** Include possible reasoning usage in the estimate so concurrent calls reserve realistic headroom. Actual usage above the estimate is handled according to the configured commit-overage policy. See the [exposure estimation guide](/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles). +4. **Track thinking:output ratio per model and prompt template.** Investigate deviations from each workload's baseline rather than applying one universal cutoff. Reasoning models moved the governance surface. The budget you enforce now has to see tokens the user never will. That's a runtime authority problem, not a dashboard problem. If you're still relying on `max_tokens` and provider caps, you're enforcing a budget that doesn't know what it's paying for. @@ -231,8 +216,9 @@ Related reading: ## References -- Anthropic: [Extended thinking documentation](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking) — `thinking.budget_tokens`, billing semantics, `max_tokens` > `budget_tokens` requirement -- Anthropic: [Introducing Claude Opus 4.7](https://www.anthropic.com/news/claude-opus-4-7) — adaptive thinking, `output_config.effort`, task budgets +- Anthropic: [Extended thinking documentation](https://platform.claude.com/docs/en/build-with-claude/extended-thinking) — `thinking.budget_tokens`, billing semantics, `max_tokens` > `budget_tokens` requirement +- Anthropic: [Effort](https://platform.claude.com/docs/en/build-with-claude/effort) — current adaptive-thinking and effort support by model +- Anthropic: [Task budgets](https://platform.claude.com/docs/en/build-with-claude/task-budgets) — beta model support and advisory multi-request semantics - OpenAI: [Reasoning guide](https://developers.openai.com/api/docs/guides/reasoning) — Responses API, `reasoning.effort`, `reasoning_tokens` in usage, `max_output_tokens` - Google: [Understand and count tokens (Gemini API)](https://ai.google.dev/gemini-api/docs/tokens) — `thoughts_token_count` returned separately; output pricing includes thinking tokens - DeepSeek: [R1 model card](https://api-docs.deepseek.com/guides/reasoning_model) — `reasoning_content` returned alongside `content`; both billed diff --git a/blog/claude-code-cursor-windsurf-budget-limits-mcp.md b/blog/claude-code-cursor-windsurf-budget-limits-mcp.md index e064f0ee..4ff64cdd 100644 --- a/blog/claude-code-cursor-windsurf-budget-limits-mcp.md +++ b/blog/claude-code-cursor-windsurf-budget-limits-mcp.md @@ -142,7 +142,7 @@ Session starts → cycles_check_balance (remaining: $10.00) For a live, non-dry-run reservation, insufficient budget is an error such as HTTP `409 BUDGET_EXCEEDED`, not a successful response with `DENY`. `DENY` is part of the `decide` and dry-run decision model. The host must handle either form before it invokes the costly tool. -For the six MCP integration patterns (simple reserve/commit, preflight, [graceful degradation](/glossary#graceful-degradation), long-running, fire-and-forget, multi-step), see [Integrating Cycles with MCP](/how-to/integrating-cycles-with-mcp). For per-run and per-conversation budget recipes, see [Common Budget Patterns](/how-to/common-budget-patterns). +For the six MCP integration patterns (simple reserve-commit, preflight, [graceful degradation](/glossary#graceful-degradation), long-running, fire-and-forget, multi-step), see [Integrating Cycles with MCP](/how-to/integrating-cycles-with-mcp). For per-run and per-conversation budget recipes, see [Common Budget Patterns](/how-to/common-budget-patterns). ## Applying Configured Caps diff --git a/blog/computer-use-agents-have-no-tool-boundary.md b/blog/computer-use-agents-have-no-tool-boundary.md index dd30b23a..c9bc3c29 100644 --- a/blog/computer-use-agents-have-no-tool-boundary.md +++ b/blog/computer-use-agents-have-no-tool-boundary.md @@ -206,15 +206,17 @@ A team that can answer "yes" to all seven is running computer-use as an action s ## What Changes When Clicks Are Treated as Actions -The shift is the same one the [memory-writes](/blog/agent-memory-writes-are-actions-too) and [merge](/blog/when-coding-agents-press-merge) extensions made: take the unit the agent actually emits, and apply the action authority lifecycle to it. The lifecycle does not change. What changes is the feature vector the rule body inspects. +This section describes a host architecture that implements the click classifier, application policy, audit records, and required Cycles budget boundary above. Those click semantics are not built into the current Cycles server. -The agent's session has a finite click-authority budget. The 800th `Edit → Save` cycle does not have the same authority as the first one. By the time the schedule's relative-weighting effects accumulate, the agent's authority for a high-blast click has narrowed without anyone hand-tuning a cap. +The shift is the same one the [memory-writes](/blog/agent-memory-writes-are-actions-too) and [merge](/blog/when-coding-agents-press-merge) patterns make: take the unit the agent actually emits and apply application authorization plus a reserve-commit exposure budget to it. The host classifier inspects the click feature vector and assigns the amount that Cycles meters. -The audit trail names the click, not just the eventual database row. Every action the agent took to produce the change is recorded with its (target, intent, context) classification — so when the migration completes and a downstream customer asks "what exactly did the agent do to my record?", the answer is in the runtime decisions, not reconstructed from application logs. +The agent's session has a finite caller-assigned click-exposure budget. Repeated `Edit → Save` cycles consume that budget according to the host's schedule; a later high-exposure click may fail to reserve even though the application still considers the tool permissible. -A/B page changes stop being silent attack surface. A button label that shifts mid-session is exactly the case the freshness cap is for. The agent re-screenshots, the gate re-classifies, the click either re-validates or returns `DENY` with a `reason_code: target_mismatch`. +The application audit trail can name the click, target, intent, classification, authorization decision, and actual browser event. Cycles contributes the correlated budget hold and settlement; it does not record the browser event or reconstruct the action without those application logs. -And the action authority lens stays unified across the corpus. Outbound side effects, memory writes, merge buttons, and clicks all run through the same reserve-commit lifecycle, with the same audit shape and the same three-way decision model. The substrate is uniform; the feature vector changes per surface. +An A/B page change becomes a case for the host's freshness rule. The agent re-screenshots, the host re-classifies the target, and application policy either re-authorizes it or rejects it with an application-defined reason such as `target_mismatch`. This is separate from the protocol's budget denial reason. + +Outbound side effects, memory writes, merge buttons, and clicks can use the same Cycles reserve-commit lifecycle for caller-assigned exposure. Their feature classifiers, action authorization, reason vocabulary, and outcome audit records remain surface-specific application concerns. ## Next Steps diff --git a/blog/cross-platform-ai-agent-governance-salesforce-servicenow.md b/blog/cross-platform-ai-agent-governance-salesforce-servicenow.md index 5bd4f1b1..06f58bfd 100644 --- a/blog/cross-platform-ai-agent-governance-salesforce-servicenow.md +++ b/blog/cross-platform-ai-agent-governance-salesforce-servicenow.md @@ -82,7 +82,7 @@ tenant:acme-corp → $50,000/month total AI budget └─ agent:knowledge-bot → $5,000/month ``` -The `tenant:acme-corp` scope acts as a hard cap across all platforms. Even if individual platform budgets sum to more than $50K, the tenant-level budget prevents collective overspend. This is the existing Cycles hierarchical scope model — no protocol changes required. +The `tenant:acme-corp` ledger acts as a shared reservation ceiling across connectors that submit this tenant scope. Even if individual app allocations sum to more than $50K, atomic reservations cannot collectively claim estimates beyond available tenant capacity. Actual external cost and settlement still depend on integration coverage, estimate quality, and overage policy. This uses the existing Cycles hierarchy; no protocol change is required. ### Two governance dimensions @@ -180,7 +180,7 @@ The Cycles reservation ledger contains the budget lifecycle for actions routed t GET /v1/reservations?tenant=acme-corp&status=COMMITTED ``` -Each entry in the response includes the full context: +Each entry includes the budget-lifecycle context submitted to Cycles: ```json { diff --git a/blog/cycles-budget-guard-hard-enforcement-for-claude-code.md b/blog/cycles-budget-guard-hard-enforcement-for-claude-code.md index de66aafd..3aa8da7d 100644 --- a/blog/cycles-budget-guard-hard-enforcement-for-claude-code.md +++ b/blog/cycles-budget-guard-hard-enforcement-for-claude-code.md @@ -1,5 +1,5 @@ --- -title: "Cycles Budget Guard for Claude Code" +title: "Announcing Cycles Budget Guard for Claude Code" date: 2026-07-22 author: Albert Mavashev tags: [announcement, claude-code, budgets, budget-enforcement, runtime-authority, MCP, security] @@ -13,7 +13,7 @@ head: content: "Claude Code budget enforcement, Cycles Budget Guard, Claude Code hooks, AI agent budgets, runtime authority, MCP budget tools" --- -# Cycles Budget Guard for Claude Code +# Announcing Cycles Budget Guard for Claude Code A prompt injection or hallucinated argument turns a routine Claude Code task into a request for an operation its budget should forbid. The control question is concrete: what stops the tool before execution? diff --git a/blog/cycles-server-performance-benchmarks.md b/blog/cycles-server-performance-benchmarks.md index 3810f454..18db6e43 100644 --- a/blog/cycles-server-performance-benchmarks.md +++ b/blog/cycles-server-performance-benchmarks.md @@ -160,7 +160,7 @@ Every mutation response includes current balance snapshots for all affected scop ### Event emission: async and off the hot path -As of v0.1.25, every reservation deny and commit overage triggers a webhook event. These events are emitted asynchronously via `CompletableFuture.runAsync()` on a dedicated daemon thread pool — they never block the request thread. Redis commands for event storage and subscription lookup are pipelined into a single round-trip. The runtime balance events (budget.exhausted, budget.over_limit_entered, budget.debt_incurred) only inspect the in-memory balance list returned by the Lua script — no additional Redis calls. +As of the current v0.1.25 runtime, a DENY from a dry-run reservation or `/v1/decide`, plus each commit overage, queues an event; live reservation exceptions do not emit `reservation.denied`. `EventEmitterService` uses a dedicated bounded `ThreadPoolExecutor`, so emission does not wait on Redis, but an event can be dropped and logged if the queue is saturated. Redis commands for event storage and subscription lookup are pipelined. The runtime balance events (`budget.exhausted`, `budget.over_limit_entered`, `budget.debt_incurred`) inspect the balance list returned by the Lua operation without an extra balance read. ## How we measure diff --git a/blog/cycles-vs-llm-proxies-and-observability-tools.md b/blog/cycles-vs-llm-proxies-and-observability-tools.md index 84de2f70..96755561 100644 --- a/blog/cycles-vs-llm-proxies-and-observability-tools.md +++ b/blog/cycles-vs-llm-proxies-and-observability-tools.md @@ -14,7 +14,7 @@ head: # Cycles vs LLM Proxies and Observability Tools: Where Budget Enforcement Fits -A platform team runs [autonomous agents](/glossary#autonomous-agent) in production. They have a solid stack: LiteLLM routes model calls across OpenAI and Anthropic with automatic fallback. Langfuse traces every request with per-model cost attribution. Provider caps are set at $10,000 per month as a safety net. +Consider an illustrative platform team running [autonomous agents](/glossary#autonomous-agent) in production. LiteLLM routes model calls across OpenAI and Anthropic with automatic fallback. Langfuse traces requests with per-model cost attribution. Provider caps are set at $10,000 per month as a safety net. The team has not configured a LiteLLM budget policy for this workload. @@ -28,7 +28,7 @@ The provider cap is set at $10,000 per month. It is March 7th. Monthly spend is By Monday morning, the agent has made 4,700 calls and consumed $2,800. The team discovers it on the Langfuse dashboard during their weekly cost review. -Every tool in the stack worked exactly as designed. None of them prevented the overspend. +Every configured control worked as designed. The stack had routing and visibility, but no workload-level limit that matched this agent. The missing layer was not routing or visibility. It was **[runtime authority](/glossary#runtime-authority)** — a pre-execution decision about whether the next action should proceed given the remaining budget. @@ -38,15 +38,13 @@ Most teams building on LLMs end up assembling a stack that addresses three disti | Layer | Question | When it acts | Examples | |---|---|---|---| -| **Routing** | *Which* model handles this call? | Before execution (model selection) | LiteLLM, Portkey | +| **Gateway and routing** | *Which* model handles this call, and does it satisfy gateway policy? | Before execution | LiteLLM, OpenRouter, Helicone | | **Visibility** | *What* happened during this call? | After execution (logging, tracing) | Helicone, Langfuse, LangSmith | -| **Authority** | *Should* this call happen at all? | Before execution (budget check) | Cycles | +| **Application budget authority** | *Does this instrumented operation fit its application-defined budget?* | Before execution | Cycles | Routing and visibility are well-understood layers with mature tooling. -Authority — the pre-execution budget decision — is the layer most teams are missing. - -It is also the only layer that can prevent overspend rather than report it. +Modern gateways can enforce inference budgets and rate limits before a provider call. Cycles addresses a different boundary: reserve-commit accounting across arbitrary operations the application instruments, including work that never traverses an LLM gateway. ## LLM proxies and gateways @@ -68,17 +66,17 @@ Tools like LiteLLM and Portkey solve real problems that teams hit as soon as the These are valuable capabilities. A proxy earns its place in any production LLM stack. -### Where proxies stop +### Where gateway boundaries stop The gap appears when you need to **enforce** a budget, not just **track** spend. **Proxies only see model calls.** An autonomous agent does more than call LLMs. It invokes tools, writes to databases, sends emails, makes API requests, and triggers deployments. A proxy sitting between the app and the model provider has no visibility into these non-LLM actions. If your agent's tool calls cost money — and they often do — the proxy cannot meter them. -**Proxies report after the call completes.** The model call happens. [Tokens](/glossary#tokens) are consumed. The proxy logs the cost. This is useful for dashboards but cannot prevent the call from happening. By the time the proxy records the expense, the money is already spent. +**Current gateways can reject before a provider call.** LiteLLM supports project/user spend management, OpenRouter checks workspace budgets before routing, and Helicone can return `429` for request- or cost-window limits. Those controls are useful and should not be described as post-hoc-only. -**No atomic budget [reservations](/glossary#reservation).** When ten agents share a $100 budget and make concurrent calls, a proxy cannot atomically check-and-decrement the remaining balance. Each call proceeds independently. The total can exceed the budget before any individual call sees the overrun. +**Their accounting model is gateway-specific.** Gateway spend windows govern routed inference. OpenRouter documents that already-dispatched requests can produce slight budget overage. Cycles instead atomically [reserves](/glossary#reservation) a caller estimate before protected work and reconciles the actual amount afterward. -**No hierarchical scopes.** Proxies track spend per API key or per model. They cannot enforce limits at the level your business actually needs: per [tenant](/glossary#tenant), per workspace, per workflow, per run. If three tenants share the same API key, the proxy cannot distinguish their budgets. +**Hierarchy varies by product.** LiteLLM supports multi-tenant spend management per project/user, and OpenRouter supports organization, workspace, member, and key controls. The remaining distinction is not "no hierarchy"; it is whether those gateway identities match application boundaries such as a workflow, an individual run mapped to a workflow ledger, or a non-LLM operation. **No shared budget-degradation signal.** A proxy can route to a cheaper model when the primary is unavailable. A Cycles preflight can return the canonical `ALLOW`, `ALLOW_WITH_CAPS`, or `DENY` decision from configured budget state; the application must interpret any caps and perform the downgrade. @@ -88,14 +86,14 @@ The gap appears when you need to **enforce** a budget, not just **track** spend. |---|---|---| | Model routing and fallback | Yes | No (not its role) | | Unified provider API | Yes | No | -| Cost tracking (post-hoc) | Yes | Yes | -| Pre-execution budget check | No | Yes | +| Cost tracking | Yes | Yes | +| Pre-execution inference limit | Varies; supported by current LiteLLM, OpenRouter, and Helicone gateways | Can protect an instrumented inference call | | Non-LLM action coverage | No | Instrumented tools and APIs | -| Atomic reservations | No | Yes | -| Per-tenant / per-agent scopes | Limited by proxy keys/metadata | Yes | +| Caller-estimated reserve-commit lifecycle | Generally no | Yes | +| Per-tenant / per-agent scopes | Product-specific projects, workspaces, users, keys, and metadata | Subject scopes configured by the application | | [Graceful degradation](/glossary#graceful-degradation) | Partial (model fallback) | Three-way decision; caller applies caps | | Caching | Yes | No | -| Concurrency-safe accounting | No | Yes | +| Concurrency behavior | Product-specific; in-flight overage may be possible | Atomic estimate reservation | ### Using both together @@ -120,7 +118,7 @@ If Cycles allows the reservation, the proxy routes the call as usual. After the **Keep your proxy.** It solves model routing, provider abstraction, and operational resilience. -But do not expect it to govern what an autonomous system is allowed to spend in total. +Use its native spend controls for inference. Add a broader budget boundary when the governed workload also includes non-LLM operations or application scopes the gateway cannot represent. ## Observability platforms @@ -128,7 +126,7 @@ Observability tools give you visibility into what your LLM-powered application i ### What observability does well -Tools like Helicone and Langfuse have become standard in LLM application stacks, and for good reason. +Tools like Langfuse and LangSmith provide dedicated tracing and evaluation. Helicone also offers observability, but its gateway mode includes enforceable custom rate and cost limits, so it spans both categories. **Trace visualization.** See every step of an agent run — each LLM call, tool invocation, and intermediate result — laid out in a timeline. This is invaluable for debugging multi-step agent behavior. @@ -161,7 +159,7 @@ Consider an agent making 100 calls per minute at $0.03 per call: By the time an alert fires and a human responds, the system has already spent. The observability platform reported accurately. It just could not intervene. -**No enforcement mechanism.** An observability tool can tell you "this run has cost $50." It cannot prevent the next call that would push it to $53. There is no hook in the execution path where the observability platform can say "stop." +**Pure tracing does not imply enforcement.** A tracing-only integration can tell you "this run has cost $50" without participating in the next-call decision. A gateway product may add an enforcement hook; Helicone's gateway, for example, supports request- and cost-based limits. **No reservation semantics.** There is no concept of reserving budget before a call and committing actual cost afterward. Observability records what happened. It does not participate in deciding what should happen next. @@ -174,8 +172,8 @@ By the time an alert fires and a human responds, the system has already spent. T | Trace visualization | Yes | No (not its role) | | Cost attribution | Yes | Yes (via hierarchical scopes) | | Prompt debugging | Yes | No | -| Pre-execution budget enforcement | No | Yes | -| Live reservation rejection | No | Yes | +| Pre-execution budget enforcement | No for tracing-only tools; some gateway products add it | Yes for instrumented operations | +| Live reservation rejection | No for tracing-only tools | Yes | | Real-time alerting | Yes | Partial (through events/webhooks) | | Concurrency-safe accounting | No | Yes | | Shadow mode evaluation | Varies by platform | Yes; caller persists responses | @@ -187,7 +185,7 @@ Observability and runtime authority form a feedback loop. **Observability informs budgets.** Trace data shows you what runs actually cost — the distribution of per-run spend, which models drive the most cost, which workflows are bursty. This is how you set accurate budget limits instead of guessing. -**Cycles enforces budgets.** Once you know what runs should cost, Cycles ensures they stay within bounds. Pre-execution reservations prevent overspend. Three-way decisions (ALLOW, ALLOW_WITH_CAPS, DENY) enable degradation instead of hard failure. +**Cycles enforces submitted budget estimates on instrumented paths.** Atomic reservations prevent concurrent holds from oversubscribing matching ledgers. The host must make the boundary mandatory, estimate conservatively, settle actual usage, and apply any configured caps or fallback. **Together, they close the loop.** Observability shows patterns. Cycles enforces limits. When Cycles denies a request, that event appears in your observability traces — giving you visibility into enforcement decisions, not just execution results. @@ -220,7 +218,7 @@ Remove any one of these and a gap appears: - Without a proxy, you manage provider differences manually and lose fallback routing. - Without observability, you cannot debug, optimize, or understand cost trends. - Without provider caps, you have no last-resort safety net. -- Without Cycles, you have no pre-execution budget enforcement. Autonomous agents can spend without limit until a human intervenes or a monthly cap triggers. +- Without a matching pre-execution limit, any operation outside the gateway—or outside its configured identity and budget model—can continue until another control intervenes. These layers do not compete with each other. They solve different problems at different points in the execution lifecycle. @@ -228,14 +226,16 @@ The question is not "which one should I use?" It is "which layer is missing?" -For most teams running autonomous agents, the missing layer is runtime authority. +The missing layer is whichever boundary your current controls do not cover. For inference-only workloads, a configured gateway budget may be sufficient. For workflows that span providers and non-LLM tools, an application-level reserve-commit boundary can fill the gap. + +Feature claims were rechecked on July 24, 2026 against [LiteLLM's gateway documentation](https://docs.litellm.ai/), [OpenRouter workspace budgets](https://openrouter.ai/docs/guides/features/workspaces/workspace-budgets), and [Helicone custom rate limits](https://docs.helicone.ai/features/advanced-usage/custom-rate-limits). ## Next steps - [What Is Runtime Authority for AI Agents?](/blog/what-is-runtime-authority-for-ai-agents) — the foundational explainer for runtime authority as a concept - [From Observability to Enforcement](/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority) — the maturity curve from dashboards to pre-execution budget decisions - [How Cycles Compares](/concepts/how-cycles-compares-to-rate-limiters-observability-provider-caps-in-app-counters-and-job-schedulers) — full capability matrix across rate limiters, observability, provider caps, in-app counters, and job schedulers -- [Cycles vs Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps) — why monthly limits and delayed enforcement create blind spots +- [Cycles vs Provider Cost Controls](/concepts/cycles-vs-provider-spending-caps) — how vendor budgets, credits, and quotas differ from application-scoped runtime budgets - [The True Cost of Uncontrolled AI Agents](/blog/true-cost-of-uncontrolled-agents) — real-world costs of running agents without budget limits - [5 AI Agent Failures Budget Controls Would Prevent](/blog/ai-agent-failures-budget-controls-prevent) — concrete failure scenarios with dollar math - [AI Agent Cost Management: The Complete Guide](/blog/ai-agent-cost-management-guide) — the five-tier maturity model from no controls to hard enforcement diff --git a/blog/deploying-cycles-events-service-at-scale.md b/blog/deploying-cycles-events-service-at-scale.md index f3bbcbf5..f539e2bf 100644 --- a/blog/deploying-cycles-events-service-at-scale.md +++ b/blog/deploying-cycles-events-service-at-scale.md @@ -280,7 +280,7 @@ The management-port split isn't a Cycles quirk. It's the same pattern Envoy, mat - [Webhook Idempotency Patterns for AI Agent Budget Events](/blog/webhook-idempotency-patterns-for-ai-agent-budget-events) — receiver-side dedup patterns that complement the sender-side reliability described here - [Operational Runbook: Using Cycles Runtime Events](/blog/operational-runbook-using-cycles-runtime-events) — severity tiers and on-call triage patterns for the events this service delivers -- [Real-Time Budget Alerts for AI Agents](/blog/real-time-budget-alerts-for-ai-agents) — the event-system architecture motivation and the 45 event types flowing through it +- [Real-Time Budget Alerts for AI Agents](/blog/real-time-budget-alerts-for-ai-agents) — the event-system architecture motivation and the historical event vocabulary behind it - [Monitoring and Alerting](/how-to/monitoring-and-alerting) — additional Prometheus alert rules across Cycles services (Redis, reservation denial-rate, overdraft) - [Production Operations Guide](/how-to/production-operations-guide) — Redis HA, multi-instance Cycles server, capacity planning - [Security — Webhook security](/security#webhook-security) — HMAC signing, signing-secret encryption at rest, SSRF protection diff --git a/blog/estimate-drift-silent-killer-of-enforcement.md b/blog/estimate-drift-silent-killer-of-enforcement.md index 1316e98f..caa566ef 100644 --- a/blog/estimate-drift-silent-killer-of-enforcement.md +++ b/blog/estimate-drift-silent-killer-of-enforcement.md @@ -3,7 +3,7 @@ title: "Estimate Drift in Budget Enforcement" date: 2026-04-07 author: Albert Mavashev tags: [operations, production, observability, runtime-authority, incident-response, calibration] -description: "Cost estimates drift in AI agent production. When reserve:commit ratios wander outside 0.8-1.2, budgets lie. Detect drift early and recalibrate safely." +description: "Detect AI agent estimate drift by comparing reservations with actual usage, segmenting changes by workload, and recalibrating without masking budget intent." head: - - meta - name: keywords @@ -17,7 +17,7 @@ featured: false > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -You calibrated your budgets correctly. You ran shadow mode for two weeks. You chose enforcement thresholds based on real data. Enforcement went live and worked. +You calibrated your budgets against representative traffic, chose enforcement thresholds from the observed data, and moved protected paths to live reservations. Then three months later, something changes: your `reservation.commit_overage` events start climbing. In overdraft-tolerant setups, debt may begin to accumulate; in capped-charge setups, scopes may start drifting toward `is_over_limit`. A workflow that used to run comfortably starts triggering `budget.over_limit_entered`. Nobody deployed anything. Nobody changed the budgets. Nothing obvious broke. @@ -35,7 +35,7 @@ Over time, four forces push estimates away from reality: **1. Context growth.** An agent that reserves 500 tokens for an LLM call on day one may need 4,000 tokens on day ninety as conversation history, retrieved documents, and tool outputs accumulate. The estimate formula was right for the small context — but the context grew. -**2. Model behavior shifts.** Provider-side model updates change output verbosity, reasoning depth, and token consumption patterns. OpenAI [warns about this explicitly](https://developers.openai.com/cookbook/examples/how_to_count_tokens_with_tiktoken): *"The exact way that tokens are counted from messages may change from model to model. Consider the counts from the function below an estimate, not a timeless guarantee."* +**2. Model behavior shifts.** Provider-side model updates can change output verbosity, reasoning depth, and token consumption patterns. OpenAI's [token-counting guidance](https://developers.openai.com/cookbook/examples/how_to_count_tokens_with_tiktoken) cautions that message-token calculations vary by model and should be treated as estimates. **3. Tool path variance.** The same agent workflow can take different paths through its tool set depending on input. If users start asking more complex questions, the agent starts making more tool calls per run — but the estimate hasn't caught up. @@ -45,25 +45,24 @@ None of these forces fire alarms. They just slowly shift the ratio between what ## The Reserve:Commit Ratio -The single most useful signal for estimate drift is the **reserve-to-commit ratio**: how much budget you reserved divided by how much you actually spent, measured over a window. +One useful signal for estimate drift is the **reserve-to-commit ratio**: total submitted estimates divided by total actual usage for completed reservations over a window. | Ratio | Meaning | What's happening | |---|---|---| -| **> 2:1** | You're reserving 2x what you spend | Estimates are too high — false scarcity, unnecessary denials | -| **1.2 - 2:1** | Moderately over-estimated | Tighten estimates to recover capacity | -| **0.8 - 1.2:1** | Estimates are accurate | The target range | -| **< 0.8:1** | Estimates are too low | Commits consistently exceed reserves — overage events, drift toward over-limit | +| **Consistently above 1** | Estimates exceed actual usage | The buffer may be intentional, but excessive holds can reduce concurrent headroom or deny calls whose likely actual would fit | +| **Near 1** | Aggregate estimates are close to actual usage | Inspect the distribution because over- and under-estimates can cancel out | +| **Consistently below 1** | Actual usage exceeds estimates | Commit overages are likely; the configured overage policy determines whether they reject, cap the charge, or accrue debt | -The [operator's guide](/blog/operating-budget-enforcement-in-production) defines 0.8-1.2 as the ideal range and 0.8-2.0 as the operationally acceptable watch range. This post focuses on the *why* behind ratio drift over weeks and months; the operator's guide covers *what to do* when enforcement fires in the moment. +There is no implementation-defined target band. Set a tolerance per workload from its variance, concurrency needs, and desired safety margin. The [operator's guide](/blog/operating-budget-enforcement-in-production) covers what to do when enforcement fires in the moment. -**One measurement note:** compute the ratio against **actual spend**, not the charged amount. In capped-charge setups (`ALLOW_IF_AVAILABLE`), the charged amount can be less than actual when overage is capped — using charged amounts would mask under-estimation drift. +**Measurement notes:** compute the ratio against **actual usage**, not the charged amount. In capped-charge setups (`ALLOW_IF_AVAILABLE`), the charged amount can be less than actual when overage is capped, which would mask under-estimation drift. Use completed reservation records or application telemetry; the point-in-time balance fields `reserved` and `spent` do not form this ratio. The subtle thing about drift is that it can happen in either direction: -- **Over-estimation drift** (ratio climbing above 1.2): Your budgets appear to deplete faster than actual spend justifies. Agents hit denials before they've really used their allocation. Teams respond by raising budgets — which masks the problem instead of fixing it. -- **Under-estimation drift** (ratio falling below 0.8): Commits exceed reserves. `reservation.commit_overage` events fire. In overdraft-tolerant setups, debt may accumulate; in capped-charge setups, scopes can drift toward `is_over_limit`. Eventually you hit `budget.over_limit_entered` in production, with no budget change to blame. +- **Over-estimation drift** (ratio rising above its workload baseline): Active reservations hold more headroom than their work tends to consume, and a submitted estimate can be denied even when the eventual actual would have fit. Raising budgets may mask the estimator problem. +- **Under-estimation drift** (ratio falling below its workload baseline): Commits exceed reserves and `reservation.commit_overage` events fire. In overdraft-tolerant setups, debt may accumulate; in capped-charge setups, a scope is marked `is_over_limit` when the full overage cannot be charged. -Both failure modes start the same way: a ratio that wanders out of the goldilocks zone and stays there. +Both failure modes appear as sustained movement away from the tolerance chosen for that workload. ## Drift Detection: Catching Problems Before Production Incidents @@ -73,22 +72,16 @@ Drift detection is a monitoring problem, not an alerting problem. You're watchin This is the most direct under-estimation signal. Every time actual cost exceeds reserved estimate on a commit, Cycles fires a `reservation.commit_overage` event. Track the rate over time: -As a rule of thumb based on the same calibration logic as denial-rate thresholds: - -- **Healthy:** < 1% of commits fire overage -- **Warning:** 1-5% of commits fire overage — investigate specific workflows -- **Drift:** > 5% sustained for a week — recalibrate estimates - -The rate matters more than individual events. A single overage is an edge case. A rising rate is drift. Calibrate these thresholds to your own production baseline — what matters is the *trend*, not the exact percentage. +Compare the rate with the workload's calibrated baseline and segment it by model, workflow, and tenant. A rising rate is evidence that the input distribution or estimator changed; an individual overage may simply be expected variance. Set alerts from your own tolerance rather than a universal percentage. ### Signal 2: Reserve:commit ratio drift over time -Plot the ratio weekly. Look for trend, not noise: +Plot the ratio at a window appropriate to your traffic volume. Look for trend, not isolated noise: - Ratio held steady at 1.05 for three months, then started climbing to 1.4 → over-estimation drift emerging - Ratio held at 0.95 for two months, then dropped to 0.75 → under-estimation drift, overage events incoming -Drift happens at the timescale of weeks. Daily fluctuations are noise. +High-volume workloads can reveal drift quickly; low-volume or seasonal workloads need longer comparison windows. Choose a window that contains enough completed reservations to be representative. ### Signal 3: Per-entity drift segmentation @@ -103,18 +96,18 @@ A 1.1 overall ratio can hide a 0.7 ratio on one specific workflow that's heading ### Signal 4: Budget utilization trajectory -If shadow mode showed you'd hit denials ~2% of the time, and live enforcement is now denying 5%, with the same budgets and same workload volume — something estimated differently. Either your budgets drifted, your estimates drifted, or the workload drifted. The ratio tells you which. +If application-side dry-run records established one denial baseline and live enforcement now denies materially more often, compare policy changes, scope mapping, workload mix, and estimate ratios. The ratio helps identify estimate movement, but cannot by itself distinguish every cause. ## Recalibrating Without Breaking Production Detecting drift is half the battle. The other half is updating estimates without causing a new incident. Two patterns: -### Pattern 1: Gradual estimate migration (safe default) +### Pattern 1: Gradual estimate migration Don't change estimate formulas abruptly. Instead: -1. **Observe the new target** in shadow mode. Compute what your new estimate formula *would* have produced for the last week of production traffic. -2. **Compare shadow estimates to live actuals.** If shadow estimates are closer to actuals than current estimates, you have your new target. +1. **Evaluate the candidate formula side by side.** Compute what it would have produced over a representative set of production traffic. +2. **Compare candidate estimates to actuals.** If the distribution improves without removing the safety margin you require, the formula is a candidate for rollout. 3. **Roll out per scope.** Apply the new estimate to one workflow, watch the reserve:commit ratio, expand to others if it stabilizes. 4. **Watch `commit_overage` rate** during rollout. Spikes mean your new estimate is still wrong. @@ -125,14 +118,14 @@ This is the estimate-update equivalent of shadow mode itself: observe first, enf If drift is small but persistent, sometimes the fix is adjusting the safety buffer rather than the core formula. - Current formula: `estimate = predicted_tokens * cost_per_token * 1.2` (20% buffer) -- Drift shows actuals consistently 30% above estimates -- Adjusted formula: `estimate = predicted_tokens * cost_per_token * 1.4` (40% buffer) +- Analysis shows the prediction component remains useful but the chosen buffer no longer covers the desired percentile +- Adjust the multiplier to the value measured for that percentile, then validate it against held-out traffic -Buffer adjustments are easier to roll out than formula rewrites — they preserve the logic of the estimate while giving it more headroom. +A buffer adjustment preserves the prediction logic, but it still needs validation: a broad multiplier can hide a model- or workflow-specific error. ### Anti-pattern: Raising budgets to absorb drift -The most common wrong move: when overage events start climbing, raise the budget so the warnings stop. This is the same mistake as raising a capacity budget when your app has a memory leak. It hides the drift. It doesn't fix it. And it reduces the value of enforcement, because the budget is no longer representing actual intent — it's absorbing calibration error. +A tempting response to rising overage events is to raise the budget so the warnings stop. That hides estimate drift instead of fixing it and weakens the connection between the ledger and the exposure the operator intended to allow. Budgets should track *what you want to spend*. Estimates should track *what you actually spend*. When those diverge, fix the estimate. Raising the budget to paper over drift just guarantees a bigger drift-driven incident later. @@ -140,19 +133,19 @@ Budgets should track *what you want to spend*. Estimates should track *what you Drift rate varies by workload. Set a cadence based on your signal frequency: -| Signal frequency | Recommended cadence | +| Workload behavior | Example review cadence | |---|---| -| Workload changes weekly (fast iteration) | Review ratios weekly, recalibrate monthly | -| Workload changes monthly (stable) | Review ratios monthly, recalibrate quarterly | -| Workload changes rarely (mature system) | Review quarterly, recalibrate when drift signal fires | +| Changes frequently or after most releases | Review after material releases and over a representative traffic window | +| Stable but seasonal | Review across comparable seasonal periods | +| Changes rarely | Review when a drift alert, model change, or pricing change fires | -**Don't skip cadence entirely.** Even stable systems drift — provider pricing shifts, model updates happen, user input complexity evolves. An enforcement system you haven't re-examined in six months is probably operating on stale assumptions. +Even stable systems can drift as provider pricing, model behavior, or user input changes. Tie review to those changes and to a recurring interval appropriate for your workload. ## The Take Estimate drift is the failure mode that turns well-calibrated enforcement into false-positive theater or silent debt accumulation. It's not dramatic — no single event triggers it — which is why it's easy to ignore until it causes an incident. -The defense is continuous ratio monitoring, segmented by the dimensions that matter for your workload (model, tool, workflow, tenant). The reserve:commit ratio is the leading indicator. The `reservation.commit_overage` event is the confirmation signal. Staying in the 0.8-1.2 band is the goal. +The defense is continuous ratio monitoring, segmented by the dimensions that matter for your workload (model, tool, workflow, tenant). The reserve-to-commit ratio shows estimate movement, while `reservation.commit_overage` confirms individual under-estimates. The goal is to remain within a workload-specific tolerance, not a universal band. And when drift appears, recalibrate *estimates*, not *budgets*. Estimates track reality. Budgets track intent. If you raise the budget every time estimates drift, the budget stops meaning anything. diff --git a/blog/eu-ai-act-what-actually-happens-august-2-2026.md b/blog/eu-ai-act-what-actually-happens-august-2-2026.md index 87262383..d3fb146f 100644 --- a/blog/eu-ai-act-what-actually-happens-august-2-2026.md +++ b/blog/eu-ai-act-what-actually-happens-august-2-2026.md @@ -15,7 +15,7 @@ head: # EU AI Act: What Actually Happens on August 2, 2026 -If your compliance roadmap says "EU AI Act high-risk obligations apply August 2, 2026," it is out of date as of two weeks ago — and if your reaction is to shelve the whole workstream until 2027, that is a misread in the other direction. +If your compliance roadmap says "EU AI Act high-risk obligations apply August 2, 2026," the Council's June 29, 2026 approval of the Digital Omnibus changed that timeline—and shelving the whole workstream until 2027 would be a misread in the other direction. On June 29, 2026, the Council of the EU gave final approval to the Digital Omnibus on AI, following the European Parliament's endorsement on June 16. The package rewrites the AI Act's application timeline: the high-risk obligations that were scheduled for this August moved to late 2027 and 2028. But two things still take effect on August 2, 2026 — Article 50 transparency obligations, which the Commission's own draft guidance says apply to AI agents, and the Commission's enforcement powers over general-purpose AI (GPAI) providers, including fines of up to 3% of worldwide turnover. diff --git a/blog/every-local-first-agent-runtime-needs-budget-authority.md b/blog/every-local-first-agent-runtime-needs-budget-authority.md index 2291c083..110c341d 100644 --- a/blog/every-local-first-agent-runtime-needs-budget-authority.md +++ b/blog/every-local-first-agent-runtime-needs-budget-authority.md @@ -51,24 +51,24 @@ Take the four controls a CTO would reach for first, and walk through why each on ### Provider-side spending caps -Provider controls are improving. Anthropic supports organization- and workspace-level limits — its Tier 4 ceiling is [$200,000/month](https://docs.anthropic.com/en/api/rate-limits), with finer-grained limits configurable per workspace, and the API blocks further calls once the monthly cap is hit until the next billing period. OpenAI supports organization- and project-level [spending limits](https://help.openai.com/en/articles/9186755-managing-your-work-in-the-api-platform-with-projects) in the dashboard. Both are useful safety nets against catastrophic monthly bills. +Provider controls are improving, but their semantics differ. Anthropic uses prepaid usage credits, usage tiers, workspace attribution, and rate limits; exhausted credits stop API access unless the balance is reloaded. OpenAI offers soft spend alerts and optional hard monthly limits at organization and project scope; it documents that hard-limit enforcement is not instantaneous and tracked spend can slightly exceed the amount. Prepaid-credit exhaustion is a separate control. These controls are useful, but none should be described as a universal per-session boundary. But four structural mismatches keep them blind to the local-first failure mode: -- **The cap measures against the calendar, not against the run.** A monthly cap of $500 doesn't stop a single-session research loop that burns $187 in eight hours. The cap is checked against accumulated month-to-date spend, not against a per-session or per-task budget. +- **Provider windows and balances are not application runs.** A monthly threshold, prepaid balance, or throughput interval does not automatically express a per-session or per-task budget. - **BYOK distributes the spend across accounts.** When developers use truly personal accounts (signing up with personal email, paying on a personal card), each engineer's key sits on their own billing boundary and the provider's cap sees one engineer's monthly spend — never the team's. Anthropic and OpenAI [do support shared organization workspaces](https://support.claude.com/en/articles/9796807-creating-and-managing-workspaces-in-the-claude-console) where billing admins can see spend across keys, which closes the cross-account aggregation gap *inside the org*. The cross-session, cross-runtime, and per-action governance gaps below still apply either way. -- **The granularity is account- or workspace-wide, not session-wide.** The cap can't say "let this user burn $187 on background research but block deploy-related tools." It can only say "stop everything when the month's spend hits X." +- **The granularity is provider-defined, not automatically session-defined.** Projects, workspaces, keys, and cloud projects can isolate some traffic, but shared credentials do not infer a local editor session or distinguish background research from a deploy tool. - **They don't see action risk at all.** A provider cap is a *cost* protector. It has no concept of "this tool deletes data" vs "this tool reads files" — it can't deny a `deploy` call while allowing twenty `read_file` calls. The deploy that took down staging in the opening vignette was perfectly happy from the provider's point of view; the provider doesn't know what the tool *does*, only what the model call *costs*. The risk-tier framing is in [AI Agent Risk Assessment](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk). Provider controls primarily protect usage within the provider's account model. They aren't designed to govern an organization's cross-user, cross-runtime, cross-session, cross-provider surface, and they don't make per-action decisions on either spend or risk. A longer treatment of the granularity / scope / delay analysis is in [Cycles vs Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps). ### Framework-internal limits -Each runtime ships its own internal ceilings. Aider has token budgets per request and chat-mode session settings. Cline has a max-requests-per-task setting. Continue offers per-assistant configuration. These are useful for the simplest runaway shape — an agent stuck calling the same tool forever. +Some runtimes expose local request, context, approval, or configuration controls. Those can help with the simplest runaway shape, such as one process repeatedly calling a tool. -The structural problem: each ceiling is **per process, on one machine**. A user can spawn three Cline windows. A user can run two Aider terminals at once. A user can switch from Continue to Cline mid-day and reset every counter. A worker crash drops the counter. A user reboots and starts fresh. There is no shared truth about how much this *user* — let alone this *team* — has spent today. +The structural problem appears when such a ceiling is stored only in one local process. Multiple editor windows, terminals, runtimes, or restarted workers then do not share one durable accounting state. Cloud-managed runtime features may close part of that gap for their own product, but they still do not automatically aggregate every local agent and provider. -The limits also speak in step counts, not in money or risk. They don't know what an iteration costs. A `max_requests=50` runs $0.50 on Haiku and $50 on Opus. They don't distinguish "read a file" from "deploy a service" — every step is one step, regardless of blast radius. They are local circuit breakers, not [budget authority](/glossary#budget-authority) and not [action authority](/glossary#action-authority). The same shortcoming was named — for hosted-orchestrator frameworks — in [Agents Are Cross-Cutting. Your Controls Aren't.](/blog/agents-are-cross-cutting-your-controls-arent#framework-limits) The local-first version of the failure is the same failure with the additional twist that *the orchestrator runs in the user's process*, so even the per-process counter is split across however many editor windows the user opens. +Step or request limits also do not necessarily represent money or caller-assigned action exposure: two model calls can have different prices, and a file read has a different consequence from a deploy. Local circuit breakers remain useful, but a deployment needs shared state if it wants a cross-process [budget authority](/glossary#budget-authority), plus host authorization if it wants [action authority](/glossary#action-authority). The same boundary issue appears in hosted orchestrators, as described in [Agents Are Cross-Cutting. Your Controls Aren't.](/blog/agents-are-cross-cutting-your-controls-arent#framework-limits). ### Observability and cost-attribution tools @@ -93,7 +93,7 @@ Each control above fails for a different reason. The reasons share a shape: | Control | What it can see | What it can't see | |---|---|---| -| Provider cap | Account/workspace-month spend | Per-session, per-tool, per-team, per-action-risk decisions | +| Provider controls | Vendor account/project/workspace usage | Cross-provider sessions, application tools, and action-risk decisions | | Framework limit | One process | Other processes, other users, other runtimes | | Observability | Past traces (if proxied) | Pre-execution decisions | | Marketplace | Published skills | Per-runtime per-skill blast radius | diff --git a/blog/first-budget-to-put-on-a-production-agent.md b/blog/first-budget-to-put-on-a-production-agent.md index 4cb7a094..eb666460 100644 --- a/blog/first-budget-to-put-on-a-production-agent.md +++ b/blog/first-budget-to-put-on-a-production-agent.md @@ -156,7 +156,7 @@ Use this rule for the first production budget: Then add the next layer once the first one proves useful. -The end state may include tenant, workspace, workflow, run, agent, and toolset budgets. The starting point should be narrower: one meaningful control, one measurable failure mode, one denial path operators understand. +The end state may include tenant, workspace, app, workflow, agent, and toolset budgets. A workflow ledger can be keyed per run when each execution needs a separate envelope. The starting point should be narrower: one meaningful control, one measurable failure mode, one denial path operators understand. ## Resource links diff --git a/blog/how-much-do-ai-agents-cost.md b/blog/how-much-do-ai-agents-cost.md index 1e2bccb5..51734ca1 100644 --- a/blog/how-much-do-ai-agents-cost.md +++ b/blog/how-much-do-ai-agents-cost.md @@ -3,7 +3,7 @@ title: "How Much Do AI Agents Actually Cost?" date: 2026-03-13 author: Cycles Team tags: [costs, agents, guide] -description: "AI agent cost breakdown across OpenAI, Anthropic, Google, and AWS Bedrock — with real-world scenarios for support bots, coding agents, and data pipelines." +description: "AI agent cost breakdown across OpenAI, Anthropic, and Google, with current token rates and illustrative support, coding, and data-pipeline scenarios today." blog: true sidebar: false head: @@ -16,46 +16,46 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -A team we talked to recently launched their first production agent — a customer support bot running on GPT-4o. They estimated $800/month based on their prototype traffic. The first invoice came in at $4,200. The model pricing was exactly what they expected. The number of calls was not. Their agent averaged 11 LLM calls per conversation, not the 3 they'd assumed. Context windows grew with each turn. Retries on tool failures doubled the call count on bad days. The per-token price was never the problem. The per-agent price was. +Consider an illustrative customer-support agent running on GPT-4o. A prototype-based estimate puts it at $800 per month; production-shaped assumptions put it at $4,200. The model rate is unchanged. The scenario reaches the higher total because it assumes 11 LLM calls per conversation instead of three, growing contexts, and additional retries on tool failures. The per-token price is not the only variable; the execution pattern is. > **Recreate the "estimated $800, actual $4,200" scenario in the calculator:** [Open with these numbers pre-loaded →](/calculators/claude-vs-gpt-cost-standalone#s=eyJ3b3JrbG9hZE5hbWUiOiJDdXN0b21lciBzdXBwb3J0IGJvdCIsIndvcmtsb2FkRGVzY3JpcHRpb24iOiIxMSBMTE0gY2FsbHMgcGVyIGNvbnZlcnNhdGlvbi4gQ29udGV4dCB3aW5kb3dzIGdyb3cgd2l0aCBlYWNoIHR1cm4uIEVzdGltYXRlZCAkODAwL21vLCBhY3R1YWwgJDQsMjAwLiIsImlucHV0VG9rZW5zIjo1MDAwLCJvdXRwdXRUb2tlbnMiOjEyMDAsImNhbGxzUGVyRGF5IjozMzAwfQ) -This post is a reference guide. We break down current per-token pricing across the major providers, then show what those prices actually mean when you multiply by the call patterns of real agent workloads. If you're planning a budget for an agent deployment — or trying to understand why your current one costs more than expected — this is the data you need. +This post is a reference guide. We break down representative per-token pricing across major providers, then show what those prices mean under illustrative agent workload assumptions. Pricing was verified against provider documentation on **July 24, 2026**; recheck the linked provider pages before making a purchasing decision. ## Per-Token Pricing by Provider All prices below are per 1 million [tokens](/glossary#tokens). Every provider charges separately for input tokens (what you send) and output tokens (what the model generates). Agents are output-heavy relative to simple completions, because they generate tool calls, reasoning chains, and structured responses. -### OpenAI +### [OpenAI](https://developers.openai.com/api/docs/models/gpt-4o) | Model | Input (per 1M tokens) | Output (per 1M tokens) | Notes | |---|---|---|---| | gpt-4o | $2.50 | $10.00 | Flagship multimodal model | | gpt-4o-mini | $0.15 | $0.60 | Cost-optimized for high-volume | -| gpt-4.1 | $2.00 | $8.00 | Latest generation | +| gpt-4.1 | $2.00 | $8.00 | General-purpose model with long context | | gpt-4.1-mini | $0.40 | $1.60 | Balanced cost/capability | | o3 | $2.00 | $8.00 | Reasoning model | | o4-mini | $1.10 | $4.40 | Compact reasoning model | -### Anthropic +### [Anthropic](https://platform.claude.com/docs/en/about-claude/pricing) | Model | Input (per 1M tokens) | Output (per 1M tokens) | Notes | |---|---|---|---| -| Claude Opus 4 | $15.00 | $75.00 | Highest capability | -| Claude Sonnet 4 | $3.00 | $15.00 | Strong general-purpose | -| Claude Haiku 3.5 | $0.80 | $4.00 | Fast and cost-efficient | +| Claude Opus 4.8 | $5.00 | $25.00 | Premium Opus tier | +| Claude Sonnet 5 | $2.00 | $10.00 | Introductory rate through August 31, 2026; $3/$15 afterward | +| Claude Haiku 4.5 | $1.00 | $5.00 | Fast, lower-cost tier | -### Google +### [Google](https://ai.google.dev/gemini-api/docs/pricing) | Model | Input (per 1M tokens) | Output (per 1M tokens) | Notes | |---|---|---|---| | Gemini 2.5 Pro | $1.25 | $10.00 | Advanced reasoning | -| Gemini 2.5 Flash | $0.15 | $0.60 | Optimized for throughput | -| Gemini 2.0 Flash | $0.10 | $0.40 | Lowest cost option | +| Gemini 2.5 Flash | $0.30 | $2.50 | Hybrid reasoning and throughput | +| Gemini 2.5 Flash-Lite | $0.10 | $0.40 | Lower-cost high-volume option | -A quick observation: the spread between cheapest and most expensive is enormous. Gemini 2.0 Flash output costs $0.40 per million tokens. Claude Opus 4 output costs $75.00 per million tokens. That's a 187x difference. Model selection is the single biggest lever you have on agent costs — but only if your agent architecture actually lets you swap models without breaking functionality. +A quick observation: even within this small representative set, output pricing ranges from $0.40 per million tokens for Gemini 2.5 Flash-Lite to $25 for Claude Opus 4.8—a 62.5x spread. Model selection is a major cost lever, but only if the cheaper model still meets the workload's quality, latency, and tool-use requirements. ## Why Agents Cost More Than You Think @@ -87,7 +87,7 @@ Multi-agent architectures multiply everything. A coordinator dispatching to 5 su ## Real-World Cost Scenarios -Here's what agents actually cost in four common deployments. All estimates use a blended rate of 3,000 input tokens and 1,500 output tokens per call, which is conservative for production agent workloads. +Here is what four common deployment shapes cost under explicit assumptions. These are planning examples, not measured customer bills; your token mix, caching, tools, retries, and negotiated pricing will change the result. ### Scenario 1: Customer support bot @@ -105,12 +105,12 @@ A support bot handling customer questions — looking up orders, checking polici | Model | Cost per call | Daily cost | Monthly cost | |---|---|---|---| | gpt-4o | $0.018 | $21.60 | $648 | -| gpt-4o-mini | $0.001 | $1.20 | $36 | -| Claude Sonnet 4 | $0.035 | $42.00 | $1,260 | -| Claude Haiku 3.5 | $0.009 | $10.80 | $324 | -| Gemini 2.5 Flash | $0.002 | $2.40 | $72 | +| gpt-4o-mini | $0.00108 | $1.30 | $38.88 | +| Claude Sonnet 5 | $0.016 | $19.20 | $576 | +| Claude Haiku 4.5 | $0.008 | $9.60 | $288 | +| Gemini 2.5 Flash | $0.0032 | $3.84 | $115.20 | -The spread is dramatic. The same support bot costs $36/month on gpt-4o-mini or $1,260/month on Claude Sonnet 4. The capability difference matters — but so does a 35x cost difference. +Under these assumptions, the same support bot costs about $39 per month on gpt-4o-mini or $576 on Claude Sonnet 5 at its introductory rate. That spread is material, but it is not a quality-adjusted comparison. ### Scenario 2: Coding agent @@ -128,11 +128,11 @@ An agent that reads codebases, generates changes, runs tests, and iterates on fa |---|---|---|---| | gpt-4o | $0.035 | $43.75 | $1,313 | | gpt-4.1 | $0.028 | $35.00 | $1,050 | -| Claude Sonnet 4 | $0.048 | $60.00 | $1,800 | -| Claude Opus 4 | $0.240 | $300.00 | $9,000 | +| Claude Sonnet 5 | $0.032 | $40.00 | $1,200 | +| Claude Opus 4.8 | $0.080 | $100.00 | $3,000 | | o3 | $0.028 | $35.00 | $1,050 | -Coding agents on Claude Opus 4 cost $9,000/month at this volume. That's not a bug in the pricing — it's a reflection of running a premium model at agent-scale call volumes. Most teams use Opus for the hardest subtasks and a cheaper model for routine steps. +At this volume, the illustrative Claude Opus 4.8 workload costs $3,000 per month. That reflects the combination of a premium model and agent-scale call volume. Routing only the hardest subtasks to a premium model can reduce the blended rate. ### Scenario 3: Data pipeline agent @@ -148,13 +148,13 @@ An agent that processes documents — extracting data, classifying content, gene | Model | Cost per call | Daily cost | Monthly cost | |---|---|---|---| -| gpt-4o-mini | $0.001 | $2.10 | $63 | +| gpt-4o-mini | $0.00075 | $2.25 | $67.50 | | gpt-4.1-mini | $0.002 | $6.00 | $180 | -| Gemini 2.0 Flash | $0.001 | $1.50 | $45 | -| Gemini 2.5 Flash | $0.001 | $1.95 | $59 | -| Claude Haiku 3.5 | $0.004 | $12.00 | $360 | +| Gemini 2.5 Flash-Lite | $0.0005 | $1.50 | $45 | +| Gemini 2.5 Flash | $0.00215 | $6.45 | $193.50 | +| Claude Haiku 4.5 | $0.0055 | $16.50 | $495 | -High-volume, low-complexity pipelines are where the mini and flash models shine. Gemini 2.0 Flash processes 1,000 documents per day for $45/month. The same pipeline on a frontier model would cost 20-100x more with marginal quality improvement for structured extraction tasks. +High-volume, repeatable pipelines are where lower-cost models can materially change unit economics. Under this example, Gemini 2.5 Flash-Lite processes 1,000 documents per day for $45 per month. Measure extraction quality on your own data before routing solely on price. ### Scenario 4: Multi-agent workflow @@ -173,10 +173,10 @@ A coordinator agent dispatches work to specialized sub-agents — a planner, a r |---|---|---|---| | gpt-4o | $0.028 | $44.00 | $1,320 | | gpt-4.1 | $0.022 | $35.20 | $1,056 | -| Claude Sonnet 4 | $0.038 | $60.00 | $1,800 | -| Mixed (Sonnet coordinator + Haiku workers) | $0.015 avg | $24.00 | $720 | +| Claude Sonnet 5 | $0.025 | $40.00 | $1,200 | +| Mixed (Sonnet 5 coordinator + Haiku 4.5 workers) | $0.015 avg | $24.00 | $720 | -The "mixed" row is important. Most production multi-agent systems don't run every agent on the same model. The coordinator and reviewer might use Sonnet 4, while the workers use Haiku 3.5. This cuts costs by 40-60% compared to running everything on the same frontier model. +The "mixed" row assumes one Sonnet 5 coordinator call for every four Haiku 4.5 worker calls. It is 40% cheaper than the all-Sonnet example under this exact mix; different orchestration ratios produce different savings. ## The Hidden Cost Multipliers @@ -204,11 +204,11 @@ Knowing your costs is the first step. Controlling them is the next. Agent costs are a function of call patterns, not just token prices. A 10% change in model pricing matters far less than a runaway loop that makes 500 calls instead of 50. We wrote about [why monitoring alone isn't sufficient](/blog/true-cost-of-uncontrolled-agents#the-observability-gap) and how [pre-execution runtime authority](/blog/true-cost-of-uncontrolled-agents#runtime-authority-as-infrastructure) closes the gap. -[Cycles](/) provides this layer. Every LLM call checks against a budget before executing. When the budget is exhausted, the call is denied and the agent degrades gracefully. +[Cycles](/) can provide this layer at the operations your application instruments. The application reserves an estimate before protected work and commits actual usage afterward. A live reservation that cannot fit returns an error; the application decides how to stop or degrade. ## From cost visibility to cost control -Cost overruns are a symptom. The root cause is the absence of a pre-execution enforcement layer — a system that asks "is there budget for this?" before every action, not after. That's what [runtime authority](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) provides: deterministic budget decisions at the point of execution, not retroactive alerts on a dashboard. +Token rates help choose a model; call-shape measurements help size the workload. Neither guarantees that concurrent agents stay within a shared allocation. A [runtime budget boundary](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) adds that third concern: reserve capacity before each instrumented operation, then reconcile the actual amount. ## Next steps diff --git a/blog/how-scalerx-wired-cycles-into-a-java-agent-runtime.md b/blog/how-scalerx-wired-cycles-into-a-java-agent-runtime.md index 7a8d9e08..2776ce40 100644 --- a/blog/how-scalerx-wired-cycles-into-a-java-agent-runtime.md +++ b/blog/how-scalerx-wired-cycles-into-a-java-agent-runtime.md @@ -4,7 +4,7 @@ date: 2026-05-05 author: Cycles Team tags: - case-study - - integration + - integrations - java - spring-boot - agents @@ -83,7 +83,7 @@ For the hot path, that's the entire integration. ## Why there's also an explicit service -The LLM path is one annotation. The wallet-management path is a 542-line service called `CyclesBudgetManagementService`. Most of those lines aren't runtime enforcement — they're management-plane glue: wallet creation when a user signs up, funding when they buy credits, balance lookups for the [dashboard](/glossary#dashboard), event-history queries for receipts, and result parsing. Those operations don't fit the reserve/commit shape. They're CRUD against a budget admin API. +The LLM path is one annotation. The wallet-management path is a 542-line service called `CyclesBudgetManagementService`. Most of those lines aren't runtime enforcement — they're management-plane glue: wallet creation when a user signs up, funding when they buy credits, balance lookups for the [dashboard](/glossary#dashboard), event-history queries for receipts, and result parsing. Those operations don't fit the reserve-commit shape. They're CRUD against a budget admin API. The annotation handled the request-time budget question — *can this user afford this LLM call right now* — with almost no code. The line count goes to the rest of the lifecycle, which is the kind of code most integrators end up writing themselves today. @@ -121,7 +121,7 @@ This is the part of the integration that paid off most relative to its effort. C scalerX described their flow in five steps: user makes a request, Cycles tries to make a reservation, the LLM call is executed, the reservation is committed, the user gets a response. That's the happy path. -Two reservation paths exist in their codebase. The hot LLM path runs through the `@Cycles` annotation, where the starter's interceptor builds the workspace-scoped subject from the SpEL expression and handles the reserve/commit/release transitions internally. A second path lives in `CyclesBudgetManagementService` for admin operations that don't fit a method-annotation shape — for example, programmatic reservations from background jobs, or wallet-side bookkeeping. +Two reservation paths exist in their codebase. The hot LLM path runs through the `@Cycles` annotation, where the starter's interceptor builds the workspace-scoped subject from the SpEL expression and handles the reserve-commit-release transitions internally. A second path lives in `CyclesBudgetManagementService` for admin operations that don't fit a method-annotation shape — for example, programmatic reservations from background jobs, or wallet-side bookkeeping. The decision-handling logic is the same in both paths. Here's the relevant block from the manual path, since it makes the response shape concrete: @@ -147,7 +147,7 @@ Three things in this path are worth pointing out: **Deny reason comes from a structured field.** When the decision is anything else, the helper `extractDenyReason` pulls a `reason_code` out of `deny_detail`. The codes — `INSUFFICIENT_BALANCE`, `EXPIRED`, `RATE_LIMITED`, etc. — are stable strings the application can branch on. scalerX surfaces them to the user as friendly errors without round-tripping to a separate metadata service. -scalerX reports no meaningful latency overhead from the reserve/commit pair. That tracks with the architecture — the Cycles server runs in-cluster (or on the same Docker network locally), and the OpenAI Responses call dominates the request path by orders of magnitude. +scalerX reports no meaningful latency overhead from the reserve-commit pair. That tracks with the architecture — the Cycles server runs in-cluster (or on the same Docker network locally), and the OpenAI Responses call dominates the request path by orders of magnitude. ## A deliberate choice: commit on failure diff --git a/blog/how-teams-control-ai-agents-today-and-where-it-breaks.md b/blog/how-teams-control-ai-agents-today-and-where-it-breaks.md index 2d9af78c..27e1e41b 100644 --- a/blog/how-teams-control-ai-agents-today-and-where-it-breaks.md +++ b/blog/how-teams-control-ai-agents-today-and-where-it-breaks.md @@ -67,7 +67,7 @@ Here's how each approach holds up against cost and risk: Each approach has a specific failure mode when it comes to controlling spend: -**System prompts** are suggestions, not constraints. An LLM can and does ignore them — especially under complex reasoning chains, tool-use loops, or adversarial inputs. You cannot enforce a budget by asking a probabilistic system to count. A [$12,400 weekend batch run](/blog/true-cost-of-uncontrolled-agents) doesn't happen because the agent decided to ignore its instructions. It happens because the agent was doing exactly what it was told — just more times than anyone anticipated. +**System prompts** are suggestions, not transactional budget controls. A model can fail to follow them under complex reasoning, tool loops, or adversarial input. In one [$12,400 illustrative weekend workload model](/blog/true-cost-of-uncontrolled-agents), the agent does exactly what the workflow permits — simply more times than planned. **Proxy rate limits** manage request and spend controls well at the LLM layer — but they don't unify into a general action-governance layer for the entire agent runtime. If your agent calls OpenAI for reasoning, Stable Diffusion for images, and a paid data API for stock quotes, no single proxy sees the aggregate cost across all of them. Rate limits also throttle everything equally — they can't distinguish between a $0.03 lookup and a $7.20 multi-step research chain. And they typically operate per-key, not per-agent, per-[tenant](/glossary#tenant), or per-workflow. @@ -77,7 +77,7 @@ Each approach has a specific failure mode when it comes to controlling spend: **Custom rate limiters** get closest to solving the cost problem — but they're an endless game of whack-a-mole. We built ours at scalerX to track spend across LLMs, image generation, video generation, stock data APIs, web search, and more. Every provider required custom integration code and manual maintenance whenever pricing or APIs changed. It was brittle, it only covered cost — no concept of action-level risk — and it could only throttle, not make context-aware decisions like "allow this call at a lower tier" or "deny this tool but permit that one." -These are real limitations, but they're recoverable. Overspend hurts, but it's bounded — you can set hard caps at the provider level, rotate API keys, or kill a process. The money is gone, but the blast radius is financial. (For a deeper look at the full cost picture, see our [AI agent cost management guide](/blog/ai-agent-cost-management-guide).) +These are real limitations. Provider controls, key revocation, and process termination can help, but their cutoff semantics and scopes vary. Financial loss can also trigger service disruption, margin erosion, or customer impact. (For a deeper look, see our [AI agent cost management guide](/blog/ai-agent-cost-management-guide).) Risk is different. @@ -87,7 +87,7 @@ Risk is different. Cost measures how much an agent spends. Risk measures what an agent does — and the security implications of those actions. The gap in agent risk management is wider than the cost gap, because most teams haven't built any risk controls at all. -Consider an agent with tool access to send emails. It enters a loop and sends 200 messages to customers. The token cost is $1.40. The business damage — customer trust, support escalation, potential regulatory [exposure](/glossary#exposure) — could be $50,000 or more. No cost cap in the world prevents that, because the cost was trivial. The harm was in the action. +Consider an illustrative agent with permission to send emails. It enters a loop and sends 200 messages to customers. The modeled token cost is $1.40, while customer trust, support load, and regulatory [exposure](/glossary#exposure) remain unquantified and can be much larger. A monetary cap calibrated to model spend may not catch that; the harm is in the action. This is where every approach listed above fails simultaneously: @@ -138,7 +138,7 @@ The shift isn't conceptual — it's architectural. Instead of hoping guardrails | Framework max-iteration count | [Caller-assigned RISK_POINTS](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk): the application scores and meters tool exposure, not just iterations | | Spend dashboard + alert | Live reservation rejection before the instrumented call | | Custom rate limiter across providers | [Reserve-commit](/blog/what-is-runtime-authority-for-ai-agents) for both monetary and caller-assigned exposure budgets; each provider path still needs integration | -| Inherited permissions in delegation | [Authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation): explicitly provision a child sub-budget and restrict its tool set in the orchestrator | +| Inherited permissions in delegation | [Authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation): explicitly provision a narrower child ledger and restrict its tool set in the orchestrator | The pattern is the same in every row: move the decision upstream, from after execution to before it. From semantic to structural. From observation to enforcement. diff --git a/blog/how-to-add-budget-and-action-guardrails-to-rust-ai-agents-with-cycles.md b/blog/how-to-add-budget-and-action-guardrails-to-rust-ai-agents-with-cycles.md index d933cb70..e5c68670 100644 --- a/blog/how-to-add-budget-and-action-guardrails-to-rust-ai-agents-with-cycles.md +++ b/blog/how-to-add-budget-and-action-guardrails-to-rust-ai-agents-with-cycles.md @@ -25,7 +25,7 @@ This is the gap that Cycles fills. It is a **[runtime authority](/glossary#runti 2. **Constraints:** Did the deepest matching budget return configured caps, and can this host enforce the relevant fields? 3. **Evidence:** How will the application correlate its tool outcome and any non-persisting preflight result with the reservation and settlement lifecycle? -A preflight evaluation can return `ALLOW`, `ALLOW_WITH_CAPS`, or `DENY`. A live reservation succeeds with `ALLOW` or configured `ALLOW_WITH_CAPS`, or returns a budget error, which the client surfaces before the expensive call happens. Reservations, commits, releases, and direct-usage events create lifecycle records; `decide` and dry-run results are non-persisting, so applications that need a complete audit trail must log those results and external outcomes themselves. Optional [webhooks](/how-to/webhook-integrations) can feed emitted lifecycle events into downstream pipelines. +A preflight evaluation can return `ALLOW`, `ALLOW_WITH_CAPS`, or `DENY`. A live reservation succeeds with `ALLOW` or configured `ALLOW_WITH_CAPS`, or returns a budget error, which the client surfaces before the expensive call happens. Live operations mutate reservation or balance state and create their applicable audit records, but the current runtime does not emit a success Event for every reserve, commit, release, or direct debit. `decide` and dry-run create no reservation or balance mutation; the current server emits `reservation.denied` for denied evaluations, but applications still need to log allowed results and external outcomes for a complete audit trail. Optional [webhooks](/how-to/webhook-integrations) can feed the event types that have implemented emission hooks into downstream pipelines. The `runcycles` crate brings this to Rust with an API designed around ownership semantics and compile-time safety. This post shows how to integrate it into existing Rust agent code at three levels of control. @@ -33,9 +33,9 @@ The `runcycles` crate brings this to Rust with an API designed around ownership Rust is increasingly the choice for production agent infrastructure: inference servers (vLLM alternatives), tool execution sandboxes, orchestration layers, and edge deployments via WASM. The reasons are familiar — zero-cost abstractions, memory safety without GC pauses, and `Send + Sync` guarantees for concurrent workloads. -But until now, Rust had no budget enforcement library for agent runtimes. Python had the `@cycles` decorator. TypeScript had `withCycles`. Java had the `@Cycles` Spring annotation. Rust agents ran unguarded. +The Cycles integrations for Python, TypeScript, and Java already expose language-specific lifecycle helpers. The `runcycles` crate adds the corresponding protocol client and lifecycle patterns for Rust; a Rust application remains unguarded unless it places that client on every protected execution path. -The `runcycles` crate closes this gap with an API that leverages Rust's type system to provide guarantees the other languages can't: +The `runcycles` crate uses Rust's type system to make several lifecycle mistakes harder: - **`commit(self)` consumes the guard** — double-commit is a compile error, not a runtime check - **`#[must_use]`** — the compiler warns if you forget to handle a [reservation](/glossary#reservation) diff --git a/blog/how-to-add-runtime-enforcement-without-breaking-your-agents.md b/blog/how-to-add-runtime-enforcement-without-breaking-your-agents.md index 885f1127..be8341f4 100644 --- a/blog/how-to-add-runtime-enforcement-without-breaking-your-agents.md +++ b/blog/how-to-add-runtime-enforcement-without-breaking-your-agents.md @@ -15,13 +15,13 @@ head: # How to Add Runtime Enforcement Without Breaking Your Agents -The #1 objection to adding runtime enforcement to a running agent system isn't cost. It's fear: *"what if it blocks something legitimate?"* +A common objection to adding runtime enforcement to a running agent system isn't cost. It's fear: *"what if it blocks something legitimate?"* It's a fair fear. Enforcement that fires at the wrong time looks identical to a broken system. A customer agent that can't send a confirmation email because the budget ran out is indistinguishable, from the customer's perspective, from a bug. This post is about the answer to that fear: **shadow mode** (also called dry-run). Run enforcement in observe-only mode against real production traffic, watch what it *would* have done, calibrate, then progressively turn it on. In Cycles, this is enabled by setting `dry_run: true` on reservation requests — the server evaluates the full reservation path (scope derivation, budget checks, decision, caps) and returns the decision it would have made, without creating a reservation or touching balances. -It's not a new idea — it's how every serious piece of enforcement infrastructure gets rolled out, from WAFs to rate limiters to Kubernetes admission controllers. The specifics for AI agents are what's new. +It's not a new idea. WAFs, rate limiters, and admission controllers all provide observe-before-block rollout patterns; the agent-specific failure modes are what differ here. @@ -58,46 +58,46 @@ The durations are illustrative, not product requirements. Adjust them to traffic ## Phase 1: Instrument -Before enforcement exists in shadow mode or otherwise, the agent needs to call the enforcement API at the right points in its lifecycle. Every LLM call, every tool call, every sub-agent spawn should produce a reservation attempt. +Before enforcement exists in shadow mode or otherwise, the agent needs to call the enforcement API at the protected points in its lifecycle. Each model or tool path with positive cost or side-effect exposure should produce a reservation attempt. A sub-agent handoff needs its own reservation only when the application assigns positive exposure to the handoff; otherwise record the handoff as application telemetry or a zero-amount Cycles event. This phase is about catching **missing signals**. If the agent sometimes calls a tool without first reserving, shadow mode will look cleaner than reality — because the dangerous calls aren't being checked. The instrument phase ensures the shape of the data is correct before you start measuring it. -**What you're looking for:** reservation calls matching the agent's actual tool call rate, decisions returning for every attempt, no gaps where the agent acts without reserving. (In dry-run, the response has no `reservation_id` — that appears only once you move to live reservations in Phase 4.) +**What you're looking for:** dry-run calls matching the protected action paths, decisions returning for every attempt, and no gaps where a path assigned positive exposure acts without evaluation. (In dry-run, the response has no `reservation_id` — that appears only once you move to live reservations in Phase 4.) ## Phase 2: Shadow Observation This is the core observation phase. Shadow mode returns what enforcement *would* do—which reservations would return `DENY`, `ALLOW_WITH_CAPS`, or `ALLOW`—without blocking or persisting a reservation. -Every dry-run request returns a decision and the agent proceeds regardless. The server does not create a durable shadow reservation, so the application must log each response with the proposed action, run identifier, and actual outcome if you want a record to analyze later. +Every dry-run request returns a decision without blocking or executing the proposed action. The application chooses to proceed and must log each response with the proposed action, run identifier, and actual outcome if it wants a record to analyze later; the server does not create a durable shadow reservation. **What to measure during shadow mode:** 1. **Denial rate** — what percentage of reservations would have been denied? -2. **Denial location** — which scopes fire most often? (per-run, per-tenant, per-workflow?) +2. **Denial location** — which scopes fire most often? (tenant, workspace, app, workflow, agent, or toolset?) If you need a ledger per run, map the run ID to `subjects.workflow`; a run ID in `dimensions` is attribution only. 3. **Estimate accuracy** — how far are estimates from actual usage captured separately by the application? Dry-run produces no commit. 4. **Workflow distribution** — are denials concentrated in specific agent workflows? 5. **Runaway indicators** — are there bursts of reservations that look like retry loops? This data is what distinguishes calibrated enforcement from decorative enforcement. -## Phase 3: Calibrate — The Goldilocks Zone +## Phase 3: Calibrate Against Your SLOs After enough representative traffic, your application-side shadow dataset can inform budget sizing. -There's a useful heuristic for denial rates: +There is no universal acceptable denial percentage. Classify each would-deny outcome against your workload and user-impact objectives: -| Denial rate (shadow mode) | What it means | What to do | +| Shadow result | Question to answer | Typical response | |---|---|---| -| **> 5%** | Budgets are too tight — enforcement would break legitimate work | Increase limits, review specific denied workflows | -| **3-5%** | Budgets are catching edge cases but running warm | Investigate specific denials before enforcing | -| **1-3%** | The goldilocks zone — catching real anomalies without blocking legitimate work | Ready to enforce | -| **0%** | Budgets are decorative — too loose to catch anything | Tighten limits, they're not doing work | +| Legitimate paths are denied | Is the estimate, scope mapping, or limit wrong? | Fix the signal or resize the budget | +| Known runaway cases are allowed | Is the path missing instrumentation or is the limit too loose? | Add coverage or tighten the relevant ledger | +| Denials cluster in one workflow | Does that workflow need a distinct policy or degradation path? | Tune that workflow instead of broad tenant limits | +| Normal and failure scenarios behave as intended | Does the observed sample cover representative traffic and edge cases? | Begin progressive enforcement | -The percentages in this table are starting examples, not universal SLOs. A 5% shadow-denial rate would block one in twenty submitted actions if the same traffic were enforced, which deserves investigation before rollout. A zero rate can be valid for stable traffic, but it does not demonstrate that the chosen limits catch the failure scenarios you care about. Exercise known runaway, retry, and fan-out cases as well as normal traffic, then set an acceptable production denial rate from your own workload and user-impact objectives. +A zero shadow-denial rate can be valid for stable traffic, but it does not prove that the limits catch the failures you care about. Exercise known retry, fan-out, and runaway scenarios as well as normal traffic. ## Phase 4: Progressive Enforcement -When you flip the switch from shadow to enforce, don't flip all of it at once. Progressive enforcement is the rule for the same reason canary deployments are. Google's SRE Workbook [defines canarying as](https://sre.google/workbook/canarying-releases/) *"a partial and time-limited deployment of a change in a service and its evaluation"* — the same logic applies to enforcement rollout. +When you change protected paths from `dry_run: true` to live reservations, do not change every path at once. Progressive enforcement follows the same logic as canary deployments. Google's SRE Workbook [describes canarying](https://sre.google/workbook/canarying-releases/) as a partial, time-limited change evaluated before wider rollout. For agent enforcement, "fractions" means **by risk tier**: @@ -107,17 +107,17 @@ For agent enforcement, "fractions" means **by risk tier**: This ordering inverts the usual intuition ("enforce the dangerous ones first"). The reason is calibration confidence. By the time you're enforcing on `send_email`, you've already validated your budget sizing on lower-risk paths. A surprise at the email level is much more expensive than a surprise at the search level. -**Keep a kill switch.** A feature flag that can disable enforcement without a redeploy — the [kill switch pattern](https://launchdarkly.com/docs/home/flags/killswitch) — is standard practice for exactly this reason. If enforcement starts firing incorrectly at 2 AM, you want to flip back to shadow mode in seconds, not wait for a deploy cycle. +**Keep a kill switch.** An application feature flag that restores `dry_run: true` or bypasses the integration without a redeploy — the [kill switch pattern](https://launchdarkly.com/docs/home/flags/killswitch) — lets operators respond promptly if enforcement starts producing incorrect denials. ## Phase 5: Full Enforcement + Continuous Calibration -Agents evolve. Usage patterns change. A budget that was right at month 1 won't be right at month 6. In practice, shadow mode rollout never really ends — you keep running enforcement continuously, but you also keep watching the metrics shadow mode taught you to watch: denial rate, estimate accuracy, workflow distribution. +Agents evolve and usage patterns change. Continue watching denial rate, estimate accuracy, and workflow distribution after enforcement begins, and recalibrate when behavior changes. Most importantly: when you add new agent workflows or tools, **re-enter shadow mode for those paths**. Don't assume your existing calibration covers them. ## What Shadow Mode Reveals That You Can't See Otherwise -Beyond calibration, shadow mode surfaces three things production monitoring alone misses: +Before enforcement, application-side shadow records can expose three patterns without intentionally blocking live work: ### Runaway loop signatures @@ -125,7 +125,7 @@ An application shadow log showing 47 reservation attempts from one run in 90 sec ### Delegation chain amplification -In multi-agent systems, a single user request can fan out into dozens of sub-agent calls. Shadow mode shows you the [delegation topology](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) — which parents spawn how many children, how deep the chains go, and whether sub-agents are getting over-broad authority. This is the data that justifies adding [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) vs. just capping top-level budgets. +In multi-agent systems, a single user request can fan out into many sub-agent calls. Cycles dry-run responses show the submitted subjects, estimates, and hypothetical budget decisions; they do not infer the delegation graph, depth, or tool permissions. Join application handoff logs to those responses when evaluating whether explicit child ledgers and narrower host permissions are needed. ### Where you need degradation paths @@ -133,13 +133,13 @@ Shadow mode doesn't just tell you *whether* to enforce. It tells you *where you ## Common Shadow Mode Mistakes -**Treating shadow mode as a checkbox.** Running shadow mode for three days, seeing low denial rates, and declaring victory. Real usage patterns take 1-2 weeks to emerge. +**Treating shadow mode as a checkbox.** A short sample may miss periodic jobs, traffic spikes, or rare failure paths. Observe enough representative traffic and exercise known edge cases before drawing conclusions. -**Enabling too many scopes at once.** Shadow mode is cheap, but interpreting five simultaneous budget scopes is not. Start with 1-2 scopes (per-run, per-tenant), add more once those are calibrated. +**Enabling too many scopes at once.** Dry-run evaluation does not consume balances, but interpreting many simultaneous budget scopes is still difficult. Start with one or two standard scopes, such as tenant and workflow, and add more once those are calibrated. -**Staying in shadow mode forever.** The purpose of shadow mode is to enable enforcement, not to replace it. Teams that stay in shadow mode past calibration are paying the cost of enforcement infrastructure without getting the benefit. If your numbers are stable for two weeks, enforce. +**Staying in shadow mode forever.** The purpose of shadow mode is to prepare for enforcement, not replace it. Move a path to live reservations when its representative outcomes, degradation behavior, and rollback procedure meet your own cutover criteria. -**Looking only at averages.** A 1% average denial rate with 20% denials on one specific workflow means that workflow is broken. Always break metrics down by scope and workflow, not just overall. +**Looking only at averages.** A low overall denial rate can hide a much higher rate on one workflow. Break metrics down by scope and workflow, then inspect whether the concentrated denials are intended. **Ignoring estimate accuracy.** If your estimates are consistently 3x higher than actuals, your budgets are effectively 3x tighter than you think. Estimate drift is the silent killer of calibrated enforcement. @@ -147,7 +147,7 @@ Shadow mode doesn't just tell you *whether* to enforce. It tells you *where you Shadow mode turns enforcement from an all-or-nothing cutover into a measurable, reversible rollout. The same observe-before-block pattern used in policy and traffic-control systems applies to AI agents, with application logging added because Cycles dry-run responses do not persist reservation state. -If you're considering adding runtime enforcement to your agents and the fear is "what if it blocks something legitimate" — shadow mode is the answer. You don't have to guess. Run it in observe mode for two weeks, look at the denial rate, decide if your budgets are the right size, then enforce progressively from low-risk to high-risk paths. +If you're considering runtime enforcement and the fear is "what if it blocks something legitimate," dry-run evaluation lets you test the answer. Collect representative application-side results, size the budgets against your own SLOs, and then enforce progressively from lower-impact to higher-impact paths. For production paths with meaningful user impact, skipping observation increases the chance that an incorrectly sized or mis-scoped budget blocks legitimate work. If an emergency or low-risk rollout cannot support a long shadow period, compensate with narrower scope, explicit degradation behavior, and a tested rollback switch. diff --git a/blog/langchain-runcycles-cost-fn-actual-cost.md b/blog/langchain-runcycles-cost-fn-actual-cost.md index 8478273e..625f318b 100644 --- a/blog/langchain-runcycles-cost-fn-actual-cost.md +++ b/blog/langchain-runcycles-cost-fn-actual-cost.md @@ -154,5 +154,5 @@ The release-by-release sequence is worth recording for the trail it leaves. 0.1. - [`langchain-runcycles` on GitHub](https://github.com/runcycles/langchain-runcycles) — source, releases, integration tests - [`langchain-runcycles` on PyPI](https://pypi.org/project/langchain-runcycles/) — package releases, including the v0.3.0 tool-side `cost_fn` -- [LangChain `AgentMiddleware` reference](https://docs.langchain.com/oss/python/langchain/middleware/) — the framework hook this package implements +- [LangChain `AgentMiddleware` reference](https://docs.langchain.com/oss/python/langchain/middleware/overview) — the framework hook this package implements - [Cycles Protocol](https://github.com/runcycles/cycles-protocol) — the open spec for runtime budget and [action authority](/glossary#action-authority) diff --git a/blog/langgraph-budget-control-durable-execution-retries-fan-out.md b/blog/langgraph-budget-control-durable-execution-retries-fan-out.md index 86441275..52250e1e 100644 --- a/blog/langgraph-budget-control-durable-execution-retries-fan-out.md +++ b/blog/langgraph-budget-control-durable-execution-retries-fan-out.md @@ -14,13 +14,13 @@ head: # LangGraph Budget Control for Durable Execution, Retries, and Fan-Out -A team builds an insurance claim processor in LangGraph. The graph has six nodes — classify, extract, validate, enrich, review, decide — with checkpointing enabled so runs can pause and resume. It works well in development. +Consider a constructed insurance-claim processor in LangGraph. The graph has six nodes—classify, extract, validate, enrich, review, decide—with checkpointing enabled so runs can pause and resume. In production, a batch of 200 claims kicks off on Tuesday morning. The "enrich" node calls an external API that starts returning rate-limit errors. LangGraph's node-level retry policy retries each failed enrich call three times. Each retry triggers a new LLM call to re-plan the enrichment approach — $0.45 per attempt. Across 200 claims, 600 extra LLM calls add $270 in retry spend on top of the original $180. Total bill: $450 instead of $180. Then it gets worse. Twelve claims trigger the "review" node to fan out into four parallel sub-graphs — one per policy type. Each sub-graph has its own retry policy. When the sub-graphs encounter the same rate limit, each retries independently — 4 branches × 3 retries × $6.65 per branched retry = $80 per claim. Those 12 claims alone burn through $960 in an hour. -The [provider dashboard shows the spike at 6 PM](/blog/cycles-vs-llm-proxies-and-observability-tools) when someone checks. The team's $500/month spending cap hasn't triggered — it's March 4th, and the monthly total is only at $1,410. +In this illustrative scenario, the team discovers the spike later in its [provider dashboard](/blog/cycles-vs-llm-proxies-and-observability-tools). The exact alerting and provider-control behavior depends on its account configuration. Durable execution makes agents more reliable. It also makes cost failures more expensive — because every retry, resume, and [fan-out](/glossary#fan-out) replays work that already cost money. @@ -28,13 +28,13 @@ Durable execution makes agents more reliable. It also makes cost failures more e ## Why Durable Execution Changes the Budget Problem -One-shot agents have a simple cost model: one pass through the workflow, one bill. If the run fails, you lose that run's cost. The waste is bounded. +One-shot agents have a simpler cost model: fewer retry, checkpoint, and replay surfaces. Their spend is still unbounded unless an execution limit exists. Durable graph agents — whether built on LangGraph, Temporal, or Restate — break this model. Runs checkpoint, pause, resume, retry, and branch. The cost of a single logical run is not "one pass." It is the sum of every attempt, across every checkpoint, across every branch. Three properties of durable execution change how bounded [exposure](/glossary#exposure) works: -**Checkpoints create replay surfaces.** When a graph resumes from a checkpoint, it can re-execute nodes that already consumed [tokens](/glossary#tokens) and triggered side effects. If the budget system does not know which nodes already ran, it cannot prevent double-charging. +**Checkpoints create replay surfaces.** Depending on checkpoint placement and application logic, a resumed graph can re-execute work that already consumed [tokens](/glossary#tokens) or triggered side effects. Budget idempotency prevents duplicate ledger mutation only when the caller reuses the same key and request body; business-side replay safety remains separate. **Retries compound across graph depth.** A retry at the graph level replays multiple nodes. A retry at the node level replays multiple LLM calls within that node. If both layers have retry policies, the total cost is the product, not the sum. A 3× graph retry with 3× node retry produces up to 9× the expected cost for a single pass. @@ -43,12 +43,12 @@ Three properties of durable execution change how bounded [exposure](/glossary#ex | Property | One-shot agent | Durable graph agent | |---|---|---| | Retry cost | Replays full run | Replays from checkpoint — may re-execute completed nodes | -| Fan-out cost | Sequential, predictable | Parallel, multiplicative | +| Fan-out cost | Fewer concurrent branches | Sum of parallel branch work | | Failure blast radius | One run's budget | Accumulated spend across all attempts | | Budget check timing | Before each LLM call | Before each LLM call + before each node + before each retry | | Concurrency risk | Low (single thread) | High (parallel branches, shared budget) | -This is not a theoretical concern. It is the default behavior of any graph-based agent framework with persistence and retry logic enabled. The framework does its job — making execution reliable. The missing piece is making execution **bounded**. +These are design risks to test, not claims about every graph run. Actual replay depends on checkpointing, retry policies, idempotency, and node implementation. ## The Four Budget Problems in LangGraph Workflows @@ -68,57 +68,57 @@ A graph with 3 retries per node and 3 retries per graph can produce up to 9 exec These three retry layers operate at different levels of the stack. SDK retries replay a single HTTP call — transparent to the node, cost = one LLM call per attempt. Node retries re-execute the node function, which may contain multiple LLM calls and tool invocations — cost = the full node body per attempt. Graph-level retries resume from a checkpoint and re-enter the node from persisted state, replaying everything above. Each layer compounds the cost of the layers below it. -This is the same geometric multiplication pattern behind the [retry storm failure that cost $1,800 in 12 minutes](/blog/ai-agent-failures-budget-controls-prevent). Durable execution does not prevent [retry storms](/glossary#retry-storm) — it makes them more likely, because the framework is designed to keep trying. +This is the same geometric multiplication pattern behind the [illustrative retry-storm model that costs $33.86 under its stated assumptions](/blog/ai-agent-failures-budget-controls-prevent). Durable execution does not prevent [retry storms](/glossary#retry-storm) — it can amplify them when retry layers are not coordinated. ### 3. Fan-out branches racing for shared budget A LangGraph node fans out into four parallel sub-graphs. Each sub-graph checks the remaining budget before starting. All four see "$40 remaining" because they check concurrently. All four proceed. Total spend: $160. -This is the concurrency problem that breaks every application-level budget checker. A variable in memory, a row in a database, even a Redis counter — none of these provide the atomic reserve-and-deduct semantics needed when multiple branches spend from the same budget simultaneously. The fundamental issue is that [a checker is not an authority](/blog/vibe-coding-budget-wrapper-vs-budget-authority). +This breaks a non-atomic read-check-write budget checker. A database transaction or atomic Redis script can implement the required invariant; Cycles packages that reserve-and-settle behavior as a protocol service. The fundamental issue is that [a read-only checker is not an authority](/blog/vibe-coding-budget-wrapper-vs-budget-authority). ### 4. Checkpoint-unaware budget state If budget tracking lives in application memory — a counter, a running total, a class variable — it is lost when the process restarts. LangGraph checkpoints the graph state. It does not checkpoint your budget counter. -When the graph resumes from a checkpoint, the agent's state is restored. The budget counter resets to zero. The actual spend does not. The agent now believes it has a full budget and proceeds to spend it again. +When the graph resumes from a checkpoint, an application-only counter may reset unless it was persisted consistently. Provider spend and external side effects do not reset with it. -This failure mode is unique to durable execution. One-shot agents don't survive process restarts, so in-memory budget tracking, while fragile, at least fails safely — the agent dies with the process. Durable agents survive restarts by design. Their budget tracking must survive too. +Durable execution makes this mismatch easier to trigger because the workflow survives restarts by design. Its budget state and idempotency model must survive too. ## The Pattern: Reserve at the Node, Settle at the Edge The [reserve-commit lifecycle](/blog/ai-agent-budget-control-enforce-hard-spend-limits) already solves the core problem of pre-execution budget enforcement. For durable graph execution, the same pattern applies — but scoped to the graph's structure: -**Run-level budget.** A hard limit for the entire graph execution, including all retries and fan-outs. No combination of retries, replays, or parallel branches can exceed it. +**Run-level budget.** Map each graph execution to an enforceable scope—commonly a unique workflow subject—and require every protected call to submit it. `subject.dimensions.run_id` is attribution-only and does not create a ledger. **Node-level reservation.** Before each node executes, reserve the estimated cost from the run budget. The reservation is atomic — if the budget is insufficient, the node does not start. The run receives a clear budget-exhausted signal instead of silently proceeding. -**Idempotent commit.** When a node completes, commit the actual cost with a unique execution identifier (run ID + node ID + attempt number). If the same node replays on retry or resume, the commit operation recognizes the duplicate and does not charge again. The node ran once; it pays once. +**Idempotent settlement.** Retries of the same Cycles operation must reuse the same idempotency key and request body. A genuinely new node attempt needs a new key because it performs new work. The application or checkpoint state decides whether a replay represents the same operation. -**Retry-safe settlement.** On retry, check whether a reservation for this node-execution already exists. If it was committed (node completed successfully), skip the reservation — the cost is already settled. If it was reserved but never committed (the process crashed mid-execution), release the stale reservation and create a fresh one. +**Retry-safe settlement.** On resume, reconcile any existing reservation before starting more work. If the external call may have completed, do not blindly release the hold; retry the same commit when the actual outcome is known or send the case to operator reconciliation. -**Fan-out budget scoping.** Each parallel branch receives a sub-budget carved atomically from the parent budget. Branch A gets $10, Branch B gets $10, Branch C gets $10, Branch D gets $10. The parent's budget decreases by $40 in a single atomic operation. No branch can overspend because each sub-budget is a hard ceiling, not a shared pool. +**Fan-out budget scoping.** Every branch call can consume a shared workflow ledger atomically. If separate branch ceilings are required, provision explicit branch-mapped agent or workflow ledgers and submit those scopes. Cycles does not carve or transfer branch balances from a parent in one allocation operation. | LangGraph event | Budget action | What it prevents | |---|---|---| -| Graph start | Create run budget | Unbounded total spend | +| Before graph execution | Provision or select a unique enforceable workflow budget | Unbounded submitted estimate for that workflow | | Node entry | Reserve from run budget | Node executing without budget | | LLM call within node | Check reservation covers call | Mid-node overrun | -| Node completion | Commit actual cost (idempotent) | Double-charging on replay | -| Node failure + retry | Release uncommitted reservation, re-reserve | Leaked reservations | -| Fan-out (parallel branches) | Allocate sub-budgets from parent | Concurrent overspend | -| Fan-in (join) | Reconcile sub-budget actuals to parent | Accounting drift | -| Graph completion | Settle run budget, release unused | Over-reservation | +| Node completion | Commit best-known actual cost; retry the same commit with the same key/body | Duplicate ledger mutation on transport retry | +| Node failure + retry | Reconcile ambiguous reservations; use a new key only for new work | Leaked or incorrectly released reservations | +| Fan-out (parallel branches) | Reserve every branch against the shared workflow and any explicit branch scope | Oversubscription of matching ledgers | +| Fan-in (join) | Confirm branch reservations are settled | Accounting drift | +| Graph completion | Reconcile remaining in-flight reservations | Orphaned holds | ## What This Looks Like in Practice -Cycles — a [runtime authority](/glossary#runtime-authority) for [autonomous agents](/glossary#autonomous-agent) — integrates with LangGraph through a [LangChain callback handler](/how-to/integrating-cycles-with-langchain) on the model. The handler fires on every LLM call inside every node: it creates a reservation on `on_llm_start`, commits actual cost on `on_llm_end`, and releases on `on_llm_error`. The reservation boundary sits at the model call, not at the graph edge — so a node that makes three LLM calls gets three reservations. +The documented [LangChain callback handler](/how-to/integrating-cycles-with-langchain) can wrap model calls inside LangGraph nodes: it creates a reservation on `on_llm_start`, commits caller-calculated usage on `on_llm_end`, and releases on `on_llm_error`. The example handler is application code, not a class exported by the Cycles package, and its default UUID keys are not checkpoint-aware. ```python from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import MemorySaver from langchain_openai import ChatOpenAI from runcycles import CyclesClient, CyclesConfig, Subject -from budget_handler import CyclesBudgetHandler # see integration guide +from budget_handler import CyclesBudgetHandler # local class copied from the integration guide client = CyclesClient(CyclesConfig.from_env()) @@ -153,7 +153,7 @@ graph.add_edge("enrich", END) app = graph.compile(checkpointer=MemorySaver()) ``` -When LangGraph resumes from a checkpoint and re-enters a node, the handler treats it like any other LLM call — the reservation fires again. [Idempotency keys](/glossary#idempotency-key) on commits (run ID + node ID + attempt number) prevent double-charging: a retried node creates a new reservation, while a replayed-from-checkpoint node that already committed is recognized as settled. +When LangGraph resumes from a checkpoint and re-enters a node, the basic handler treats it like another LLM call and creates another reservation. A production checkpoint-aware wrapper must persist the Cycles operation identity or mark the node execution settled. Merely constructing a key from run, node, and attempt fields does not deduplicate anything unless the same logical operation reuses the same key and body. For fan-out, each parallel review node gets its own model instance with a budget-scoped handler: @@ -178,7 +178,7 @@ for branch in ["liability", "medical", "property", "general"]: graph.add_node(f"review_{branch}", make_review_node(branch_llm)) ``` -Each parallel node's LLM calls are budget-bounded independently. The scoped `Subject` per branch means Cycles tracks spend separately — no shared-pool race condition. +Each parallel node's LLM calls can share the workflow ledger and, when explicitly provisioned, match a narrower branch agent ledger. The server checks all matching ledgers atomically. A distinct `Subject` alone does not create a budget. For the full callback handler implementation and runnable examples, see [Integrating Cycles with LangChain](/how-to/integrating-cycles-with-langchain). @@ -188,13 +188,13 @@ The difference is not subtle. It is the difference between a cost surprise and a | Scenario | Without node-level control | With Cycles | |---|---|---| -| Graph resumes from checkpoint | All nodes re-execute, full cost repeated | Idempotent commit skips already-settled nodes | -| 3-level nested retry | Up to 27× cost multiplier (3×3×3) | Run budget caps total across all retries | -| 4-way fan-out, $40 remaining | All 4 branches proceed, $160 spent | Sub-budgets: 4 × $10, total capped at $40 | -| Process crash mid-node | Reservation leaked, budget permanently reduced | Uncommitted reservation auto-released on retry | -| Overnight batch of 500 graph runs | No per-run limit, total cost unknown until morning | Each run bounded, batch total = sum of run budgets | +| Graph resumes from checkpoint | Application may replay already completed work | Checkpoint-aware caller reuses the same operation identity or skips settled work | +| 3-level nested retry | Up to 27 executions under the stated 3×3×3 policy | Every new attempt consumes the shared workflow budget | +| 4-way fan-out, $40 remaining | Non-atomic checks can each proceed from the same snapshot | Atomic reservations contend against the same $40 ledger | +| Process crash mid-node | External outcome and hold can become ambiguous | Caller retries the same settlement or sends it to reconciliation | +| Overnight batch of 500 graph runs | No application-defined per-run boundary | Unique workflow ledgers bound submitted estimates when every path uses them | -The insurance claim processor from the opening scenario would have stopped at $180 — enforcement before the action, not observation after. The retry replays would have been idempotent — committed nodes would not re-charge. The fan-out branches would have received sub-budgets. The run-level hard limit would have prevented any single execution from exceeding its allocation. +In the constructed claim scenario, a mandatory workflow ledger would reject the first new estimate that no longer fit. The exact provider-bill maximum still depends on conservative estimates, commit-overage policy, complete instrumentation, and checkpoint-aware idempotency. ## Next steps diff --git a/blog/lethal-trifecta-rule-of-two-metered-authority.md b/blog/lethal-trifecta-rule-of-two-metered-authority.md index 0f9860e5..4666abf8 100644 --- a/blog/lethal-trifecta-rule-of-two-metered-authority.md +++ b/blog/lethal-trifecta-rule-of-two-metered-authority.md @@ -70,8 +70,8 @@ The [agent security controls map](/blog/agent-security-controls-map) places thes ## Further Reading - [Agent Security Controls Map](/blog/agent-security-controls-map) — where each control layer sits and what job it owns -- [Beyond Budget: How Cycles Controls Agent Actions](/blog/beyond-budget-how-cycles-controls-agent-actions) — the action-authority mechanics +- [How Cycles Meters Caller-Assigned Action Exposure](/blog/beyond-budget-how-cycles-controls-agent-actions) — metering exposure alongside host action authorization - [AI Agent Risk Assessment: Score, Classify, Enforce](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk) — pricing actions by blast radius -- [MCP Tool Poisoning: 84% Success Rate](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) — why detection alone keeps losing +- [MCP-ITP: Target-Tool Calls Reached 84.2%](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) — what the controlled tool-poisoning benchmark measured - [The Moltbook Exposure](/blog/moltbook-exposure-agent-tokens-no-gates) — injection at social scale, and bounding what a hijacked agent can do - [When Budget Runs Out: Degradation Patterns](/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents) — what agents do at the wall diff --git a/blog/manifest-vs-cycles-routing-vs-runtime-authority.md b/blog/manifest-vs-cycles-routing-vs-runtime-authority.md index a30c94c3..18f15d10 100644 --- a/blog/manifest-vs-cycles-routing-vs-runtime-authority.md +++ b/blog/manifest-vs-cycles-routing-vs-runtime-authority.md @@ -26,9 +26,9 @@ Manifest's pitch is straightforward: stop sending every query to the most expens ## What Cycles does -[Cycles](https://runcycles.io) is a [runtime authority](/glossary#runtime-authority) for [autonomous agents](/glossary#autonomous-agent). Before an action executes, the agent reserves cycles. If no cycles remain, the action does not run. After execution, actual usage is committed and unused cycles are released. +[Cycles](https://runcycles.io) is a budget-authority component for [autonomous agents](/glossary#autonomous-agent). Before a protected action executes, the host submits a reservation. If the reservation is rejected and the host enforces that result at a mandatory boundary, the action does not run. After execution starts, the caller commits measured usage; it releases the hold only when the action did not start or demonstrably consumed nothing. -Cycles enforces this across hierarchical scopes — [tenant](/glossary#tenant), workspace, app, workflow, agent, toolset — with atomic, concurrency-safe [reservations](/glossary#reservation). It can fully allow, allow with constraints, or deny execution, which makes [graceful degradation](/glossary#graceful-degradation) possible rather than hard failure. +Cycles checks explicitly provisioned ledgers across hierarchical scopes — [tenant](/glossary#tenant), workspace, app, workflow, agent, toolset — with atomic, concurrency-safe [reservations](/glossary#reservation). It can return `ALLOW`, `ALLOW_WITH_CAPS`, or a rejection; the host authorizes the action, applies returned caps, and chooses any [graceful degradation](/glossary#graceful-degradation). Cycles is not tied to OpenClaw or any single agent framework. It works across any tool, API, or workflow that needs bounded execution. @@ -38,14 +38,14 @@ If the problem is **OpenClaw model selection and cost optimization**, Manifest i ## When Cycles is the better fit -If the problem is **bounded autonomous execution** — preventing agents from spending without limits across any combination of tools, APIs, and workflows — Cycles is the direct answer. Its docs focus on reserve/commit semantics, atomic reservation, concurrency-safe shared budgets, idempotent settlement, and hierarchical scopes. That is a runtime control-plane story, not a routing story. +If the problem is **bounded autonomous execution** — preventing agents from spending without limits across any combination of tools, APIs, and workflows — Cycles is the direct answer. Its docs focus on reserve-commit semantics, atomic reservation, concurrency-safe shared budgets, idempotent settlement, and hierarchical scopes. That is a runtime control-plane story, not a routing story. ## The short version - **Manifest optimizes and routes** — which model should handle this request? -- **Cycles authorizes and enforces** — is this action still allowed to execute? +- **Cycles reserves and accounts** — does the submitted amount fit the matching budget ledgers? -They are not the same product category. In some stacks they may be complementary: Manifest picks the model, Cycles decides whether the call should happen at all. +They are not the same product category. In some stacks they may be complementary: Manifest picks the model, while a mandatory Cycles integration checks the submitted estimate before the host makes the call. --- diff --git a/blog/mcp-gateways-are-not-runtime-authority.md b/blog/mcp-gateways-are-not-runtime-authority.md index a05e4bf2..8a5bba13 100644 --- a/blog/mcp-gateways-are-not-runtime-authority.md +++ b/blog/mcp-gateways-are-not-runtime-authority.md @@ -85,7 +85,7 @@ Examples: An MCP gateway can block tools that should not be visible. It can help with authentication and central routing. But once a tool is approved, the gateway alone does not usually provide cumulative budget state, risk-point accounting, hierarchical tenant scopes, or reserve-commit semantics. -That is the gap [MCP Tool Poisoning Has an 84% Success Rate](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) addresses from the attack side. This post is the architecture-side version: even clean tools need per-action authority. +That is the attack-side gap examined in [MCP-ITP: Target-Tool Calls Reached 84.2%](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it). This post is the architecture-side version: even clean tools need an independently enforced application authorization boundary. ## The Two-Layer Pattern @@ -170,6 +170,6 @@ If that answer matters in production, a gateway alone is incomplete. - [OWASP MCP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html) - MCP deployment guidance for least privilege, schema integrity, sandboxing, monitoring, and supply chain controls - [OWASP Top 10 for Agentic Applications 2026](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) - broader agentic risk framework - [Runtime Authority vs Runtime Authorization](/concepts/runtime-authority-vs-runtime-authorization) - local layer distinction -- [MCP Tool Poisoning Has an 84% Success Rate](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) - attack-side context for MCP security +- [MCP-ITP: Target-Tool Calls Reached 84.2%](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) - attack-side context and benchmark boundaries - [Zero Trust for AI Agents](/blog/zero-trust-for-ai-agents-why-every-tool-call-needs-a-policy-decision) - why every tool call needs an external policy decision - [Getting Started with the Cycles MCP Server](/quickstart/getting-started-with-the-mcp-server) - config-level integration path diff --git a/blog/mcp-tool-budgets-before-execution.md b/blog/mcp-tool-budgets-before-execution.md index 85549831..64e6abec 100644 --- a/blog/mcp-tool-budgets-before-execution.md +++ b/blog/mcp-tool-budgets-before-execution.md @@ -8,7 +8,7 @@ tags: - budget-control - runtime-authority - architecture -description: "MCP exposes tools, but it does not decide whether the next call should run. Wrap MCP handlers with Cycles reserve/commit checks before side effects happen." +description: "MCP exposes tools, but it does not decide whether the next call should run. Wrap MCP handlers with Cycles reserve-commit checks before side effects happen." blog: true sidebar: false featured: false @@ -491,7 +491,7 @@ An MCP gateway answers *can this tool be reached?* — authentication, allowlist The first question is about access. The second is about [exposure](/glossary#exposure) — the cumulative cost, action count, or blast radius the agent has already accumulated. Two questions, two layers. A gateway without runtime authority is a pass/fail access system; the 201st email goes through if the tool is allowed at all. Runtime authority without a gateway has to trust the tool inventory. -Many production incidents we see are not unknown tools. They are approved tools called too many times, in the wrong scope, after the budget should have run out. That's exactly the gap a per-tool-call reservation closes; when run dimensions are enforced, the same pattern also caps the whole run. +A common failure pattern is an approved tool called too many times or under the wrong application scope. A mandatory per-tool-call reservation can bound the cumulative estimate. To create a per-run budget with the current subject model, the caller must map that run to an enforceable scope such as a unique workflow; `subject.dimensions.run_id` is attribution-only. For the architecture-side detail of where this sits relative to gateways and authorization, see [MCP Gateways Are Not Runtime Authority](/blog/mcp-gateways-are-not-runtime-authority). diff --git a/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it.md b/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it.md index 4ca6d5ec..dfe77e59 100644 --- a/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it.md +++ b/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it.md @@ -1,9 +1,9 @@ --- -title: "MCP Tool Poisoning: 84% Success Rate" +title: "MCP-ITP: Target-Tool Calls Reached 84.2%" date: 2026-03-27 author: Albert Mavashev tags: [security, MCP, tool-poisoning, agents, production, OWASP, runtime-authority, supply-chain] -description: "In benchmarks, tool poisoning attacks succeed 84% of the time with auto-approval. 10,000+ MCP servers, 30+ CVEs, and no independent runtime enforcement gate." +description: "MCP-ITP induced selected target-tool calls in up to 84.2% of MCPTox prompts. Learn what the benchmark measured and how to layer MCP defenses in production." blog: true sidebar: false head: @@ -12,7 +12,7 @@ head: content: MCP tool poisoning, Model Context Protocol security, MCP supply chain, tool description injection, runtime enforcement, MCP security controls --- -# MCP Tool Poisoning: Why Framework Controls Need Backup +# MCP-ITP: Target-Tool Calls Reached 84.2% in MCPTox > **Part of: [AI Agent Risk & Blast Radius Reference](/guides/risk-and-blast-radius)** — the full pillar covering action authority, risk scoring, blast-radius containment, and degradation paths. @@ -22,7 +22,7 @@ That's the finding that reframed MCP security in 2026. [Invariant Labs demonstra -The ecosystem is large enough that provenance and configuration matter. As of early 2026, one directory reported [over 10,000 public MCP servers](https://mcpplaygroundonline.com/blog/mcp-security-tool-poisoning-owasp-top-10-mcp-scan). [Trend Micro found 492 MCP servers](https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data) exposed to the internet with zero authentication, while researchers [reported 1,184 malicious skills](https://www.cryptonewsz.com/openclaws-clawhub-flags-1184-malicious-skills/) in OpenClaw's separate ClawHub ecosystem. In controlled benchmark testing, the [MCP-ITP framework](https://arxiv.org/abs/2601.07395) measured tool-poisoning success rates **up to 84.2%** under auto-approval. That benchmark condition should not be read as evidence about how common auto-approval is in production. +The ecosystem is large enough that provenance and configuration matter. As of early 2026, one directory reported [over 10,000 public MCP servers](https://mcpplaygroundonline.com/blog/mcp-security-tool-poisoning-owasp-top-10-mcp-scan). [Trend Micro found 492 MCP servers](https://www.trendaisecurity.com/en-us/resources-insights/research/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data) exposed to the internet with zero authentication, while researchers [reported 1,184 malicious skills](https://www.cryptonewsz.com/openclaws-clawhub-flags-1184-malicious-skills/) in OpenClaw's separate ClawHub ecosystem. On the controlled MCPTox benchmark, [MCP-ITP](https://arxiv.org/abs/2601.07395) induced the selected legitimate target-tool call in up to **84.2% of evaluated prompts across 12 model/agent settings**. The study measured whether the model selected the target tool; it did not test production auto-approval prevalence or confirm live side-effect execution. OWASP responded by publishing the [MCP Top 10](https://owasp.org/www-project-mcp-top-10/), a dedicated security framework for MCP vulnerabilities (currently in beta), separate from the broader Agentic AI Top 10. Researchers have also [catalogued more than 30 CVEs](https://medium.com/ai-security-hub/mcps-first-year-what-30-cves-and-500-server-scans-tell-us-about-ai-s-fastest-growing-attack-6d183fc9497f) across MCP implementations. Those reports establish a broad implementation and supply-chain attack surface; they do not mean every CVE or exposed server is a metadata-poisoning exploit in the wild. @@ -106,13 +106,11 @@ This is the architectural gap. And it's why scanners, pinning, and per-tool appr What's missing is an enforcement layer that sits **between** the agent's decision and the tool's execution — evaluating every tool call against policy before it runs, without requiring human-in-the-loop for every invocation. -## The Enforcement Gap: What the $47,000 Incident Taught Us +## The Cumulative-Control Gap The missing enforcement layer isn't just a security problem. It's an operational one. -In March 2026, a multi-agent research system built on a common open-source stack [generated a $47,000 API bill](https://earezki.com/ai-news/2026-03-23-the-ai-agent-that-cost-47000-while-everyone-thought-it-was-working/) when two agents entered a recursive loop that ran for 11 days. Traditional monitoring — Datadog, PagerDuty — didn't catch it because the API calls were succeeding. Every tool call returned 200. The agents were "working." - -The reported incident illustrates a cumulative-control gap: individually successful calls can still form an unsafe loop. OWASP's MCP guidance covers pre-execution authorization, audit, scope enforcement, and other controls, while the [Coalition for Secure AI (CoSAI)](https://www.helpnetsecurity.com/2026/03/03/enterprise-ai-agent-security-2026/) maps additional MCP threat categories. Evaluation before execution is one requirement among supply-chain, identity, sandbox, and monitoring controls. +Individually successful calls can still form an unsafe loop. Monitoring can show that each call returned successfully without deciding whether the cumulative sequence should continue. OWASP's MCP guidance covers pre-execution authorization, audit, scope enforcement, and other controls. Evaluation before execution is one requirement among supply-chain, identity, sandbox, and monitoring controls. ## How a Mandatory Boundary Limits MCP Blast Radius @@ -204,10 +202,10 @@ For everyone else, here's a practical path: Research and data referenced in this post: - [Invariant Labs: MCP Security Notification — Tool Poisoning Attacks](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks) — Original tool poisoning research with proof-of-concept -- [MCP-ITP: An Automated Framework for Implicit Tool Poisoning](https://arxiv.org/abs/2601.07395) — Ruiqi Li et al., January 2026. Benchmark showing up to 84.2% attack success rate across 12 LLM agents +- [MCP-ITP: An Automated Framework for Implicit Tool Poisoning](https://arxiv.org/abs/2601.07395) — Ruiqi Li et al., January 2026. On MCPTox, the method induced selected target-tool calls in up to 84.2% of evaluated prompts across 12 model/agent settings; it did not test live production execution. - [OWASP MCP Top 10](https://owasp.org/www-project-mcp-top-10/) — The dedicated security framework for MCP vulnerabilities - [AISecHub: MCP's First Year — 30 CVEs and 500 Server Scans](https://medium.com/ai-security-hub/mcps-first-year-what-30-cves-and-500-server-scans-tell-us-about-ai-s-fastest-growing-attack-6d183fc9497f) — February 2026. CVE breakdown, audit scores, and attack surface analysis -- [Trend Micro: MCP Security — Network-Exposed Servers](https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data) — 492 exposed servers with zero authentication +- [Trend Micro: MCP Security — Network-Exposed Servers](https://www.trendaisecurity.com/en-us/resources-insights/research/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data) — 492 exposed servers with zero authentication - [CyberArk: Poison Everywhere — No Output From Your MCP Server Is Safe](https://www.cyberark.com/resources/threat-research-blog/poison-everywhere-no-output-from-your-mcp-server-is-safe) — Full-schema poisoning beyond tool descriptions - [Snyk: Malicious MCP Server on npm — postmark-mcp](https://snyk.io/blog/malicious-mcp-server-on-npm-postmark-mcp-harvests-emails/) — First confirmed malicious MCP server in the wild - [Palo Alto Networks Unit 42: MCP Sampling Attack Vectors](https://unit42.paloaltonetworks.com/model-context-protocol-attack-vectors/) — Resource theft, conversation hijacking, covert invocation diff --git a/blog/moltbook-exposure-agent-tokens-no-gates.md b/blog/moltbook-exposure-agent-tokens-no-gates.md index 48e8918f..ce0e2ff3 100644 --- a/blog/moltbook-exposure-agent-tokens-no-gates.md +++ b/blog/moltbook-exposure-agent-tokens-no-gates.md @@ -41,7 +41,7 @@ The last two rows are where this stops being an ordinary leak. A leaked session Moltbook's headline number claimed 1.5 million [autonomous agents](/glossary#autonomous-agent). Wiz's investigation found ~17,000 humans operating them, with no verification distinguishing an autonomous agent from a human-driven bot fleet. The platform's unit of trust — "this is an agent, with karma" — measured nothing. -The industry response to problems like this is identity infrastructure: registries, agent credentials, signed agent cards. Those are necessary — an agent without provenance can't be governed at all, which is why [agent identity is not user identity](/blog/agent-identity-is-not-user-identity) argues for dedicated agent identities with owner mapping. But Moltbook shows the ceiling of identity alone: even perfect verification of *who* an agent is says nothing about *what it may do next, at whose expense*. Palo Alto Networks' [read of the case](https://www.paloaltonetworks.com/blog/network-security/the-moltbook-case-and-how-we-need-to-think-about-agent-security/) gets the framing right — identity, operating boundaries, and context integrity as three pillars. The operational question it leaves open is the one that matters in production: boundaries defined where, enforced by what, at which moment? A boundary that lives in a policy document is a wish. A boundary evaluated before each action is a control. +The industry response to problems like this is identity infrastructure: registries, agent credentials, signed agent cards. Those are necessary — an agent without provenance can't be governed at all, which is why [agent identity is not user identity](/blog/agent-identity-is-not-user-identity) argues for dedicated agent identities with owner mapping. But Moltbook shows the ceiling of identity alone: even perfect verification of *who* an agent is says nothing about *what it may do next, at whose expense*. Palo Alto Networks' [read of the case](https://www.paloaltonetworks.com/blog/ai-security/the-moltbook-case-and-how-we-need-to-think-about-agent-security/) gets the framing right — identity, operating boundaries, and context integrity as three pillars. The operational question it leaves open is the one that matters in production: boundaries defined where, enforced by what, at which moment? A boundary that lives in a policy document is a wish. A boundary evaluated before each action is a control. ### 2. One credential carried total authority @@ -83,14 +83,14 @@ The Moltbook exposure ended as well as these things can: found by researchers, p 1. [Wiz Research — Exposed Moltbook database reveals millions of API keys](https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys) — disclosure timeline, exposed data, root cause 2. [Fortune — Moltbook security concerns, with Karpathy and Marcus reactions](https://fortune.com/2026/02/02/moltbook-security-agents-singularity-disaster-gary-marcus-andrej-karpathy/) — February 2, 2026 -3. [Palo Alto Networks — The Moltbook case and how we need to think about agent security](https://www.paloaltonetworks.com/blog/network-security/the-moltbook-case-and-how-we-need-to-think-about-agent-security/) — identity / boundaries / context-integrity framing +3. [Palo Alto Networks — The Moltbook case and how we need to think about agent security](https://www.paloaltonetworks.com/blog/ai-security/the-moltbook-case-and-how-we-need-to-think-about-agent-security/) — identity / boundaries / context-integrity framing 4. [SecurityWeek — Permiso's analysis of Moltbook: bot-to-bot prompt injection and data leaks](https://www.securityweek.com/security-analysis-of-moltbook-agent-network-bot-to-bot-prompt-injection-and-data-leaks/) — live malicious activity, distinct from the database exposure ## Further Reading - [Agent Identity Is Not User Identity](/blog/agent-identity-is-not-user-identity) — why agents need dedicated, owner-mapped identities - [Least-Privilege API Keys for AI Agents](/blog/least-privilege-api-keys-for-ai-agents) — making a leaked credential a bounded liability -- [MCP Tool Poisoning: 84% Success Rate](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) — the tool-channel variant of the same gap +- [MCP-ITP: Target-Tool Calls Reached 84.2%](/blog/mcp-tool-poisoning-why-agent-frameworks-cant-prevent-it) — the controlled tool-channel benchmark - [Agent Skills Are the New Supply Chain](/blog/agent-skills-are-the-new-supply-chain) — governing what agents ingest and execute - [The State of AI Agent Incidents (2026)](/blog/state-of-ai-agent-incidents-2026) — the catalog this incident joins - [Agent Delegation Chains Need Authority Attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) — capping fleets by design diff --git a/blog/multi-agent-budget-control-crewai-autogen-openai-agents-sdk.md b/blog/multi-agent-budget-control-crewai-autogen-openai-agents-sdk.md index 6189797e..6cd75d8e 100644 --- a/blog/multi-agent-budget-control-crewai-autogen-openai-agents-sdk.md +++ b/blog/multi-agent-budget-control-crewai-autogen-openai-agents-sdk.md @@ -18,7 +18,7 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -A team builds a research pipeline using CrewAI with three agents: a Planner that breaks topics into sub-questions, a Researcher that investigates each one, and a Writer that synthesizes the results. The Planner delegates 5 sub-questions per topic to the Researcher. For complex sub-questions, the Researcher delegates down to a Deep Analyst agent that makes 15 LLM calls per investigation. In development, one topic costs ~$3.50. +Consider a constructed research pipeline with three agents: a Planner that breaks topics into sub-questions, a Researcher that investigates each one, and a Writer that synthesizes the results. The Planner delegates five sub-questions per topic to the Researcher. For complex sub-questions, the Researcher delegates to a Deep Analyst that makes 15 LLM calls per investigation. Under the illustrative assumptions below, one development topic costs about $3.50. In production, a batch of 40 topics kicks off overnight. The Researcher's delegation is non-deterministic — some topics trigger zero Deep Analyst calls, others trigger four. One topic causes all 5 sub-questions to delegate to the Deep Analyst, each triggering its own [tool loop](/glossary#tool-loop) with retries. That single topic costs $89. @@ -32,159 +32,106 @@ In production, a batch of 40 topics kicks off overnight. The Researcher's delega The Deep Analyst's cost is not linear in call count — each retry sends a longer context window, so later calls cost 3-5× more than early ones. That is why 190 calls cost $89, not $7. -The 40-topic batch: $1,740 instead of the projected $140. Most topics cost $15-30 because production topics are more complex than the development test set. The provider dashboard shows the total. It does not show which agent in the delegation chain caused the blowout, or that delegation depth was the problem. +Under the same constructed model, a 40-topic batch totals $1,740 instead of the projected $140. Provider attribution depends on the keys, projects, and tracing metadata the application supplies; a shared provider identity will not infer this agent hierarchy on its own. ## Why Delegation Chains Are Different from Fan-Out -[Fan-out](/blog/langgraph-budget-control-durable-execution-retries-fan-out) creates parallel branches from a single parent — the total cost is the sum of the branches. Delegation chains create serial depth — Agent A calls Agent B calls Agent C. The cost is multiplicative because each delegator's retry and loop behavior wraps around the entire subtree below it. +[Fan-out](/blog/langgraph-budget-control-durable-execution-retries-fan-out) creates parallel branches from a single parent. Delegation chains create serial depth—Agent A calls Agent B calls Agent C. Costs are still the sum of executed work, but a retry at a higher level can replay an entire lower subtree, causing the amount of work to grow rapidly with depth. If the Planner retries a failed topic, it re-executes the Researcher, which re-executes every Deep Analyst delegation. A single retry at the top of the chain replays every agent below it. This is the recursive version of the [retry storm pattern](/blog/ai-agent-failures-budget-controls-prevent) — except the blast radius grows with delegation depth, not retry count. | Property | [Fan-out](/glossary#fan-out) (parallel) | Delegation chain (serial depth) | |---|---|---| -| Cost structure | Additive — sum of branches | Multiplicative — product of depths | -| Concurrency risk | Branches race on shared budget | Child inherits parent's remaining budget | +| Cost structure | Sum of concurrently executed branches | Sum of nested work, with upper-level retries able to replay subtrees | +| Concurrency risk | Branches may contend for a shared budget | Nested calls may share an ancestor workflow budget when callers submit that scope | | Retry blast radius | One branch retries independently | Parent retries the entire child subtree | | Visibility | Branches visible at one graph level | Depth hidden inside opaque agent calls | -| Budget scoping | Sub-budgets per branch | Budget must flow DOWN with diminishing allocation | +| Budget scoping | Explicit branch/workflow scopes where the framework exposes them | Explicit workflow and agent ledgers; no automatic inheritance or transfer | ## The Delegation Tax: Framework by Framework -None of the major multi-agent frameworks enforce per-agent budgets. Each provides a delegation mechanism with no cost boundary between delegator and delegate. +Multi-agent frameworks expose different usage, termination, guardrail, and hook surfaces. Those controls should be used where they fit, but they do not automatically create Cycles ledgers or a shared cross-provider dollar boundary for each application-defined agent. ### CrewAI -Agents in a Crew can delegate tasks to other agents via `allow_delegation=True`. When Agent A delegates to Agent B, the framework creates a new task execution context. There is no budget boundary between them — they share the same API key and the same global execution. The Crew has no concept of "Agent B's budget." A delegated agent can make unlimited LLM calls because nothing in the framework tracks per-agent spend. +CrewAI supports delegation and exposes usage/operational controls that vary by version and configuration. A Cycles integration still needs to identify each protected model or tool call and submit the intended workflow and agent subjects; the framework does not provision those external budgets automatically. ### AutoGen -Multi-agent conversations use `GroupChat` or `initiate_chat()` chains. When an AssistantAgent sends work to another agent, the receiving agent runs its own LLM call loop. AutoGen tracks message counts but not token costs. The `max_consecutive_auto_reply` setting limits message rounds, not spend. A single reply that involves 5 tool calls and 5 LLM calls counts as 1 reply toward the limit — the cost inside that reply is invisible to the framework. +AutoGen APIs differ between its stable core/AgentChat packages and the legacy 0.2 `GroupChat`/`ConversableAgent` surface. Conversation termination controls such as legacy `max_consecutive_auto_reply` bound replies rather than an application-defined dollar amount. Use the current runtime's usage and termination features, then add an external budget boundary only where their semantics do not meet the requirement. ### OpenAI Agents SDK -The `handoff()` mechanism passes control from one agent to another. Each agent has its own system prompt and tool definitions. The SDK provides tracing via `RunContext` but no budget enforcement. A handoff chain of 3 agents, each making 10 tool calls, produces 30+ LLM calls with no per-agent ceiling. +The SDK's `handoff()` mechanism passes control to another agent. Current releases aggregate request and token usage across model calls, tool calls, and handoffs in the run context, and applications can use that usage to implement limits. That is useful, but it is not itself a pre-execution reservation against an external per-agent dollar ledger. -| Framework | Delegation mechanism | Built-in cost control | What's missing | +| Framework | Delegation mechanism | Useful native control | What a Cycles integration adds | |---|---|---|---| -| CrewAI | `allow_delegation=True` | None | Per-agent spend limit | -| AutoGen | `initiate_chat()`, `GroupChat` | `max_consecutive_auto_reply` (count, not cost) | Token/dollar cap per agent | -| OpenAI Agents SDK | `handoff()` | None (tracing only) | Pre-execution budget check | +| CrewAI | Crew/agent delegation | Framework usage and execution controls | External scoped budget for instrumented calls | +| AutoGen | Version-specific AgentChat/Core orchestration | Termination and runtime controls | External cumulative budget with caller-supplied scopes | +| OpenAI Agents SDK | `handoff()` / agents as tools | Aggregated request/token usage, hooks, and tool guardrails | Pre-execution reservation when the application requires it | -The common gap: these frameworks control execution flow. They do not control execution cost. That requires a [runtime authority](/blog/ai-agent-budget-control-enforce-hard-spend-limits) that sits between each agent and the LLM provider, making a deterministic allow/deny decision before every call. +The integration question is whether the native control is evaluated before the cost-bearing operation, uses the unit and scope your application needs, and remains consistent across every provider path. If not, a [runtime budget boundary](/blog/ai-agent-budget-control-enforce-hard-spend-limits) can fill that specific gap. -## The Pattern: Hierarchical Budget Allocation for Delegation Chains +## The Pattern: Explicit Shared and Per-Agent Ledgers -The [reserve-commit lifecycle](/blog/ai-agent-budget-control-enforce-hard-spend-limits) already solves single-agent budget enforcement. For multi-agent delegation, the same pattern applies — but budget must flow down the chain with diminishing allocations. +The [reserve-commit lifecycle](/blog/ai-agent-budget-control-enforce-hard-spend-limits) can apply to each protected call in a multi-agent run. Cycles does not transfer a parent balance to a child or infer the delegation graph. An operator provisions the intended workflow and agent budgets, and each integration submits the matching `Subject`. ``` -Run Budget: $25.00 -├── Planner: $2.00 (reserved from run) -├── Researcher (sub-question 1): $4.00 (reserved from run) -│ └── Deep Analyst: $2.00 (reserved from Researcher's allocation) -├── Researcher (sub-question 2): $4.00 -│ └── (no delegation — stays within $4.00) -├── Researcher (sub-question 3): $4.00 -│ └── Deep Analyst: $2.00 -├── Writer: $3.00 -└── Unallocated: $4.00 (safety margin) +Topic workflow budget: $25.00 ← shared ancestor ceiling +├── planner agent budget: $2.00 +├── researcher agent budget: $12.00 +├── deep-analyst agent budget: $4.00 +└── writer agent budget: $3.00 ``` +These are overlapping ceilings, not carved-out balances. A Deep Analyst call submitted with both the topic workflow and `deep-analyst` agent scopes consumes the matching workflow and agent ledgers atomically. + Three design principles make this work: -**Diminishing allocation.** Each delegation level gets a fraction of the parent's budget, not the full remaining balance. The Deep Analyst receives $2.00 carved from the Researcher's $4.00 — not $23.00 from the run's remaining budget. This bounds the blast radius of any single agent regardless of depth. +**Explicit child ceilings.** Provision smaller agent ledgers where deeper agents should have less room. An absent ledger is skipped; it is not a zero budget. If a path must be disabled, the host must deny it or the operator must provision an explicit zero allocation at the intended scope. -**Pre-delegation [reservation](/glossary#reservation).** Before Agent A delegates to Agent B, Agent A reserves the sub-budget from its own allocation. If Agent A's remaining budget cannot fund the delegation, the delegation does not happen — the agent receives a clear budget-exhausted signal and can take an alternative path. This is enforcement before the action, not observation after. +**Mandatory per-call integration.** Every cost-bearing child call must reserve before execution with the correct workflow and agent subjects. Creating a ledger does not intercept framework traffic on its own. -**Commit on return.** When the delegated agent completes, actual cost is committed and unused budget is released back to the parent. The Researcher reserved $2.00 for the Deep Analyst, but if the Deep Analyst only spent $1.30, the remaining $0.70 returns to the Researcher's pool. No budget is permanently locked. +**Per-call settlement.** A commit settles the caller-reported actual amount and releases any unused part of that call's estimate back to the same matching ledgers. It does not transfer a child allocation back to a parent. ## What This Looks Like in Practice -Cycles — a [runtime authority](/glossary#runtime-authority) for [autonomous agents](/glossary#autonomous-agent) — integrates with any multi-agent framework through a budget-scoped handler per agent. Each agent in the delegation chain gets its own `Subject` in the Cycles hierarchy, creating a hard limit that survives across framework boundaries. - -For CrewAI, attach a handler to each agent's LLM: - -```python -from langchain_openai import ChatOpenAI -from runcycles import CyclesClient, CyclesConfig, Subject -from budget_handler import CyclesBudgetHandler # see integration guide +The integration point depends on the framework version: -client = CyclesClient(CyclesConfig.from_env()) - -# Each agent gets a budget-scoped handler -def make_agent_llm(agent_name: str) -> ChatOpenAI: - handler = CyclesBudgetHandler( - client=client, - subject=Subject( - tenant="acme", - workflow="research-pipeline", - agent=agent_name, - ), - ) - return ChatOpenAI(model="gpt-4o", callbacks=[handler]) +| Framework | Candidate boundary | Cycles requirement | +|---|---|---| +| CrewAI | Each agent's configured model client and consequential tool wrapper | Submit a stable workflow ID and the actual agent role on every protected call | +| AutoGen | The current model-client or runtime middleware surface | Do not copy legacy `ConversableAgent` examples into a newer runtime without checking its API | +| OpenAI Agents SDK | Model/tool lifecycle hooks; use the published Cycles integration where it covers the call | Usage is observable in `RunContext`, but a hard external budget still needs a reservation before the protected operation | -planner_llm = make_agent_llm("planner") # bounded by planner's budget -researcher_llm = make_agent_llm("researcher") # bounded by researcher's budget -analyst_llm = make_agent_llm("deep-analyst") # bounded by analyst's budget -``` +The handler must build a `Subject` such as `tenant=acme`, `workflow=topic-123`, and `agent=deep-analyst` for each protected call. The corresponding workflow and agent budgets must already exist. A fictitious generic `CyclesBudgetHandler` is not part of the Cycles clients; use the framework-specific guide or implement the reserve/commit/release calls directly. -For AutoGen, attach the handler to each agent's underlying model: - -```python -from autogen import ConversableAgent - -# Each agent gets a budget-scoped LLM -researcher = ConversableAgent( - name="researcher", - llm_config={ - "model": "gpt-4o", - "callbacks": [CyclesBudgetHandler( - client=client, - subject=Subject( - tenant="acme", - workflow="research-pipeline", - agent="researcher", - ), - )], - }, -) -``` +See [Integrating Cycles with CrewAI](/how-to/integrating-cycles-with-crewai), [Integrating Cycles with AutoGen](/how-to/integrating-cycles-with-autogen), and [Integrating Cycles with OpenAI Agents](/how-to/integrating-cycles-with-openai-agents). Recheck those guides against the framework version in your lockfile. -For the OpenAI Agents SDK, intercept each `handoff()` boundary: - -```python -from runcycles import CyclesClient, CyclesConfig, Subject - -# Before handoff, reserve sub-budget from parent -def budget_handoff(parent_agent: str, child_agent: str, budget_usd: float): - client.reserve( - subject=Subject( - tenant="acme", - workflow="research-pipeline", - agent=child_agent, - ), - amount=int(budget_usd * 100_000_000), # USD to microcents - ) -``` +## What Explicit Ledgers Change -The key is that each agent's LLM calls are bounded independently. A [checker variable in application memory is not enough](/blog/vibe-coding-budget-wrapper-vs-budget-authority) — it does not survive process restarts, does not handle concurrent agents, and does not provide atomic reservation semantics. The runtime authority must be external to the framework. +For the constructed scenario, assume each topic has a unique workflow scope, all protected calls use conservative estimates, and the relevant workflow and agent ledgers are explicitly provisioned: -For the full callback handler implementation, see [Integrating Cycles with LangChain](/how-to/integrating-cycles-with-langchain). +| Scenario | Without the scoped boundary | With mandatory Cycles reservations | +|---|---|---| +| Deep Analyst enters tool loop | Calls continue until a framework/provider limit or application stop | First call whose estimate no longer fits the agent or workflow ledger is rejected | +| Planner retries failed delegation | A new execution can replay the child subtree | New child calls continue consuming the same explicit workflow and agent ceilings | +| 40-topic overnight batch | Illustrative total reaches $1,740 | Submitted estimates are bounded by $25 per topic when each topic has its own workflow ledger | +| Debugging which agent consumed budget | Shared provider identity requires trace reconstruction | Cycles balance/lifecycle records show submitted and settled amounts by configured subject | +| Non-deterministic delegation depth | Cost varies with executed work | Depth cannot consume beyond the configured estimated headroom without a denied reservation | -## What Happens Without Per-Agent Budgets +This bounds the [exposure](/glossary#exposure) represented by the submitted estimates. It does not prove a $1,000 provider-bill maximum unless estimates conservatively cover actual usage, every path is instrumented, and the selected commit-overage policy is acceptable. -The difference between debugging a $1,740 bill and preventing it. +## Framework sources -| Scenario | Without per-agent budget | With Cycles | -|---|---|---| -| Deep Analyst enters tool loop | 200+ calls, $89 per topic | Budget exhausted after ~15 calls, graceful denial | -| Planner retries failed delegation | Recreates entire child subtree at full cost | New sub-budget from parent's remaining allocation | -| 40-topic overnight batch | $1,740, discovered Monday morning | Each topic capped at $25, batch max = $1,000 | -| Debugging which agent overspent | Parse API logs, reconstruct delegation chain manually | Per-agent balance queries show where spend accumulated | -| Non-deterministic delegation depth | Cost variance of 25× between topics | Hard limit per agent regardless of delegation path | +Framework behavior was rechecked on July 24, 2026. APIs evolve, so use the documentation for the version in your lockfile: -The research pipeline from the opening scenario would have stopped at $25 per topic. The Deep Analyst's tool loop would have hit its $2.00 sub-budget after ~15 calls instead of running to 75+. The overnight batch of 40 topics would have cost at most $1,000 — bounded [exposure](/glossary#exposure) instead of an open-ended bill. +- [OpenAI Agents SDK usage tracking](https://openai.github.io/openai-agents-python/usage/) +- [OpenAI Agents SDK handoffs](https://openai.github.io/openai-agents-python/handoffs/) +- [AutoGen stable documentation](https://microsoft.github.io/autogen/stable/) +- [CrewAI documentation](https://docs.crewai.com/) ## Next steps diff --git a/blog/multi-agent-coordination-failure-structural-prevention.md b/blog/multi-agent-coordination-failure-structural-prevention.md index 59fe5547..40c1d0f6 100644 --- a/blog/multi-agent-coordination-failure-structural-prevention.md +++ b/blog/multi-agent-coordination-failure-structural-prevention.md @@ -57,7 +57,7 @@ The failures in the 44% bucket — the ones MAST called "system design" — are The structural answer is to make the boundaries enforceable rather than advisory. In Cycles, this is the scope hierarchy: every reservation derives a scope path like `tenant:acme/workspace:support/app:triage/workflow:refund/agent:executor/toolset:refunds`. Each node in that path is an independent budget boundary. An agent at one node can't reach budget allocated to a sibling node, no matter what its prompt says. Role overlap becomes materially bounded in the budget model — two agents trying to act on the same refund both need reservations against the `agent:executor` scope, and the budget ledger caps how much the overlap can cost even if both proceed. What the budget ledger does *not* do is prove that only one agent ultimately commits the refund against the downstream system; that still needs a workflow-level idempotency key or application-owned lock. Budget atomicity bounds the *authority*; business-object atomicity is still the application's job. For the full pattern, see [Agent Delegation Chains and Authority Attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation). -The other structural pattern is *authority attenuation*: the child agent's budget is a strict subset of the parent's, at the moment of delegation. A parent with $100 and access to `toolset:refund` + `toolset:email` can hand its child a sub-budget of $10 with access to `toolset:refund` only. A prompt-injected child can't escalate out of its parent's attenuated authority because the attenuation is enforced in the budget ledger, not the prompt. +The other structural pattern is *authority attenuation*: before enabling a child, the orchestrator restricts its actual tools and uses an explicitly provisioned child ledger with a smaller ceiling. A parent workflow with a $100 shared ledger can run a child whose matching agent ledger is $10 while the host exposes only refund tools. Cycles enforces the submitted overlapping budgets; it does not transfer the parent balance or create the tool restriction. ## Layer 2: protocol prevention (inter-agent misalignment) @@ -69,20 +69,20 @@ The pattern is that handoffs between agents need a *contract*, not just a messag **Verification gates.** The handoff is a checkpoint: did the previous agent satisfy the pre-conditions the next agent needs? Did it stay within its authority? Did it produce an output whose shape matches the downstream contract? MAST's "premature completion" failure mode — the 23% category — is exactly what happens when verification is the agent's own responsibility. Structural verification, run by the orchestrator rather than the agent, is what breaks that loop. -**Bounded coordination budgets.** The more agents that touch a task, the higher the coordination overhead: context tokens pass through each agent, each agent adds its own tokens to the running trace, and the total grows super-linearly in the chain depth. A separate budget — capped per workflow — for "coordination tokens" puts an explicit ceiling on how deep a delegation chain can go before the system intervenes. [DeepMind's "Towards a Science of Scaling Agent Systems"](https://arxiv.org/html/2512.08296v1) measured that unstructured multi-agent networks amplify errors up to 17.2× relative to a single-agent baseline, with diminishing returns beyond roughly four agents; the coordination-budget cap is how you operationalize that finding. +**Bounded coordination budgets.** The more agents that touch a task, the higher the coordination overhead: context tokens pass through each agent, and each agent adds tokens to the trace. A workflow budget can put an explicit resource ceiling on the resulting fan-out. In a controlled evaluation of 180 configurations, [Google Research](https://research.google/blog/towards-a-science-of-scaling-agent-systems-when-and-why-agent-systems-work/) found up to 17.2× error amplification for the independent-agent architecture, compared with 4.4× for centralized coordination. A budget does not fix those errors; it limits submitted resource consumption while architecture and validation controls address reliability. -Protocols live above the runtime, in the orchestration layer. Cycles provides the scaffolding — each agent's reservation emits events, each commit ties back to a scope path, each handoff can be audited — but the contract itself is the orchestrator's responsibility. The runtime enforces that the contract was possible; the orchestrator enforces that it was met. +Protocols live above the runtime, in the orchestration layer. Cycles can record supported reservation and settlement events tied to submitted scopes, but a handoff contract, its authorization, and its outcome remain the orchestrator's responsibility. ## Layer 3: runtime prevention (verification + backstop) The third category — the ~24% of failures that are task-verification issues — is the one where a correctly-architected, correctly-protocoled agent chain still produces a wrong answer, and no internal check catches it. This is the "200 OK" category: the system looks healthy at every layer but the output is wrong. -Prompt-level verification is brittle here for the same reason it's brittle in the first two layers: the verifier is another LLM call, subject to the same failure modes as the agent it's verifying. The Cloud Security Alliance's industry analysis ["Fixing AI Agent Delegation for Secure Chains"](https://cloudsecurityalliance.org/articles/control-the-chain-secure-the-system-fixing-ai-agent-delegation) documents exploit patterns ("Agent Session Smuggling," "Cross-Agent Privilege Escalation") where a compromised sub-agent silently rewrites the verification state — every agent in the chain looks fine in isolation. Industry write-ups like the CSA's aren't peer-reviewed research, but the documented exploit classes are real and worth designing against. +Prompt-level verification is brittle here for the same reason it's brittle in the first two layers: the verifier is another LLM call, subject to the same failure modes as the agent it's verifying. The Cloud Security Alliance's industry analysis ["Fixing AI Agent Delegation for Secure Chains"](https://cloudsecurityalliance.org/blog/2026/03/25/control-the-chain-secure-the-system-fixing-ai-agent-delegation) documents exploit patterns ("Agent Session Smuggling," "Cross-Agent Privilege Escalation") where a compromised sub-agent silently rewrites the verification state — every agent in the chain looks fine in isolation. Industry write-ups like the CSA's aren't peer-reviewed research, but the documented exploit classes are real and worth designing against. What works at this layer is *external enforcement that doesn't trust the agents*. Three backstops: -- **Pre-execution budget enforcement.** The reserve-commit pattern means every tool call, model invocation, or delegation request runs a pre-execution check against the budget ledger. A compromised agent can't just keep calling tools until it runs out of money — each call must reserve first, and reservations are denied once the budget is exhausted. See [AI Agent Budget Control: Enforce Hard Spend Limits](/blog/ai-agent-budget-control-enforce-hard-spend-limits) for the mechanics. -- **Action authority via risk points.** Not all actions are equal — a read is cheap, a refund is expensive, a `rm -rf` is terminal. Cycles tracks a separate `RISK_POINTS` budget per toolset, so an agent can run out of "dangerous action" budget before it runs out of "LLM call" budget. This is what prevents a compromised agent from using remaining model budget to rack up catastrophic side effects. +- **Pre-execution budget enforcement.** Put a mandatory reserve-commit boundary on each protected tool or model path. The first submitted estimate that no longer fits the matching ledgers is rejected. Delegation itself is not automatically budgeted, and any path that bypasses the boundary remains outside this control. See [AI Agent Budget Control: Enforce Hard Spend Limits](/blog/ai-agent-budget-control-enforce-hard-spend-limits) for the mechanics. +- **Caller-assigned exposure via risk points.** Not all actions are equal—a read, refund, and destructive shell command have different consequences. A mandatory host boundary can reserve from a separate `RISK_POINTS` budget per toolset before an authorized action. That bounds the cumulative exposure the host submits; authentication, tool authorization, argument validation, and sandboxing remain separate controls. - **Graceful degradation paths.** When a reservation is denied, the agent has a defined fallback: downgrade model, narrow capability, queue for human review, or stop. [When Budget Runs Out: Graceful Degradation Patterns](/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents) covers the decision matrix. The runtime layer is deliberately not the first layer of defense — architecture and protocol should catch most failures before they hit runtime. But it's the only layer that still works when the first two have been bypassed, which is why it's the one worth building hard enforcement against. @@ -99,7 +99,7 @@ Every major multi-agent framework ships some subset of these prevention layers. | **Semantic Kernel** | Per-agent tool scoping (loose) | App-defined | App-defined | App-defined | | **MS Agent Framework** | Checkpointing; scope supported, not enforced | Five built-in orchestration patterns | App-defined | App-defined | -All five do the orchestration well. What none of them ship natively is pre-execution budget enforcement at the scope boundary — it's solvable at the application layer in each of them, but nothing stops a prompt-injected sub-agent from draining the parent's budget before the orchestrator notices unless that enforcement has been explicitly wired in. This isn't a criticism of the frameworks — orchestration is a genuinely hard problem and they're solving it well — but it's why a coordination-failure incident at 2 AM usually bottoms out in the budget layer, not the orchestration layer. +Framework capabilities vary by version: several expose usage, termination, guardrail, or rate controls that applications should use. An external Cycles boundary is relevant when those native controls do not provide the required pre-execution cumulative budget across the caller's scopes and providers. The integration must be explicit; no budget service can contain traffic it never sees. The pattern teams converge on in practice is framework *plus* a runtime authority layer: use CrewAI or LangGraph for the orchestration, delegate the budget boundary enforcement to a dedicated layer like Cycles. See [Multi-Agent Budget Control: CrewAI, AutoGen, OpenAI Agents SDK](/blog/multi-agent-budget-control-crewai-autogen-openai-agents-sdk) for the integration patterns. @@ -140,7 +140,7 @@ The frameworks do good work at one or two of these layers. Holding the third — - [When Budget Runs Out: Graceful Degradation Patterns](/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents) — fallback patterns when a reservation is denied - [UC Berkeley MAST study](https://arxiv.org/abs/2503.13657) — 1,600+ trace annotated multi-agent failure taxonomy - [SEMAP: structured protocols for multi-agent coordination](https://arxiv.org/abs/2510.12120) — 69.6% failure reduction via explicit contracts -- [Cloud Security Alliance: Fixing AI Agent Delegation for Secure Chains](https://cloudsecurityalliance.org/articles/control-the-chain-secure-the-system-fixing-ai-agent-delegation) — industry analysis with documented delegation-chain exploit patterns +- [Cloud Security Alliance: Fixing AI Agent Delegation for Secure Chains](https://cloudsecurityalliance.org/blog/2026/03/25/control-the-chain-secure-the-system-fixing-ai-agent-delegation) — industry analysis with documented delegation-chain exploit patterns - [DeepMind: Towards a Science of Scaling Agent Systems](https://arxiv.org/html/2512.08296v1) — error amplification across unstructured multi-agent networks ## Related how-to guides diff --git a/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation.md b/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation.md index a97a17fd..1e2ccf46 100644 --- a/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation.md +++ b/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation.md @@ -18,7 +18,7 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -A platform team runs a SaaS product with AI-powered document analysis. Fifty customers share the same infrastructure. One afternoon, a single customer's integration triggers an agent loop — the same 200-page PDF reprocessed 40 times with increasingly long context windows. In three hours, that one [tenant](/glossary#tenant) consumes $4,200 of the platform's $5,000 monthly provider budget. +Consider a SaaS product with AI-powered document analysis and 50 customers on shared infrastructure. One customer's integration reprocesses the same 200-page PDF 40 times with growing context. Without tenant-scoped enforcement, that [tenant](/glossary#tenant) can consume a disproportionate share of the platform's monthly provider budget; the exact cost depends on model, tokens, and retries. The other 49 customers start seeing failures. Model calls return rate-limit errors. Jobs queue indefinitely. The platform's shared spending cap — set at the provider level — does not distinguish between customers. It just shuts everything down when the ceiling is reached. @@ -46,17 +46,17 @@ The common thread: **one tenant's behavior affects every other tenant's experien It also makes billing and trust harder. When a customer asks "why did my costs spike?" and the answer is "because another customer's agent ran away," you have a credibility problem. Customers need predictable usage envelopes — knowing that their allocation is theirs, regardless of what other tenants do. -## Why Provider-Level Caps Fail in Multi-Tenant Systems +## Where Provider Controls Stop in Multi-Tenant Systems -Every major AI provider offers some form of spending limit — OpenAI monthly caps, Anthropic usage tiers, AWS Bedrock service quotas. These controls are designed for single-account governance. They sit at the wrong level of the stack for multi-tenant platforms. +Major providers offer different controls: OpenAI has soft spend alerts and optional hard monthly organization/project limits, Anthropic uses prepaid credits and usage tiers with workspace reporting, and AWS Bedrock exposes capacity quotas plus separate billing controls. These remain useful outer boundaries. -**No per-customer isolation.** A $10,000 monthly cap on your OpenAI organization applies to all tenants combined. There is no mechanism to say "Tenant A gets $500, Tenant B gets $200, Tenant C gets $1,000." The cap is a single number shared by everyone. +**No automatic customer mapping.** A $10,000 hard limit on one shared OpenAI project applies to all application tenants using that project. A platform can create separate provider projects, workspaces, or keys where supported, but it must maintain that mapping; the provider does not infer "Tenant A gets $500, Tenant B gets $200." -**No per-workflow or per-run boundaries.** Provider caps do not know what a "workflow" or "run" is in your system. They cannot limit a single agent execution to $25, or cap a particular feature at $100/month per customer. The granularity stops at the account or project level. +**No automatic workflow or run identity.** Provider controls operate on vendor identities, billing windows, credits, or quotas. A shared identity does not express a $25 application run or a $100/month feature budget unless the platform creates and enforces that mapping. -**Reactive, not preventive.** Most provider caps operate on billing cycles. They tell you what happened; they do not block the next model call in real time. By the time the cap triggers, the damage is done — and it affects everyone. +**Different timing and fallback semantics.** Alerts are reactive; credit, quota, and hard-limit controls can reject requests. OpenAI documents that hard spend-limit enforcement is not instantaneous, and no provider knows which application-specific fallback is safe. -The structural problem is clear: provider caps protect the provider's [exposure](/glossary#exposure) to you, not your exposure to individual customers. For multi-tenant AI platforms, the enforcement boundary must exist **per customer, inside your runtime**. This is the problem [Cycles](/) was built to solve — [runtime authority](/glossary#runtime-authority) as infrastructure, enforced before execution, scoped to each tenant. +The structural gap is the application's own customer hierarchy. A multi-tenant platform needs a per-customer boundary in its runtime unless it is willing to mirror every tenant and workflow into provider identities. Cycles supplies shared budget state for caller-submitted tenant and subject scopes; the application must instrument every protected path. ## What Per-Tenant Budget Enforcement Looks Like @@ -65,7 +65,7 @@ Per-tenant enforcement means treating each customer as an independent budget sco The core behavior is simple: 1. **Each tenant gets a defined budget** — $500/month, 1M [tokens](/glossary#tokens)/day, whatever matches your pricing model -2. **Every agent action reserves budget from the tenant's scope** before execution — not from a shared pool +2. **Every protected agent path submits the tenant scope** and reserves before execution 3. **When a tenant's budget is exhausted, that tenant is denied** — their agents stop or degrade 4. **Other tenants are completely unaffected** — their budgets, their [reservations](/glossary#reservation), their agent executions continue normally @@ -96,10 +96,10 @@ When an agent makes a reservation, the system checks every applicable ledger amo This means: -- A single run cannot exceed its $25 ceiling, even if the tenant has $1,500 remaining -- An agent cannot exceed its allocation, even if the workflow has capacity -- A workflow cannot exceed its share of the tenant budget -- The tenant cannot exceed their overall limit, regardless of how budget is distributed internally +- A run's submitted reservations cannot oversubscribe its $25 ledger even if the tenant ledger has room +- An agent's submitted reservations cannot oversubscribe its ledger even if the workflow ledger has room +- A workflow ledger can bound submitted reservations across its instrumented agents +- A tenant ledger can bound submitted reservations across instrumented descendant paths Scopes compose naturally. You do not need to implement enforcement at every level on day one. Start with tenant budgets for isolation, then add run-specific standard-field scopes for execution safety, and layer in product-workflow or agent budgets as your model matures. The [modeling guide](/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles) covers the trade-offs involved in choosing which standard field carries a run identifier. @@ -110,7 +110,7 @@ Multi-tenant cost control requires two complementary mechanisms: **budgets** and | | Budget | Quota | |--|--------|-------| | **What it controls** | Total economic exposure (dollars, tokens) | Policy boundaries (counts, rates, access) | -| **How it works** | Real-time balance with reserve/commit | Policy rule checked at request time | +| **How it works** | Real-time balance with reserve-commit | Policy rule checked at request time | | **Enforcement** | Atomic — tracks cumulative spend | Stateless or counter-based — tracks occurrences | | **Example** | "$500/month for this tenant" | "Max 100 agent runs per day" | | **What it prevents** | Overspend, runaway costs | Abuse, resource hogging, plan enforcement | @@ -145,15 +145,15 @@ Per-tenant budget enforcement, combined with quotas, delivers concrete operation Each copilot session runs within a per-session budget nested inside the monthly account budget. The [three-way decision model](/protocol/caps-and-the-three-way-decision-model-in-cycles) can carry operator-configured caps such as lower token limits through `ALLOW_WITH_CAPS`. The application must select and enforce that degradation policy; the current server does not tighten caps automatically as the session balance falls. -**Agent platform with per-run ceilings.** A development tools company offers AI agent pipelines that customers configure and run. Each pipeline execution gets a per-run budget based on the pipeline type: code review at $5/run, deep analysis at $30/run, simple chat at $1/run. The tenant's monthly budget caps total spend across all runs. A customer running 200 code reviews in a month spends up to $1,000 — and no more, even if their agents loop. +**Agent platform with per-run ceilings.** A development tools company offers AI agent pipelines that customers configure and run. Each pipeline execution maps its run ID to a unique workflow ledger: code review at $5/run, deep analysis at $30/run, simple chat at $1/run. A separate tenant ledger constrains aggregate submitted usage. Exact maximum settled spend still depends on mandatory coverage and the configured commit overage policy, so size strict estimates conservatively. -**Enterprise customer with departmental sub-budgets.** A large enterprise tenant gets $10,000/month. Their IT team allocates sub-budgets by workspace: Engineering gets $5,000, Marketing gets $2,000, Support gets $3,000. Each department's agents draw from their own workspace scope. When Marketing's budget is exhausted mid-month, Engineering and Support are unaffected. The enterprise admin can reallocate budget between workspaces via the [admin API](/how-to/budget-allocation-and-management-in-cycles) without involving the platform operator. +**Enterprise customer with departmental ledgers.** In this illustrative structure, a tenant has a $10,000 monthly ledger and explicit workspace ledgers set to Engineering $5,000, Marketing $2,000, and Support $3,000. Each protected call consumes the matching tenant and workspace ledgers. When Marketing's workspace ledger is exhausted, its next reservation fails while the other workspace ledgers can retain capacity. Operators can adjust allocations through separately authorized [admin API](/how-to/budget-allocation-and-management-in-cycles) mutations; Cycles does not transfer balances automatically. ## Rolling It Out -You do not need the full hierarchy on day one. The proven path for multi-tenant platforms: +You do not need the full hierarchy on day one. A practical sequence for multi-tenant platforms: -1. **Start with tenant-level budgets.** This is the highest-leverage change — it creates isolation between customers. Every customer gets a defined ceiling. One tenant's behavior can no longer affect others. Start here even if the limits are generous. +1. **Start with tenant-level budgets.** This creates budget isolation between customers on mandatory instrumented paths. It does not isolate shared provider rate limits, credentials, data, or compute capacity; use separate controls for those resources. 2. **Add run-level budgets next.** Per-run caps are the best defense against runaway execution — loops, [retry storms](/glossary#retry-storm), and recursive tool calls. They protect both the tenant and the platform from a single bad execution. @@ -161,7 +161,7 @@ You do not need the full hierarchy on day one. The proven path for multi-tenant 4. **Layer in workflow and agent budgets.** As your product matures, add scopes that match your product model — per-workflow caps for different features, per-agent budgets for multi-agent systems, per-workspace budgets for enterprise customers. -5. **Differentiate by plan tier.** Map your pricing model directly to budget and quota configurations. Free, Pro, and Enterprise plans get different limits enforced at the same infrastructure layer. +5. **Differentiate by plan tier.** Map your pricing model to budget configurations. If you also use an external quota system, keep its plan limits aligned; the current v0.1.25 reference server does not implement the v0.1.26 action-quota preview. For teams introducing enforcement to an existing system, [shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) returns what would be denied without blocking. Log those non-persisting responses in the application, alongside actual outcomes, to size budgets before enabling hard enforcement. diff --git a/blog/openai-agents-sdk-has-guardrails-for-content-but-nothing-for-actions.md b/blog/openai-agents-sdk-has-guardrails-for-content-but-nothing-for-actions.md index 155e3b82..8bca23f2 100644 --- a/blog/openai-agents-sdk-has-guardrails-for-content-but-nothing-for-actions.md +++ b/blog/openai-agents-sdk-has-guardrails-for-content-but-nothing-for-actions.md @@ -2,7 +2,7 @@ title: "OpenAI Agents SDK Guardrails vs Action Control" date: 2026-03-30 author: Albert Mavashev -tags: [openai, agents, runtime-authority, governance, risk, actions, python, RunHooks] +tags: [openai, agents, runtime-authority, governance, risk, action-control, python, RunHooks] description: "OpenAI Agents SDK tool guardrails validate individual function tools. They aren't cross-tenant budget or risk authority — RunHooks is where that fits." blog: true sidebar: false @@ -38,7 +38,7 @@ What none of those primitives provide is cross-cutting [runtime authority](/glos The gap has three dimensions: -**Cost.** There are no spending limits. A tenant running a support agent and a tenant running an analytics pipeline share the same unlimited OpenAI budget. If one tenant's agent enters a retry loop, the entire account pays for it. Provider-level spending caps are account-wide and may react too slowly — by the time they trigger, the damage is done. +**Cost.** The Agents SDK does not provide a cross-tenant spend ledger. OpenAI's API platform now offers monthly spend alerts and optional hard limits at organization and project scope, but it does not infer application tenants or agent runs within a shared project. OpenAI also documents that hard-limit enforcement is not instantaneous. Per-tenant, per-run, or shorter-window controls therefore require separate provider projects or an application/gateway boundary. **Risk.** Tool guardrails let you write a custom validator per function tool, but there's no first-class concept of a risk level or an authorization threshold, and no shared ledger that tallies cumulative risk across the run. `search_knowledge_base` and `send_email` have to be policed by independently maintained guardrail code; nothing tracks "this agent has already burned its risk budget for the session." diff --git a/blog/openai-agents-tool-guardrails-vs-runtime-budgets.md b/blog/openai-agents-tool-guardrails-vs-runtime-budgets.md index 5703ec5d..b12c28fe 100644 --- a/blog/openai-agents-tool-guardrails-vs-runtime-budgets.md +++ b/blog/openai-agents-tool-guardrails-vs-runtime-budgets.md @@ -89,7 +89,7 @@ In OpenAI Agents SDK workflows, the budget boundary usually appears in four plac **Agent handoffs.** Handoffs change who is acting, but they should not create unbounded authority. If a parent agent delegates work to a specialist, the delegated agent should receive bounded budget and bounded action authority, not a blank check. -Cycles uses the OpenAI Agents SDK `RunHooks` integration point to apply a reserve-commit lifecycle across model calls, tool invocations, and handoffs. The [OpenAI Agents integration guide](/how-to/integrating-cycles-with-openai-agents) covers the implementation details; this post is about the architecture decision. +The Cycles integration uses the OpenAI Agents SDK `RunHooks` interface to reserve and settle configured model calls and tool invocations. Handoffs are not reservations: the plugin attempts to record them as zero-amount direct-debit events, and an event failure does not block the handoff. The [OpenAI Agents integration guide](/how-to/integrating-cycles-with-openai-agents) covers the implementation details; this post is about the architecture decision. ## A practical rollout sequence diff --git a/blog/openai-api-budget-limits-per-user-per-run-per-tenant.md b/blog/openai-api-budget-limits-per-user-per-run-per-tenant.md index 3a50facd..44d14b02 100644 --- a/blog/openai-api-budget-limits-per-user-per-run-per-tenant.md +++ b/blog/openai-api-budget-limits-per-user-per-run-per-tenant.md @@ -16,34 +16,34 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -A team runs 30 OpenAI-powered agents across their platform. They set a $5,000/month spending cap in the OpenAI dashboard. On Thursday, one customer's research agent enters a retry loop — expanding its context window on each attempt, calling GPT-4o 200+ times in under an hour. The bill: $1,400 for a single agent run. +A team runs 30 OpenAI-powered agents across a shared project. In this constructed scenario, they configure a $5,000 monthly project limit. One customer's research agent enters a retry loop and consumes $1,400 before the project reaches that broader boundary. -The org-level cap? Still at 60%. It does not trigger. The problem is not that the cap was wrong. The problem is that it applies to the entire organization. There is no way to say "this user gets $20 per day" or "this run cannot exceed $5" through OpenAI's billing controls alone. +The problem is scope, not the existence of a provider control. OpenAI now offers both soft spend alerts and optional hard monthly limits at organization and project scope. A hard limit rejects affected traffic with `429 insufficient_quota` after tracked spend reaches the amount, but enforcement is not instantaneous. Neither an organization nor a shared project limit automatically says "this user gets $20 per day" or "this run may reserve $5." ## The Granularity Gap in OpenAI Spending Controls -OpenAI provides three levels of spending controls: organization monthly caps, project-level budgets, and usage tier limits. These are designed to protect OpenAI's billing relationship with you. They are not designed to protect you from individual users, individual agent runs, or individual [tenants](/glossary#tenant) running up the bill. +OpenAI provides monthly spend alerts and optional hard spend limits at organization and project scope, plus an OpenAI-assigned monthly usage limit and configurable model rate limits. These controls are useful provider-side boundaries. They do not automatically model individual users, agent runs, or application [tenants](/glossary#tenant) sharing the same project. Here is the gap: -- **Org monthly cap** — one number for everything. If you need 200 users each limited to $10/day, there is no way to express that. The cap fires when the org-wide total crosses the threshold, not when any individual crosses theirs. -- **Project budgets** — closer to useful, but project boundaries do not map to user boundaries or run boundaries. A project can contain thousands of agent runs, and the budget does not distinguish between them. -- **Usage tiers** — rate-limiting mechanisms tied to your account's trust level. They throttle requests per minute, not spend per user or per run. +- **Organization spend limit** — can alert or reject affected traffic at a monthly organization threshold, but it does not distinguish users or runs inside that organization. +- **Project spend limit** — can provide a hard monthly project boundary, but a shared project may contain thousands of agent runs and does not infer their identities. +- **Usage tiers and model rate limits** — constrain approved monthly usage or request/token throughput, not application spend per user or run. -The common thread: these controls protect OpenAI's [exposure](/glossary#exposure) to you. They do not protect your exposure to individual actors within your system. +The common thread is that these controls use OpenAI organization, project, and model identities. Application actors need an explicit mapping or a separate runtime boundary. | Control | Granularity | Blocks next call? | Prevents runaway agent? | |---|---|---|---| -| OpenAI org monthly cap | Entire organization | No (billing-cycle reactive) | No | -| OpenAI project budget | Per project | Partially (delayed enforcement) | Not per-run | -| OpenAI usage tier | Account level | No (soft rate limit) | No | -| **Per-user daily budget** | Individual user | **Yes (pre-execution)** | **Yes** | -| **Per-run cap** | Single agent execution | **Yes (pre-execution)** | **Yes** | -| **Per-tenant monthly limit** | Customer / team | **Yes (pre-execution)** | **Yes** | +| OpenAI spend alert | Organization or project | No; traffic continues | Not by itself | +| OpenAI hard spend limit | Organization or project | Yes, with non-instantaneous enforcement | Only when a run has its own project boundary | +| OpenAI usage/rate limit | Organization/project/model | Yes for the applicable quota | Not a spend budget per run | +| **Application per-user budget** | Individual user | **Yes at the mandatory integration boundary** | **Yes for instrumented calls** | +| **Application per-run budget** | Single agent execution | **Yes at the mandatory integration boundary** | **Yes for instrumented calls** | +| **Application per-tenant budget** | Customer / team | **Yes at the mandatory integration boundary** | **Yes for instrumented calls** | -The bottom three rows require enforcement outside of OpenAI — a [runtime authority](/glossary#runtime-authority) that sits between your application and the API, making a deterministic allow/deny decision before every call. For the general argument about why post-hoc controls fail, see [AI Agent Budget Control: Enforce Hard Spend Limits](/blog/ai-agent-budget-control-enforce-hard-spend-limits). For how this compares to other tools in the stack, see [Cycles vs LLM Proxies and Observability Tools](/blog/cycles-vs-llm-proxies-and-observability-tools). +The bottom three rows require an application identity and enforcement boundary outside OpenAI. A Cycles integration can make a deterministic budget decision on the submitted estimate before each instrumented call. For the general argument about why post-hoc controls fail, see [AI Agent Budget Control: Enforce Hard Spend Limits](/blog/ai-agent-budget-control-enforce-hard-spend-limits). For how this compares to other tools in the stack, see [Cycles vs LLM Proxies and Observability Tools](/blog/cycles-vs-llm-proxies-and-observability-tools). ## Three Budget Patterns for OpenAI Agents @@ -117,11 +117,11 @@ For full implementation recipes with admin API setup, reset schedules, and budge ## Why Reserve-Commit Works with OpenAI's Token-Based Billing -OpenAI charges per token, and the challenge is that you do not know the exact output token count before the call. You know your input. You set `max_tokens` for the output ceiling. But the actual output could be 50 [tokens](/glossary#tokens) or 4,000 tokens. You need to reserve budget before the call — and you need to not permanently lose the difference. +OpenAI charges per token, and the challenge is that you do not know the exact output token count before the call. You know your input and configure an output ceiling using the field supported by the chosen API and model, but actual output may be much smaller. A reservation can hold the conservative estimate before the call and return unused capacity at settlement. The reserve-commit lifecycle solves this: -1. **Estimate** — calculate worst-case cost using input token count and `max_tokens`: +1. **Estimate** — calculate conservative cost using input token count and the configured output ceiling. This illustrative GPT-4o Chat Completions calculation uses its published list rates; always recheck current or contracted pricing: ``` input_cost = 2,000 input tokens × 250 microcents = 500,000 @@ -141,11 +141,11 @@ actual = 2,000 × 250 + 600 × 1,000 = 1,100,000 microcents 5. **Release** — the 424,000 microcent difference is returned to the budget pool automatically. -This pattern works for every OpenAI model — GPT-4o, GPT-4o-mini, o3, o3-mini, GPT-4.1, GPT-4.1-mini, GPT-4.1-nano — because the lifecycle is model-agnostic. Only the pricing constants change. For the full pricing table in [USD_MICROCENTS](/glossary#usd-microcents), see [Cost Estimation Cheat Sheet](/how-to/cost-estimation-cheat-sheet). For the production decorator pattern with `tiktoken`, metrics reporting, and caps handling, see [Integrating Cycles with OpenAI](/how-to/integrating-cycles-with-openai). For the protocol-level mechanics, see [How Reserve/Commit Works](/protocol/how-reserve-commit-works-in-cycles). +The lifecycle is model-agnostic, but pricing, token counting, output fields, cached-input discounts, and reasoning usage vary by model and API. For the pricing table in [USD_MICROCENTS](/glossary#usd-microcents), see [Cost Estimation Cheat Sheet](/how-to/cost-estimation-cheat-sheet). For the production decorator pattern with metrics reporting and caps handling, see [Integrating Cycles with OpenAI](/how-to/integrating-cycles-with-openai). For the protocol mechanics, see [How Reserve/Commit Works](/protocol/how-reserve-commit-works-in-cycles). ## Combining Patterns: The Hierarchical Budget Stack -The three patterns are not mutually exclusive. They compose. A single OpenAI API call can be checked against per-user, per-run, and per-tenant budgets simultaneously, because the scope hierarchy enforces at every level: +The three patterns are not mutually exclusive. They compose when the operator explicitly provisions the corresponding ledgers. A single instrumented OpenAI call can then be checked against per-user, per-run, and per-tenant scopes atomically: ``` Tenant: customer-a ($500/month) @@ -164,27 +164,34 @@ Each scope catches a different category of failure. The hierarchy ensures that n ## What Happens When Budget Runs Out -An OpenAI call gets denied — then what? A hard stop is one option, but not the only one. +An OpenAI call's budget reservation is rejected — then what? A hard stop is one option, but not the only one. -Cycles returns a [three-way decision](/glossary#three-way-decision), not a binary allow/deny: +A non-locking `decide` preflight or dry-run evaluation returns the [three-way decision](/glossary#three-way-decision). A live reservation succeeds with `ALLOW` or configured `ALLOW_WITH_CAPS`, or returns an error such as `409 BUDGET_EXCEEDED`: | Decision | What it means | What to do | |---|---|---| | `ALLOW` | Full budget available | Call OpenAI normally | | `ALLOW_WITH_CAPS` | The deepest matching budget has configured caps | Apply the caps — e.g., reduce `max_tokens` to `caps.max_tokens` | -| `DENY` | Budget exhausted | Do not call OpenAI — degrade, defer, or inform the user | +| `DENY` | Preflight predicts rejection | Do not submit a live call, or use the result only for shadow analysis | -The `ALLOW_WITH_CAPS` decision is particularly useful for OpenAI integrations. When the runtime authority returns `caps.max_tokens: 500`, the agent passes that directly to OpenAI's `max_tokens` parameter. The model generates a shorter response — still useful, but cheaper. The user gets an answer instead of an error. +The `ALLOW_WITH_CAPS` decision can carry a configured generic `caps.max_tokens` value. The host must map it to the correct OpenAI field for the selected surface, such as `max_output_tokens` on Responses or the applicable completion-token field, and validate it against model requirements. The current Cycles server does not introduce or tighten that cap automatically as balance falls. Beyond caps, four degradation strategies apply to OpenAI workloads: -- **Downgrade** — switch from GPT-4o ($10/M output) to GPT-4o-mini ($0.60/M output). A 16x cost reduction with a quality trade-off the user may not even notice for many tasks. +- **Downgrade** — route to a cheaper model after validating the quality trade-off for the workload. Model prices and availability change, so keep the route table tied to current provider data. - **Disable** — turn off tool use or retrieval augmentation. The model answers from its own knowledge instead of making additional API calls. - **Defer** — queue the request for a later budget window. Useful for batch processing and non-urgent tasks. - **Deny** — stop entirely. The right choice when partial results are worse than no results, or when the action has irreversible consequences. For the full degradation strategy guide, see [Degradation Paths: Deny, Downgrade, Disable, or Defer](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer). For the protocol reference, see [Caps and the Three-Way Decision Model](/protocol/caps-and-the-three-way-decision-model-in-cycles). +## OpenAI sources + +OpenAI control behavior was rechecked on July 24, 2026: + +- [Spend limits](https://developers.openai.com/api/docs/guides/spend-limits) — soft alerts, optional hard organization/project limits, `429 insufficient_quota`, and non-instantaneous enforcement +- [Managing projects](https://help.openai.com/en/articles/9186755-managing-projects-in-the-api-platform) — project identities, model access, rate limits, and spend-limit configuration + ## Next steps - **[Integrating Cycles with OpenAI](/how-to/integrating-cycles-with-openai)** — production integration with the `@cycles` decorator, `tiktoken`, and caps handling diff --git a/blog/openclaw-budget-guard-first-week-dry-run-to-production.md b/blog/openclaw-budget-guard-first-week-dry-run-to-production.md index 137f0380..f56f758c 100644 --- a/blog/openclaw-budget-guard-first-week-dry-run-to-production.md +++ b/blog/openclaw-budget-guard-first-week-dry-run-to-production.md @@ -45,7 +45,7 @@ Start with the smallest config that produces useful data: "enableEventLog": true, "analyticsWebhookUrl": "https://analytics.example.com/openclaw-sessions", "logLevel": "info", - "defaultModelName": "anthropic/claude-sonnet-4-20250514" + "defaultModelName": "anthropic/claude-sonnet-4-6" } } } @@ -70,8 +70,8 @@ At `agent_end`, the plugin builds a `SessionSummary`, attaches the full object t "remaining": 850000000, "spent": 150000000, "costBreakdown": { - "model:anthropic/claude-sonnet-4-20250514": { "count": 22, "totalCost": 66000000 }, - "model:anthropic/claude-opus-4-20250514": { "count": 4, "totalCost": 60000000 }, + "model:anthropic/claude-sonnet-4-6": { "count": 22, "totalCost": 66000000 }, + "model:anthropic/claude-opus-4-8": { "count": 4, "totalCost": 20000000 }, "tool:web_search": { "count": 12, "totalCost": 12000000 }, "tool:code_execution": { "count": 3, "totalCost": 6000000 }, "tool:read_file": { "count": 18, "totalCost": 1800000 } @@ -97,12 +97,12 @@ Two things to do with this: The integration guide gives the baseline band: `"External API tools (web search, code execution) typically cost 500K-1M. Lightweight tools (text formatting, math) cost 10K-50K."` Start there unless your sandbox provider, timeout, or container lifecycle makes code execution materially more expensive. The plugin's session summaries will tell you which tools are being used and how often; provider telemetry or a custom estimator tells you whether the unit price is too low. -**Confirm or update `modelBaseCosts`.** The plugin reserves a fixed amount per model call regardless of token count, and the [$5 walkthrough](/blog/openclaw-budget-guard-five-dollar-agent) flagged that this produces ±20% variance. That's fine for budget *enforcement* — you're approximating, not billing — but the estimates need to be in the right ballpark relative to each other or `downgrade_model` won't pick the right fallback. In the JSON-configured OpenClaw path, `costBreakdown.totalCost / count` usually reflects the configured estimate that was committed, not independent provider billing. Use provider token/billing telemetry, an LLM proxy, or a programmatic `modelCostEstimator` when you need measured per-call cost. A rough Anthropic-pricing-anchored ratio: +**Confirm or update `modelBaseCosts`.** The plugin reserves a fixed amount per model call regardless of token count. The difference from provider billing depends on prompt and output size, so treat the fixed values as policy estimates rather than invoice reconstruction. The estimates still need to be in the right ballpark relative to each other or `downgrade_model` will choose fallbacks from a misleading cost order. In the JSON-configured OpenClaw path, `costBreakdown.totalCost / count` usually reflects the configured estimate that was committed, not independent provider billing. Use provider token/billing telemetry, an LLM proxy, or a programmatic `modelCostEstimator` when you need measured per-call cost. A rough ratio anchored to [Anthropic's published pricing](https://platform.claude.com/docs/en/about-claude/pricing), verified July 24, 2026: | Model | Starting estimate (USD_MICROCENTS) | |---|---| -| Claude Opus 4 | 15,000,000 | -| Claude Sonnet 4 | 3,000,000 | +| Claude Opus 4.8 | 5,000,000 | +| Claude Sonnet 4.6 | 3,000,000 | | Claude Haiku 4.5 | 1,000,000 | These are *per-call* averages, not per-token. Adjust upward if your prompts run long. After a few sessions, use the summary's model `count` values to understand call mix, then compare against external/provider cost data. Only treat `totalCost / count` as an observed average if you have wired in a real estimator; otherwise it is just the estimate you configured being charged back through the summary. @@ -112,12 +112,12 @@ A concrete fitted config after day 3 looks like: ```json { "modelBaseCosts": { - "anthropic/claude-opus-4-20250514": 15000000, - "anthropic/claude-sonnet-4-20250514": 3000000, + "anthropic/claude-opus-4-8": 5000000, + "anthropic/claude-sonnet-4-6": 3000000, "anthropic/claude-haiku-4-5-20251001": 1000000 }, "modelFallbacks": { - "anthropic/claude-opus-4-20250514": ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-haiku-4-5-20251001"] + "anthropic/claude-opus-4-8": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5-20251001"] }, "toolBaseCosts": { "web_search": 1000000, @@ -169,7 +169,7 @@ A useful heuristic: set `lowBudgetThreshold` to **roughly the cost of the most e Read this off your event log. With `enableEventLog: true`, every [reservation](/glossary#reservation) logs the running balance: ``` -Model reserved: anthropic/claude-sonnet-4-20250514 (estimate=3000000, remaining=147000000) +Model reserved: anthropic/claude-sonnet-4-6 (estimate=3000000, remaining=147000000) ``` Look at the `remaining` values across a representative session. The threshold you want is somewhere between *"agent is two thirds done"* and *"agent has one Opus call left"*. That's where degradation has time to matter. @@ -192,7 +192,7 @@ Now apply the [cutover decision tree](/blog/shadow-to-enforcement-cutover-decisi | Category | OpenClaw-specific check | Green when | |---|---|---| -| **Cost calibration** | Compare configured `toolBaseCosts` and `modelBaseCosts` against provider telemetry, billing logs, or estimator output. Use `costBreakdown.totalCost / count` only when a real estimator is feeding actuals. | Per-call observations within ~20% of estimates for a representative sample; extend to a steady week before high-risk workflows | +| **Cost calibration** | Compare configured `toolBaseCosts` and `modelBaseCosts` against provider telemetry, billing logs, or estimator output. Use `costBreakdown.totalCost / count` only when a real estimator is feeding actuals. | Estimate error stays within the conservative tolerance your overage policy and workload can absorb across a representative sample | | **Policy coverage** | `unconfiguredTools` list across recent session summaries | List is empty (or only contains tools you've explicitly chosen not to budget) | | **Operational readiness** | Has anyone on the team run a denial rehearsal and seen the relevant model block, tool reservation denial, call-limit block, or access-list block in OpenClaw logs? | Yes — at least one rehearsed denial | | **Reversion readiness** | Can you flip `failClosed: false` (or `dryRun: true`) without a deploy? | Yes — config-toggle path tested | diff --git a/blog/openclaw-budget-guard-five-dollar-agent.md b/blog/openclaw-budget-guard-five-dollar-agent.md index 1eabb4ff..b2133786 100644 --- a/blog/openclaw-budget-guard-five-dollar-agent.md +++ b/blog/openclaw-budget-guard-five-dollar-agent.md @@ -3,7 +3,7 @@ title: "We Gave Our OpenClaw Agent a $5 Budget" date: 2026-03-28 author: Albert Mavashev tags: [openclaw, budgets, agents, graceful-degradation, model-downgrade, production, cost-control, ai-agent-cost, llm-cost-management] -description: "A $12 OpenClaw research session constrained to a $5 Cycles budget. The agent downgrades models, disables expensive tools, self-regulates, finishes at $4.85." +description: "A constructed OpenClaw walkthrough showing how a $5 Cycles budget can downgrade models, disable expensive tools, and stop new work safely before the cap." blog: true sidebar: false head: @@ -26,38 +26,38 @@ When the session crossed the $1.50 low-budget threshold, the plugin downgraded f That's the difference between a kill switch and [runtime authority](/blog/what-is-runtime-authority-for-ai-agents). -> **TL;DR:** Install the plugin, set a budget, and your OpenClaw agent automatically downgrades models, disables expensive tools, and self-regulates when budget gets tight — instead of crashing. +> **TL;DR:** Configure the plugin with a budget, model fallbacks, and tool-cost estimates, and it can downgrade models or block expensive tools before the cap is exhausted. -*Note: The session described below is a representative walkthrough based on real plugin behavior with realistic cost estimates. The numbers, logs, and config are all producible with the plugin — we've simplified the narrative for clarity, but nothing is fabricated.* +*Note: This is a constructed walkthrough using real plugin fields and control paths. The costs and session behavior are illustrative, not provider billing data or a measured production incident.* ## What the logs looked like -Here's the plugin output from that session, at info level — no debug mode needed: +Here is representative plugin output for the walkthrough, at info level: ``` Cycles Budget Guard for OpenClaw v0.7.5 tenant: research-team - defaultModelName: anthropic/claude-opus-4-20250514 + defaultModelName: anthropic/claude-opus-4-8 failClosed: true lowBudgetThreshold: 150000000 -Model reserved: anthropic/claude-opus-4-20250514 (estimate=15000000, remaining=500000000) -Model committed: anthropic/claude-opus-4-20250514 (cost=15000000 USD_MICROCENTS) -Tool reserved: web_search (estimate=5000000, remaining=485000000) +Model reserved: anthropic/claude-opus-4-8 (estimate=5000000, remaining=500000000) +Model committed: anthropic/claude-opus-4-8 (cost=5000000 USD_MICROCENTS) +Tool reserved: web_search (estimate=5000000, remaining=495000000) Tool committed: web_search (cost=5000000 USD_MICROCENTS) -Model reserved: anthropic/claude-opus-4-20250514 (estimate=15000000, remaining=480000000) -Model committed: anthropic/claude-opus-4-20250514 (cost=15000000 USD_MICROCENTS) +Model reserved: anthropic/claude-opus-4-8 (estimate=5000000, remaining=490000000) +Model committed: anthropic/claude-opus-4-8 (cost=5000000 USD_MICROCENTS) ... Budget level changed: healthy → low (remaining=150000000) -Budget low — downgrading model anthropic/claude-opus-4-20250514 → anthropic/claude-sonnet-4-20250514 -Model reserved: anthropic/claude-sonnet-4-20250514 (estimate=3000000, remaining=147000000) +Budget low — downgrading model anthropic/claude-opus-4-8 → anthropic/claude-sonnet-4-6 +Model reserved: anthropic/claude-sonnet-4-6 (estimate=3000000, remaining=147000000) ... Tool "code_execution" blocked: cost 10000000 exceeds expensive threshold 5000000 ... -Model committed: anthropic/claude-sonnet-4-20250514 (cost=3000000 USD_MICROCENTS) -Agent session budget summary: remaining=15000000 spent=485000000 reservations=34 +Model committed: anthropic/claude-sonnet-4-6 (cost=3000000 USD_MICROCENTS) +Agent session budget summary: remaining=15000000 spent=485000000 reservations=72 ``` Every [reservation](/glossary#reservation), commit, downgrade, and block is visible. No digging through provider dashboards. This is what AI agent cost management looks like when it's built into the execution lifecycle — not bolted on after the fact. @@ -72,19 +72,21 @@ and avoid expensive tools. 7% of budget remaining. Est. ~11 tool calls and ~3 model calls remaining at current rate. Limit responses to 1024 tokens. ``` -The model responded to this signal by reducing optional web searches, writing tighter prose, and skipping the summary paragraph it usually generates. We did not hardcode any task-specific fallback behavior — the model adapted to the budget constraint on its own, like it adapts to other system prompt instructions. +In this walkthrough, the model responds to this signal by reducing optional web searches, writing tighter prose, and skipping an optional summary. That response is illustrative: prompt hints can influence model behavior, but they are not an enforcement guarantee. -This is the part that surprises most teams: **budget-aware agents tend to be more disciplined and less wasteful.** When the model knows resources are limited, it focuses. Fewer tangents, less padding, more direct answers. The prompt hint turns a blunt cost limit into a soft constraint the model can reason about. +The hard control remains the reservation and tool policy. The prompt hint is only a soft signal that may help a model wind down gracefully. -## What the session summary told us +## What the session summary can tell you + +The excerpt below shows selected fields; additional component entries are omitted, so the visible subtotals do not add up to the full `spent` value. ```json { "remaining": 15000000, "spent": 485000000, "costBreakdown": { - "model:anthropic/claude-opus-4-20250514": { "count": 8, "totalCost": 120000000 }, - "model:anthropic/claude-sonnet-4-20250514": { "count": 14, "totalCost": 42000000 }, + "model:anthropic/claude-opus-4-8": { "count": 8, "totalCost": 40000000 }, + "model:anthropic/claude-sonnet-4-6": { "count": 14, "totalCost": 42000000 }, "tool:web_search": { "count": 9, "totalCost": 45000000 }, "tool:code_execution": { "count": 3, "totalCost": 30000000 } }, @@ -96,7 +98,7 @@ This is the part that surprises most teams: **budget-aware agents tend to be mor Three things jumped out: -1. **Opus cost $1.20 for 8 calls. Sonnet cost $0.42 for 14 calls.** Sonnet handled nearly twice as many calls for a third of the cost. In our testing, output quality was comparable for this type of task. +1. **The configured Opus estimate was $0.05 per call; Sonnet was $0.03.** The summary exposes the actual call mix and the estimates the plugin committed. It does not establish equivalent model quality or reproduce the provider bill. 2. **Code execution was blocked after 3 calls.** Each call cost $0.10. The `disable_expensive_tools` strategy kicked in at low budget. The agent compensated by describing the analysis in text instead of generating charts. @@ -104,13 +106,13 @@ Three things jumped out: ## Three patterns we observed -Running this config across multiple test sessions, three patterns emerged that changed how we think about LLM cost management. +The walkthrough highlights three operating patterns worth testing on your own workload. -### Model downgrade is usually invisible +### Model downgrade is a controlled tradeoff, not a quality guarantee -Sonnet's output quality for research and analysis tasks is comparable to Opus in most cases. In our test sessions, the downgraded outputs were difficult to distinguish from the Opus-generated ones. The 5x cost reduction was measurable; the quality difference was hard to detect. +In this configuration, the fixed Sonnet estimate is 40% lower than the Opus estimate. Whether the quality tradeoff is acceptable depends on the task and must be evaluated with your own outputs. -The key is configuring the fallback chain correctly. `"anthropic/claude-opus-4-20250514": ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-haiku-4-5-20251001"]` gives the plugin two steps to try. It picks the cheapest model that fits within the remaining budget. +The key is configuring the fallback chain correctly. `"anthropic/claude-opus-4-8": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5-20251001"]` gives the plugin two steps to try. It picks the lowest configured estimate that fits within the remaining budget. ### Tool limits catch more bugs than budget limits @@ -124,9 +126,9 @@ Every session produces a cost breakdown. After a few days, patterns are obvious: Three things we learned the hard way: -**Enable `enableEventLog` from day one.** When a session behaves unexpectedly, the event log tells you exactly what happened — which tools were blocked, when models were downgraded, why a reservation was denied. Without it, you're reading tea leaves from the session summary. +**Enable `enableEventLog` from day one.** When a session behaves unexpectedly, the event log records the plugin's budget sequence—which tools it blocked, when it selected a fallback, and why a reservation was denied. It does not replace OpenClaw or provider logs for tool arguments, model output, or external outcomes. -**Model costs are estimates.** The plugin reserves a fixed amount per Opus call regardless of how many [tokens](/glossary#tokens) are actually used. A short response costs the same as a long one. The `modelCostEstimator` callback can improve this if you have a proxy that tracks token usage, but out of the box, expect ±20% variance. +**Model costs are estimates.** The plugin reserves a fixed amount per model call regardless of how many [tokens](/glossary#tokens) are actually used. A short response costs the same as a long one. The `modelCostEstimator` callback can improve this if you have a proxy that tracks token usage; otherwise, compare estimates with provider telemetry and tune them for your workload. **OpenClaw doesn't pass the model name in hook events.** We had to add `defaultModelName` to the config because the `before_model_resolve` event only contains `{ prompt }`. We've filed a [feature request](https://github.com/openclaw/openclaw/issues/55771) — until it's resolved, set `defaultModelName` to your agent's model. @@ -141,13 +143,13 @@ Three things we learned the hard way: "cyclesBaseUrl": "${CYCLES_BASE_URL}", "cyclesApiKey": "${CYCLES_API_KEY}", "tenant": "research-team", - "defaultModelName": "anthropic/claude-opus-4-20250514", + "defaultModelName": "anthropic/claude-opus-4-8", "modelFallbacks": { - "anthropic/claude-opus-4-20250514": ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-haiku-4-5-20251001"] + "anthropic/claude-opus-4-8": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5-20251001"] }, "modelBaseCosts": { - "anthropic/claude-opus-4-20250514": 15000000, - "anthropic/claude-sonnet-4-20250514": 3000000, + "anthropic/claude-opus-4-8": 5000000, + "anthropic/claude-sonnet-4-6": 3000000, "anthropic/claude-haiku-4-5-20251001": 1000000 }, "toolBaseCosts": { diff --git a/blog/openclaw-budget-guard-stop-agents-burning-money.md b/blog/openclaw-budget-guard-stop-agents-burning-money.md index bde018d7..7dfd6a3a 100644 --- a/blog/openclaw-budget-guard-stop-agents-burning-money.md +++ b/blog/openclaw-budget-guard-stop-agents-burning-money.md @@ -2,7 +2,7 @@ title: "Your OpenClaw Agent Has No Spending Limit" date: 2026-03-27 author: Albert Mavashev -tags: [openclaw, budgets, agents, runtime-authority, cost-control, plugin, tool-limits] +tags: [openclaw, budgets, agents, runtime-authority, cost-control, plugins, tool-limits] description: "OpenClaw agents can retry model and tool calls without a budget cap. Learn how its Cycles budget guard adds pre-execution limits without app code changes." blog: true sidebar: false @@ -30,7 +30,7 @@ If you're running OpenClaw agents in production — or plan to — these will hi ### 1. Runaway spend -An agent stuck in a retry loop, a quality-check loop, or a recursive research task can burn through your entire API budget in minutes. Provider spending caps (OpenAI, Anthropic) are account-wide, monthly, and react too slowly. By the time the cap kicks in, the damage is done. +An agent stuck in a retry loop, quality-check loop, or recursive research task can consume substantial API budget quickly. Provider controls can help, but their project, workspace, account, credit, or quota scopes may not express the OpenClaw session boundary you need. **What the plugin does:** Every model call and tool invocation reserves budget *before* execution via a [Cycles server](/quickstart/what-is-cycles). When the budget is exhausted, the next call is blocked — not the one after the alert fires. Burn rate anomaly detection catches sudden spending spikes (like a [tool loop](/glossary#tool-loop)) and fires a callback within seconds, before the budget is gone. Predictive exhaustion warnings estimate when the budget will run out and alert you proactively. @@ -50,7 +50,7 @@ In a multi-[tenant](/glossary#tenant) platform or a team with shared API keys, o The agent session ends. You know it cost *something*, but you don't know which tools were expensive, which models it chose, or how many calls it made. Debugging a cost spike means digging through API provider dashboards and correlating timestamps. -**What the plugin does:** Every session produces a [cost breakdown](/how-to/integrating-cycles-with-openclaw#session-analytics-and-cost-breakdown) — per-tool cost, per-model cost, invocation counts, and remaining budget. Attached to context metadata and optionally sent to a webhook. For real-time visibility, pipe 12 metrics into Datadog, Prometheus, or any OTLP collector via the built-in [`metricsEmitter`](/how-to/integrating-cycles-with-openclaw#observability-with-otlp-metrics-v0-5-0). Enable `enableEventLog` for a full audit trail of every budget decision — useful for debugging why an agent ran out of budget or why a tool was blocked. +**What the plugin does:** Every session produces a [cost breakdown](/how-to/integrating-cycles-with-openclaw#session-analytics-and-cost-breakdown) — per-tool cost, per-model cost, invocation counts, and remaining budget. It is attached to context metadata and optionally sent to a webhook. For real-time visibility, pipe 12 metrics into Datadog, Prometheus, or any OTLP collector via the built-in [`metricsEmitter`](/how-to/integrating-cycles-with-openclaw#observability-with-otlp-metrics-v0-5-0). Enable `enableEventLog` for a session log of plugin budget decisions; application outcomes still require application logging. ### 5. Abrupt failure @@ -98,11 +98,11 @@ For production, add model fallbacks and tool costs: "tenant": "my-org", "failClosed": true, "modelFallbacks": { - "anthropic/claude-opus-4-20250514": ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-haiku-4-5-20251001"] + "anthropic/claude-opus-4-8": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5-20251001"] }, "modelBaseCosts": { - "anthropic/claude-opus-4-20250514": 1500000, - "anthropic/claude-sonnet-4-20250514": 300000, + "anthropic/claude-opus-4-8": 500000, + "anthropic/claude-sonnet-4-6": 300000, "anthropic/claude-haiku-4-5-20251001": 100000 }, "toolBaseCosts": { @@ -153,10 +153,10 @@ The plugin hooks into five OpenClaw lifecycle events: | `before_model_resolve` | Reserves budget for the model call (held open for later commit). Downgrades if budget is low. Blocks if exhausted. Checks burn rate and exhaustion forecast. | | `before_prompt_build` | Commits the previous model reservation (with optional `modelCostEstimator` reconciliation). Injects budget status into the system prompt. | | `before_tool_call` | Checks tool permissions and call limits. Reserves budget. Starts heartbeat timer for long-running tools. Checks burn rate and exhaustion forecast. Retries on transient server errors. | -| `after_tool_call` | Commits the actual cost. Stops heartbeat timer. | +| `after_tool_call` | Commits the configured tool estimate (or estimator result, when supplied). Stops heartbeat timer. | | `agent_end` | Commits final model reservation. Releases orphaned reservations. Builds session summary with cost breakdown, unconfigured tool report, and event log. | -Every reservation follows the Cycles [reserve-commit-release](/protocol/how-reserve-commit-works-in-cycles) protocol. Budget is deducted atomically on the server — no race conditions, no double-spend, no stale reads. +Every reservation follows the Cycles [reserve-commit-release](/protocol/how-reserve-commit-works-in-cycles) protocol. Matching budget mutations are atomic on the server; callers must still reuse the same idempotency key and request body for retries and keep every protected path behind the plugin boundary. ## Try it without a server @@ -189,7 +189,7 @@ All plugin behavior works identically — model downgrade, tool limits, prompt h - **Multi-tenant platforms** — your users need isolated budgets so one customer's agent doesn't drain another's allocation - **Anyone building with consequential tools** — if your agent can send emails, create tickets, trigger deployments, or write to databases, you need call limits, not just cost limits - **Cost-conscious teams** — model downgrade and [graceful degradation](/glossary#graceful-degradation) let you ship capable agents on tight budgets -- **Teams that need observability** — pipe budget metrics into Datadog, Prometheus, or Grafana via OTLP, and enable session event logs for full audit trails +- **Teams that need observability** — pipe budget metrics into Datadog, Prometheus, or Grafana via OTLP, and enable session event logs for plugin-level budget diagnostics ## Get started diff --git a/blog/operating-budget-enforcement-in-production.md b/blog/operating-budget-enforcement-in-production.md index c04d8ed8..f49818cd 100644 --- a/blog/operating-budget-enforcement-in-production.md +++ b/blog/operating-budget-enforcement-in-production.md @@ -16,7 +16,7 @@ head: > **Part of: [Multi-Tenant AI Operations Reference](/guides/multi-tenant-operations)** — the full pillar covering scope hierarchy, per-tenant enforcement, multi-agent coordination, tenant lifecycle, and identity. -Budget enforcement works. Your agents are denied when they exceed limits. [Webhook events](/blog/real-time-budget-alerts-for-ai-agents) fire in real time. PagerDuty pages you. +An instrumented action can be denied when a matching budget has insufficient room. If you run the events service and configure a matching [webhook subscription](/blog/real-time-budget-alerts-for-ai-agents), the resulting event can reach your incident tooling. @@ -30,16 +30,16 @@ Not every webhook event is a page. Map events to severity and expected response | Severity | Events | Response | What's Happening | |---|---|---|---| -| **Critical** | `budget.exhausted`, `budget.over_limit_entered`, `system.store_connection_lost` | Minutes | Agents are blocked or enforcement is degraded. Revenue-impacting. | -| **Warning** | `reservation.denied`, `budget.threshold_crossed` (95%) | Hours | Agents may be failing or budget is nearly depleted. | -| **Info** | `budget.threshold_crossed` (80%), `reservation.commit_overage` | Next business day | Early warning. Review estimates and capacity. | +| **Critical** | `budget.exhausted` or `budget.over_limit_entered` on a customer-critical scope | Per your SLO | New positive reservations matching the affected ledger may be blocked. | +| **Warning** | Rising live-denial metric or application 4xx errors; rising `reservation.commit_overage` | Per your SLO | Protected actions may be failing, or estimates may need recalibration. | +| **Info** | `budget.funded`, `budget.debited`, `budget.reset`, `budget.debt_repaid` | Routine review | An operator or automation changed ledger state. | | **Audit** | `tenant.suspended`, `api_key.revoked`, `api_key.auth_failed` | As needed | Security or lifecycle event. Verify intentional. | -Route critical events to PagerDuty. Route warnings to a Slack channel. Send info to a dashboard or email digest. See the [webhook integrations guide](/how-to/webhook-integrations) for setup. +These severity assignments are examples; route events according to the affected scope and your service SLOs. The current reference server emits exhaustion at 100% utilization, but does not automatically emit configurable 80% or 95% `budget.threshold_crossed` alerts. Calculate early-warning thresholds from balance snapshots or your telemetry. See the [webhook integrations guide](/how-to/webhook-integrations) for delivery setup. -## Diagnostic decision tree: reservation.denied +## Diagnostic decision tree: a budget denial -`reservation.denied` is the most common operational event. An agent tried to reserve budget and was refused. Here's how to diagnose why. +A live reservation that cannot proceed returns a protocol error such as `409 BUDGET_EXCEEDED`; the current exception path does not emit `reservation.denied`. Detect live failures through application error handling or `cycles_reservations_reserve_total{decision="DENY"}`. The `reservation.denied` event is emitted only when a dry-run reservation or `/v1/decide` evaluation returns `DENY`, making it useful for calibration rather than proof that live work was blocked. ### Step 1: Identify scope and tenant @@ -51,15 +51,28 @@ The event payload tells you who, what, and where: "tenant_id": "acme-corp", "scope": "tenant:acme-corp/workspace:prod/agent:support-bot", "actor": { "type": "api_key", "key_id": "key_9f8e7d6c" }, - "data": { "reason_code": "BUDGET_EXCEEDED", "requested_amount": 5000000 } + "data": { + "scope": "tenant:acme-corp/workspace:prod/agent:support-bot", + "unit": "USD_MICROCENTS", + "reason_code": "BUDGET_EXCEEDED", + "requested_amount": 5000000, + "remaining": 0, + "action": { "kind": "llm.chat", "name": "support-reply" }, + "subject": { "tenant": "acme-corp", "workspace": "prod", "agent": "support-bot" } + } } ``` -Pull recent denial events for this [tenant](/glossary#tenant) to see if it's a single agent or widespread: +For a hypothetical denial, pull recent `reservation.denied` events for this [tenant](/glossary#tenant). For a live incident, start from the application's error response and runtime metric tags instead: ```bash curl "http://localhost:7979/v1/admin/events?tenant_id=acme-corp&event_type=reservation.denied&limit=50" \ -H "X-Admin-API-Key: $ADMIN_KEY" + +# Live reservation denials (Prometheus; tenant tag is optional by configuration) +sum by (tenant, reason) ( + rate(cycles_reservations_reserve_total{decision="DENY"}[5m]) +) ``` ### Step 2: Check the budget @@ -87,11 +100,11 @@ Look at the response: | Symptom | Likely Cause | Immediate Fix | |---|---|---| -| Single agent, many denials in quick succession | Retry loop or runaway agent | Revoke the API key: `DELETE /v1/admin/api-keys/{key_id}` | -| Many agents across workspace, all denied | Budget exhausted for shared scope | Emergency fund: `POST /v1/admin/budgets/fund` with CREDIT | -| Intermittent denials, some agents succeed | Concurrent agents competing for limited remaining budget | Increase allocation or add overdraft buffer | -| Denials started after a deploy | New code version has higher cost estimates | Review and lower estimate amounts in agent code | -| Denials for one tenant only | Tenant-specific budget depleted | Fund that tenant's budget specifically | +| Single key, many denials in quick succession | Retry loop, intentional fan-out, or duplicate instrumentation | Correlate with application traces; revoke the key if the activity is unauthorized or cannot be stopped safely | +| Many agents across one workspace, all denied at the same scope | Shared ledger exhausted, frozen, closed, or over-limit | Inspect the reason code and ledger state before funding or changing policy | +| Intermittent denials, some agents succeed | Limited remaining budget, varying estimates, or concurrent reservations | Inspect estimates and active reservations; resize only if the allocation is actually wrong | +| Denials started after a deploy | Changed estimates, scope mapping, or workload behavior | Compare the deployed signals with the previous version | +| Denials for one tenant only | A tenant-owned ledger or tenant lifecycle state is denying work | Inspect that tenant's denying scope and reason code | ## Emergency response playbook @@ -117,12 +130,10 @@ curl -X POST "http://localhost:7979/v1/admin/budgets/fund?scope=tenant:acme-corp "reason": "emergency top-up: agents blocked in prod" }' -# 3. Verify agents can reserve again -# Agents will automatically succeed on next attempt — no restart needed +# 3. Verify a new reservation succeeds +# No restart is required, but other denying scopes or states can still block it ``` -**Time to resolution:** Under 60 seconds if you have the API key ready. - ### Scenario B: Over-limit — debt exceeds overdraft An agent committed more than estimated (via `ALLOW_WITH_OVERDRAFT` policy), accumulating debt past the overdraft limit. New reservations are blocked. @@ -155,7 +166,7 @@ One API key is generating hundreds of reservation attempts per minute. # 1. Identify the key from denial events # Event data: actor.key_id = "key_9f8e7d6c" -# 2. Revoke the key immediately (permanent, takes effect instantly) +# 2. Revoke the key if the activity is unauthorized or cannot be stopped curl -X DELETE "http://localhost:7979/v1/admin/api-keys/key_9f8e7d6c" \ -H "X-Admin-API-Key: $ADMIN_KEY" @@ -167,41 +178,36 @@ curl "http://localhost:7979/v1/admin/audit/logs?key_id=key_9f8e7d6c&limit=100" \ curl -X POST "http://localhost:7979/v1/admin/api-keys" \ -H "X-Admin-API-Key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ - -d '{"tenant_id": "acme-corp", "name": "support-bot-v2", "permissions": ["reservations:create", "reservations:commit", "balances:read"]}' + -d '{"tenant_id": "acme-corp", "name": "support-bot-v2", "permissions": ["reservations:create", "reservations:commit", "reservations:release", "balances:read"]}' ``` **Important:** Revoking a key is permanent via the API — there is no un-revoke. Active reservations created before revocation can still be committed or released using another valid key for the same tenant. Only new requests using the revoked key are blocked. ## Estimate accuracy: the most underrated metric -The gap between what agents *reserve* and what they *commit* is the single best leading indicator of budget incidents. - -- **Reserve >> Commit** (ratio > 2:1): Agents over-estimate. Budget *appears* consumed but most is released after commit. You're experiencing false scarcity — budgets run out sooner than actual spend warrants. -- **Reserve << Commit** (ratio < 0.8:1): Agents under-estimate. Overage events fire. Debt accumulates. You'll see `reservation.commit_overage` and eventually `budget.over_limit_entered`. -- **Reserve ≈ Commit** (ratio 0.8-1.2:1): Estimates are accurate. Budget utilization is predictable. This is the target range. +The gap between what agents *reserve* and what they *commit* is a useful leading indicator of budget pressure. | Reserve:Commit Ratio | What It Means | Action | |---|---|---| -| > 2:1 | Severe over-estimation | Reduce agent estimate amounts to free budget capacity | -| 1.2 – 2:1 | Moderate buffer | Acceptable for workloads with high variance | -| 0.8 – 1.2:1 | Accurate | Ideal range. No action needed. | -| < 0.8:1 | Under-estimation | Increase estimates or add overdraft buffer | +| Consistently above 1 | Estimates exceed actual usage | Decide whether the safety buffer is intentional; excessive buffers can reduce concurrent headroom | +| Near 1 | Estimates are close to actual usage | Verify the distribution, not just the aggregate | +| Consistently below 1 | Actual usage exceeds estimates | Improve the estimator and review the configured commit-overage policy | -How to measure: compare `reserved` and `spent` from the balance API over time. A rising `reserved` with flat `spent` signals over-estimation. Rising `debt` with low `reserved` signals under-estimation. +There is no universal healthy band: a deterministic call can target a narrow tolerance, while a high-variance tool may need a larger safety buffer. Calculate the ratio from completed reservation estimates and their actual amounts in application telemetry or exported reservation records. The balance API's `reserved` field is a point-in-time total of active holds and `spent` is cumulative, so dividing those two fields is not a reserve-to-commit ratio. ## Five metrics that predict budget incidents -These are the numbers your budget operations dashboard should show. The thresholds below are suggested starting points — tune them based on your workload patterns. +These are useful numbers for a budget operations dashboard. Derive alert thresholds from workload baselines, service objectives, and the time operators need to respond. | Metric | What It Shows | Watch For | |---|---|---| -| **Denial rate** | % of reservation attempts denied | > 5% sustained over 15 minutes | -| **Budget velocity** | $ consumed per hour | > 2x the 7-day rolling average | -| **Estimate accuracy** | reserved / committed ratio | Outside 0.8 – 2.0 range | -| **Time to exhaustion** | Hours until remaining = 0 at current velocity | < 4 hours | -| **[Webhook delivery](/glossary#webhook-delivery) failure rate** | % of deliveries failing | > 10% (your alerting pipeline is degraded) | +| **Denial rate** | % of reservation attempts denied | Deviation from the expected, classified baseline | +| **Budget velocity** | Units consumed per time window | Unexpected change after accounting for seasonality and traffic | +| **Estimate accuracy** | Estimate / actual ratio per completed reservation | Drift outside the tolerance chosen for that workload | +| **Time to exhaustion** | Time until remaining reaches zero at current velocity | Less than the funding or degradation response lead time | +| **[Webhook delivery](/glossary#webhook-delivery) failure rate** | % of deliveries failing | Breach of your alert-delivery SLO | -**Denial rate** is the most important. A 0% denial rate means budgets are either too generous or enforcement isn't active. A 20% denial rate means agents are routinely failing — either budgets are too tight or there's a systemic issue. Target: < 2% for healthy workloads. +Interpret denial rate in context. A zero rate can be correct for normal traffic but does not prove that tested runaway cases are bounded. A high rate can be intentional on abusive traffic or harmful on ordinary user workflows; classify denials before changing limits. **Budget velocity** catches runaway agents before budgets exhaust. If a workspace normally spends $5/hour and suddenly spends $50/hour, you have a problem — even if the budget isn't exhausted yet. @@ -211,17 +217,17 @@ These are the numbers your budget operations dashboard should show. The threshol Budget enforcement works best when budgets are calibrated to actual workloads. Here's how to get there: -1. **Start at 2x expected cost.** Over-allocate on day one. You can always reduce later. Under-allocation on a new workload causes immediate agent failures. +1. **Size from representative data and failure objectives.** Use workload percentiles, concurrency, estimate uncertainty, and the maximum exposure you are prepared to accept. A universal multiplier cannot encode those trade-offs. -2. **Use shadow mode for the first week.** Set [`dry_run: true`](/protocol/dry-run-shadow-mode-evaluation-in-cycles) on reservation requests. The server evaluates the budget decision but doesn't actually reserve funds, so agents are never blocked. Review the decisions to tune budgets before enforcing. +2. **Evaluate representative traffic with dry runs.** Set [`dry_run: true`](/protocol/dry-run-shadow-mode-evaluation-in-cycles) on reservation requests. The server returns a hypothetical decision without creating a reservation or balance mutation; the application must retain that response and actual outcome for analysis. Observe enough traffic and edge cases for your workload rather than relying on a fixed duration. -3. **Set overdraft_limit to 10-20% of allocation.** This handles burst variance without blocking agents during normal traffic spikes. A $100 budget with $15 overdraft tolerates short bursts without hitting over-limit. +3. **Configure overdraft only when the accounting policy needs it.** `overdraft_limit` applies to `ALLOW_WITH_OVERDRAFT`. Size it from the largest tolerated overage and concurrent-commit behavior, then monitor and reconcile debt. Do not add overdraft merely to hide an undersized budget. -4. **Configure threshold alerts at 80% and 95%.** Default thresholds fire `budget.threshold_crossed` at 80%, 95%, and 100% utilization. The 80% alert gives you time to fund before agents are affected. +4. **Create early-warning alerts from balances or telemetry.** Choose utilization and time-to-exhaustion thresholds that leave enough response time. The current reference runtime emits `budget.exhausted` on the transition to zero remaining; configurable pre-exhaustion `budget.threshold_crossed` emission is not implemented. -5. **Review monthly.** Are budgets running out mid-cycle? Increase allocation. Is 40% unused at month-end? Reduce allocation to get more accurate cost visibility. +5. **Review on a workload-appropriate cadence.** Revisit budgets after model, price, traffic, or workflow changes and at the boundaries that matter to your billing period. -6. **Track estimate accuracy.** If reserve:commit ratio drifts outside 0.8-2.0, agent code needs estimate tuning — not budget changes. +6. **Track estimate accuracy.** Investigate drift outside the tolerance chosen for each workload. Fix estimate formulas when they stop tracking reality; change budgets only when the intended exposure changes. --- diff --git a/blog/operational-runbook-using-cycles-runtime-events.md b/blog/operational-runbook-using-cycles-runtime-events.md index b40bc4a4..714e3984 100644 --- a/blog/operational-runbook-using-cycles-runtime-events.md +++ b/blog/operational-runbook-using-cycles-runtime-events.md @@ -17,7 +17,7 @@ head: Runtime enforcement catches what observability misses — but only if someone is watching. Once you have Cycles enforcing budgets in production, you need a plan for what happens when enforcement fires at 2 AM. That's what runtime events are for. -Cycles emits webhook events on every significant budget state transition: threshold crossings, exhaustion, debt accumulation, denial rate spikes, reservation expirations. These events are the signal layer that connects enforcement to your operational infrastructure — PagerDuty, Slack, auto-remediation scripts, runbooks. +The current runtime emits events for exhaustion, entry into over-limit state, new debt, reservation expiry, commit overage, and DENY results from dry-run reservation or `/v1/decide` evaluations. These signals connect enforcement and calibration to operational infrastructure such as PagerDuty, Slack, remediation scripts, and runbooks. Configurable threshold crossings, denial-rate spikes, and burn-rate anomalies remain registered-but-planned event types. This post is the operator's runbook: which events matter, what they mean, and what to do when they fire. @@ -27,11 +27,11 @@ This post is the operator's runbook: which events matter, what they mean, and wh The alternative to events is polling dashboards. That struggles for the same reason observability-only approaches struggle with enforcement: **detection latency**. By the time a dashboard refresh shows budget exhaustion, the response window is often already closing. -(If you want the architectural background on the event system itself, the [Real-Time Budget Alerts post](/blog/real-time-budget-alerts-for-ai-agents) covers the design. The [operator's guide](/blog/operating-budget-enforcement-in-production) covers diagnostic trees for the `reservation.denied` scenario. This post is the event-by-event response reference for the other critical events.) +(If you want the architectural background on the event system itself, the [Real-Time Budget Alerts post](/blog/real-time-budget-alerts-for-ai-agents) covers the design. The [operator's guide](/blog/operating-budget-enforcement-in-production) covers live reservation failures using application errors and runtime metrics. This post is the event-by-event response reference.) -The cloud providers figured this out years ago. AWS Budgets pushes threshold alerts through SNS. [GCP Budget Notifications](https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications) push to Pub/Sub — their docs explicitly state: *"If you use budgets or cost anomaly detection as a cost control tool, email notifications might not be the best method to use to ensure timely action to control your costs."* Azure uses Action Groups for the same fan-out pattern. +The cloud providers figured this out years ago. AWS Budgets pushes threshold alerts through SNS. [GCP Budget Notifications](https://docs.cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications) push to Pub/Sub — their docs explicitly state: *"If you use budgets or cost anomaly detection as a cost control tool, email notifications might not be the best method to use to ensure timely action to control your costs."* Azure uses Action Groups for the same fan-out pattern. -Cycles follows the same playbook. Events fire within seconds of the state change, get signed with HMAC-SHA256, and land on your webhook endpoint. Your infrastructure decides what to do with them. +Cycles follows the same playbook. The runtime enqueues events when implemented state changes occur; a deployed, healthy Events Service signs outbound deliveries with HMAC-SHA256 and posts them to your webhook endpoint. Delivery is asynchronous and does not provide a fixed end-to-end latency SLA, so your infrastructure should monitor queue and delivery health. ## Event Severity Tiers @@ -41,21 +41,21 @@ Apply that principle to the events Cycles emits today: | Tier | Route | Events | SLA | |---|---|---|---| -| **Critical — page on-call** | PagerDuty/OpsGenie | `budget.exhausted`, `budget.over_limit_entered` | < 5 min response | -| **Warning — alert channel** | Slack/Teams | clusters of `reservation.denied`, `reservation.expired` bursts | < 1 hour review | -| **Info — dashboard + digest** | Grafana/digest email | `budget.debt_incurred`, `reservation.commit_overage` | Next business day | +| **Critical — page on-call** | PagerDuty/OpsGenie | `budget.exhausted`, `budget.over_limit_entered` | Your workload's page-response SLO | +| **Warning — alert channel** | Slack/Teams | clusters of dry-run/decide `reservation.denied` events; `reservation.expired` bursts | Your calibration or client-health review SLO | +| **Info — dashboard + digest** | Grafana/digest email | `budget.debt_incurred`, `reservation.commit_overage` | Your capacity-review cadence | -The split matters. If you page on every commit overage, on-call will learn to ignore the pager. If you only page on exhaustion, you've lost the chance to intervene earlier. +Treat this routing as an example, then tune it to workload criticality. If you page on every commit overage, on-call will learn to ignore the pager. Because the current runtime has no configurable pre-exhaustion threshold event, earlier intervention requires balance polling or application metrics. ## Runbook: `budget.exhausted` -**Severity:** Critical — all new reservations for this scope are being DENIED until funded. +**Severity:** Usually critical — a ledger transitioned to zero remaining. New positive reservations that derive the affected scope and unit cannot be allowed by that ledger until capacity is restored. -**Payload fields:** envelope (event_id, event_type, tenant_id, scope, timestamp) with actor context. Query the budget directly for current balance state. +**Payload fields:** `scope`, `unit`, `threshold` (`1.0`), `utilization`, `allocated`, `remaining` (`0`), `spent`, `reserved`, and `direction` (`rising`), plus envelope and actor context. Query the balance API before acting because the ledger can change after emission. **Immediate triage (first 5 minutes):** -1. **Identify blast radius.** What scope exhausted? Per-tenant? Per-workflow? Per-run? The `scope` field tells you. +1. **Identify blast radius.** Which standard scope exhausted — tenant, workspace, app, workflow, agent, or toolset? The `scope` field tells you. If your application models a run as a workflow ID, join that workflow segment to your run metadata. 2. **Check the active reservations.** Are agents currently blocked? Query the runtime server with `GET /v1/reservations?tenant={tenant}&status=ACTIVE` (authenticated with `X-Cycles-API-Key`) to see what's in flight. 3. **Check the spike pattern.** Is this gradual exhaustion (expected — budget was sized correctly and we need more) or a sudden spike (runaway agent)? @@ -75,12 +75,12 @@ Was spend rate normal until recently? │ └── Distributed spike → Traffic surge → Fund budget + rate-limit upstream traffic - → Review burn_rate_anomaly events + → Review application traffic and burn-rate telemetry ``` **Don't do this:** Immediately raise the budget permanently. That might be the right answer, but confirm there's no runaway agent first. A 3x budget increase in response to a retry loop just gives the loop 3x more runway. -**Automation opportunity:** A `budget.exhausted` event can trigger automatic budget replenishment from a reserve pool *if* `burn_rate_anomaly` hasn't also fired in the last N minutes. This is the AI agent equivalent of the [circuit breaker pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker): auto-remediate when it looks normal, escalate when it doesn't. +**Automation opportunity:** Your webhook consumer can trigger a bounded replenishment from a reserve pool after checking application traffic and burn-rate telemetry. Keep the cap and anomaly check in your own automation; `budget.burn_rate_anomaly` is not emitted by the current reference runtime. This is the AI agent equivalent of the [circuit breaker pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker): auto-remediate known-safe cases and escalate ambiguous ones. ## Runbook: `budget.over_limit_entered` @@ -94,34 +94,34 @@ Was spend rate normal until recently? 1. **Verify the debt amount.** Check `budget.debt_incurred` events over the last 24h to understand how debt accumulated. 2. **Decide: pay down debt or raise the limit.** If the overrun reflects legitimate growth, raise `overdraft_limit` via admin API. If it reflects estimation drift or a runaway, repay debt via REPAY_DEBT funding operation. -3. **Watch for `budget.over_limit_exited`.** This confirms recovery. +3. **Confirm recovery from current state.** Query the balance until `is_over_limit` is false. `budget.over_limit_exited` is registered but not emitted by the current reference runtime. **Root cause patterns:** - **Estimation drift:** Your reserve estimates are too low; actuals consistently exceed them. Fix by re-calibrating estimates (see the [shadow mode rollout guide](/blog/how-to-add-runtime-enforcement-without-breaking-your-agents)). - **Concurrent overspend:** Multiple commits landed at once and pushed debt past the limit. Fix by reducing `overdraft_limit` or tightening per-reservation estimates. - **Policy mismatch:** Budget was set to `ALLOW_WITH_OVERDRAFT` but the workload needs hard blocking. Change policy to `REJECT` on exhaustion. -## Runbook: `reservation.denied` (individual denials) +## Runbook: `reservation.denied` (dry-run and decide evaluations) -**Severity:** Warning — a specific reservation was denied. Aggregate these by scope to detect patterns. +**Severity:** Calibration warning — a dry-run reservation or `/v1/decide` evaluation returned `DENY`. Aggregate these by scope to detect patterns before cutover. -**Expected fields include:** `scope`, `unit`, `reason_code`, plus envelope actor context. Query the event directly for full details. +**Expected fields include:** `scope`, `unit`, `reason_code`, `requested_amount`, `action`, and `subject`, plus envelope actor context. A denied reservation dry run also includes derived `remaining`; the current `/v1/decide` emitter omits it. -**When it fires:** Every time a reservation is rejected. Individual events are low severity; the signal is in the *aggregate* — query recent events per scope to detect spikes. +**When it fires:** When a nonpersisting reservation request with `dry_run: true` returns DENY, or `/v1/decide` returns DENY. A live reservation failure returns an HTTP error such as `409 BUDGET_EXCEEDED`; the current controller does not emit `reservation.denied` on that exception path. Use application error handling or `cycles_reservations_reserve_total{decision="DENY"}` for live denial alerting. **Triage (when you see a cluster):** 1. **Check denial reasons.** Query the admin API: `GET /v1/admin/events?event_type=reservation.denied&scope={scope}` (authenticated with `X-Admin-API-Key`). What `reason_code` values are showing up? 2. **Common reason codes:** - - `BUDGET_EXCEEDED` — per-scope sub-budget is tight while parent has room. Check budget hierarchy. + - `BUDGET_EXCEEDED` — at least one applicable ledger lacks available capacity. Check all returned balances; Cycles does not transfer allocation between ledgers. - `OVERDRAFT_LIMIT_EXCEEDED` — hitting the debt ceiling, not the allocated ceiling. - `BUDGET_FROZEN` — someone froze the budget via `POST /v1/admin/budgets/freeze`. Unfreeze with `POST /v1/admin/budgets/unfreeze` (X-Admin-API-Key) once investigation is complete. - `DEBT_OUTSTANDING` — unresolved debt blocking new reservations. -3. **Look at agent behavior.** Are specific agents being denied repeatedly? That's a retry loop signature — the agent keeps trying the same denied reservation. +3. **Look at calibration behavior.** Are specific dry-run or decide callers evaluating the same action repeatedly? That may be a loop in the calibration path. For a live retry loop, inspect application errors and the runtime denial metric instead. -**Don't do this:** Raise the budget to make denials go away without understanding why. High denial rates often indicate bad agent behavior (loop, estimation drift, fanout explosion) that raising the budget just hides. +**Don't do this:** Raise the budget to make DENY evaluations go away without understanding why. A high dry-run DENY rate can reflect an undersized budget, an intentionally tested boundary, or repetitive client behavior; distinguish them before changing capacity. -**Aggregation pattern:** Run a scheduled job that queries recent `reservation.denied` events per scope, counts them per window, and pages if the count crosses a threshold. This is the practical implementation of denial-rate alerting using the event stream. +**Aggregation pattern:** During shadow calibration, query recent `reservation.denied` events per scope and count them per window. For enforcement incidents, alert on application errors or the live reservation denial counter rather than assuming this event represents the exception path. ## Runbook: `reservation.commit_overage` @@ -136,7 +136,7 @@ Was spend rate normal until recently? **Triage:** 1. **Check for concentration.** Is overage happening at a specific workflow step, or spread evenly? A single workflow with 50% average overage needs targeted estimate fixes. -2. **Look at the overage distribution.** 5-10% drift is normal. 50%+ is a calibration problem. +2. **Look at the overage distribution.** Establish a workload-specific baseline and investigate material changes in its tail; there is no universal acceptable percentage. 3. **Fix the estimate source.** If your estimates come from token-count predictions, add a safety margin. If they come from prior-run averages, widen the window or use p95 instead of mean. **Automation opportunity:** A commit overage dashboard per workflow lets you spot drifting estimates before they cause incidents. This is a dashboard event, not a paging event. @@ -171,7 +171,7 @@ The runbooks above assume your webhook handlers are reliable. Industry patterns **Deduplicate by event ID.** Cycles delivers events at-least-once. The `event_id` field is unique; track which IDs you've processed and skip duplicates. Stripe's guidance: *"You can guard against duplicated event receipts by logging the event IDs you've processed, and then not processing already-logged events."* -**Set a dead-letter policy.** Cycles retries webhook delivery up to 5 times with exponential backoff (1s, 2s, 4s, 8s, 16s) and auto-disables subscriptions after 10 consecutive failures. But you also need a DLQ for events you received but couldn't process. A malformed payload shouldn't crash your consumer. +**Set a dead-letter policy.** With default subscription settings, Cycles makes an initial delivery plus up to five retries using exponential backoff and disables a subscription after ten consecutive delivery failures. These settings are configurable. You also need a DLQ for events you received but could not process; a malformed payload should not crash your consumer. ## PagerDuty and Slack Integration Recipes @@ -185,7 +185,7 @@ Cycles webhook → Your transformer → PagerDuty Events API v2 custom_details: event payload ``` -The `dedup_key` is essential. Without it, repeated `budget.exhausted` events for the same scope will create page storms. With it, PagerDuty groups them into one incident that acknowledges/resolves cleanly. +The `dedup_key` is essential. Without it, at-least-once duplicate deliveries or later independent exhaustion transitions for the same scope can create page storms. With it, PagerDuty can group them into one incident; choose when your transformer sends resolve events so a genuinely new exhaustion can open a new incident. **Slack (warning events):** @@ -193,7 +193,7 @@ Use a transformer that formats the event into a Slack message with the scope, se **Auto-remediation (info events):** -Some events are safe to auto-remediate. `reservation.commit_overage` can trigger an estimate recalibration job. `budget.debt_incurred` at low levels can trigger a pre-configured budget top-up from a reserve pool. These don't need human involvement — they need to happen consistently, not emotionally. +Some events may be suitable for bounded automation after workload-specific review. `reservation.commit_overage` can feed an estimate-recalibration job. `budget.debt_incurred` can trigger a capped top-up only when your own policy and anomaly checks approve it. Keep an audit trail and a hard automation ceiling. ## On-Call Quick Reference @@ -201,7 +201,8 @@ Some events are safe to auto-remediate. `reservation.commit_overage` can trigger |---|---|---|---| | `budget.exhausted` | Yes | Burst vs. gradual? | Fund budget (verify no runaway) | | `budget.over_limit_entered` | Yes | Debt source? | Repay debt or raise limit | -| Cluster of `reservation.denied` | No (Slack) | Denial reason codes | Depends on reason code | +| Cluster of dry-run/decide `reservation.denied` | No (calibration channel) | Denial reason codes | Recalibrate or adjust intentional limits | +| Live reservation DENY metric/application errors | Workload-dependent | Error reason and retry behavior | Fix caller loop or restore capacity | | Burst of `reservation.expired` | No (Slack) | Clustered scopes? | Fix downstream or tune TTL | | `budget.debt_incurred` | No (dashboard) | Overdraft policy? | Verify intentional | | `reservation.commit_overage` | No (dashboard) | Estimate accuracy | Recalibrate estimates | @@ -210,7 +211,7 @@ Some events are safe to auto-remediate. `reservation.commit_overage` can trigger Runtime events are how enforcement becomes operational. Without them, you have a system that blocks actions silently. With them, enforcement integrates with the same infrastructure you already use for billing alerts, quota notifications, and on-call rotations — the same pattern AWS, GCP, and Azure all converged on. -Your job as an operator is to route each event to the right response: page for critical, Slack for warning, dashboard for info, audit log for compliance. When something goes wrong, you want to know in the next five seconds — not the next five hours. +Your job as an operator is to route each implemented event to the right response: page for critical, Slack for warning, dashboard for info, and audit storage for compliance. Define and monitor delivery latency against your own incident-response SLO instead of assuming a fixed webhook arrival time. --- diff --git a/blog/pocketos-aftermath-delete-delay-vs-scoped-tokens.md b/blog/pocketos-aftermath-delete-delay-vs-scoped-tokens.md index 1954f574..0c44f522 100644 --- a/blog/pocketos-aftermath-delete-delay-vs-scoped-tokens.md +++ b/blog/pocketos-aftermath-delete-delay-vs-scoped-tokens.md @@ -25,7 +25,7 @@ This post is about that disagreement and where each layer of fix actually sits. ## What Railway patched: a delete-delay window, not token scope -Per follow-up reporting, [Railway told The Register](https://www.theregister.com/2026/04/27/cursoropus_agent_snuffs_out_pocketos/) that it patched the legacy endpoint the agent had hit so that destructive deletes are now delayed instead of instant — a window during which an operator can intervene before the data is gone. +Per follow-up reporting, [Railway told The Register](https://www.theregister.com/software/2026/04/27/cursor-opus-agent-snuffs-out-startups-production-database/5224442) that it patched the legacy endpoint the agent had hit so that destructive deletes are now delayed instead of instant — a window during which an operator can intervene before the data is gone. That is a real change, and a useful one. It buys time. It also, by itself, does not address the chain of events that caused the incident: @@ -123,4 +123,4 @@ The post-mortem you do not want to write a year from now is the one where the to - [Zenity — AI Agent Destroys Production Database in 9 Seconds](https://zenity.io/blog/current-events/ai-agent-database-deletion-pocketos) — the token-scope analysis cited above - [Apono — Nine seconds to delete a database](https://www.apono.io/blog/nine-seconds-to-delete-a-database-what-the-pocketos-incident-teaches-us-about-ai-agent-privilege-management/) — the just-in-time-access argument - [TrojAI — Why PocketOS wasn't a permissions failure](https://troj.ai/blog/pocketos-9-second-database-deletion-wasnt-permissions-failure) — the external-layer counter-argument -- [The Register — Cursor-Opus agent](https://www.theregister.com/2026/04/27/cursoropus_agent_snuffs_out_pocketos/) — the original reporting that captured Railway's delete-delay patch +- [The Register — Cursor-Opus agent](https://www.theregister.com/software/2026/04/27/cursor-opus-agent-snuffs-out-startups-production-database/5224442) — the original reporting that captured Railway's delete-delay patch diff --git a/blog/policy-drift-in-ai-agents.md b/blog/policy-drift-in-ai-agents.md index fa2626aa..6c4c02bf 100644 --- a/blog/policy-drift-in-ai-agents.md +++ b/blog/policy-drift-in-ai-agents.md @@ -51,7 +51,7 @@ Agents add more change surfaces: | Memory | Prior outcomes influence future actions | | Delegation | Parent agent starts spawning child agents | | Retry logic | A harmless retry becomes a loop | -| Scope mapping | A workflow starts charging the wrong [tenant](/glossary#tenant) or run | +| Scope mapping | A workflow starts charging the wrong [tenant](/glossary#tenant) or application-mapped run ledger | That makes static review necessary but insufficient. The review captures one snapshot. The agent keeps moving. @@ -131,7 +131,7 @@ For the protocol mechanics, see [Dry Run and Shadow Mode Evaluation](/protocol/d Drift will still happen. Runtime limits make it bounded. -If a prompt update makes the agent call search ten more times, a mandatory run budget can bound reserved exposure. If a skill update starts sending Slack messages, the handler can assign and reserve toolset [RISK_POINTS](/glossary#risk-points) before each send. If a child-agent path appears, the orchestrator can apply the [authority-attenuation pattern](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) so the child receives a carved-out budget instead of a fresh envelope. +If a prompt update makes the agent call search ten more times, a mandatory run-mapped workflow budget can bound reserved exposure. If a skill update starts sending Slack messages, the handler can assign and reserve toolset [RISK_POINTS](/glossary#risk-points) before each send. If a child-agent path appears, the orchestrator can apply the [authority-attenuation pattern](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) by provisioning a narrower child ledger and restricting the child's actual tools. The useful pattern is: @@ -151,7 +151,7 @@ A practical drift review can be short: 1. **What changed?** Prompt, model, tool descriptor, skill, code, retry policy, scope mapping, or data source. 2. **Which action classes changed?** Read, write, external send, deploy, payment, delete, or delegate. -3. **Which scopes moved?** Tenant, workspace, workflow, run, agent, or toolset. +3. **Which scopes moved?** Tenant, workspace, app, workflow, agent, or toolset. If the application keys workflow per run, analyze those workflow values by run. 4. **Which budgets changed pressure?** [USD_MICROCENTS](/glossary#usd-microcents), [TOKENS](/glossary#tokens), [CREDITS](/glossary#credits), or RISK_POINTS. 5. **Which decisions changed?** ALLOW, ALLOW_WITH_CAPS, DENY, or denial reason. 6. **Which audit trail proves it?** Reservation, commit, event, webhook, and correlation ID records. diff --git a/blog/python-ai-agent-control-cost-risk-audit-layers.md b/blog/python-ai-agent-control-cost-risk-audit-layers.md index 459b142d..441828fa 100644 --- a/blog/python-ai-agent-control-cost-risk-audit-layers.md +++ b/blog/python-ai-agent-control-cost-risk-audit-layers.md @@ -102,7 +102,7 @@ The lifecycle distinction is treated more thoroughly in [Runtime Authority vs Gu An external budget authority that the application calls *before* protected execution. The agent reserves dollars, tokens, or caller-assigned risk points. A live reservation succeeds with `ALLOW` or configured `ALLOW_WITH_CAPS`, or returns an error when budget is unavailable. After execution starts, the agent commits best-known actual usage. The ledger records the budget lifecycle; application authorization and tool-call audit data remain separate. - **Cost coverage:** pre-execution. The next action is allowed or denied based on remaining budget, not after the bill arrives. -- **Risk coverage:** pre-execution when the application classifies the action, assigns `RISK_POINTS`, and submits the reservation before the mandatory boundary. A high-consequence action such as `delete_*` can consume more of a caller-provisioned exposure budget than `read_file`; Cycles does not infer the tier from the action name. See [Beyond Budget: How Cycles Controls Agent Actions, Not Just Spend](/blog/beyond-budget-how-cycles-controls-agent-actions) for the full action-authority framing. +- **Risk coverage:** pre-execution when the application classifies the action, assigns `RISK_POINTS`, and submits the reservation before the mandatory boundary. A high-consequence action such as `delete_*` can consume more of a caller-provisioned exposure budget than `read_file`; Cycles does not infer the tier from the action name. See [How Cycles Meters Caller-Assigned Action Exposure](/blog/beyond-budget-how-cycles-controls-agent-actions) for the composed budget-and-authorization framing. - **Audit coverage:** structured budget lifecycle records by default. Reservations, commits, and releases carry the submitted Subject, action context, amount, status, and timestamps. Live rejection responses and non-persisting `decide`/dry-run outcomes need explicit retention, while tool arguments, application authorization, and external outcomes remain application records. Per-user attribution can be carried in `dimensions` or actor metadata, not as a built-in budget scope. **Where it stops short:** requires a service to operate. Self-hosted or otherwise, it's a real piece of infrastructure with availability requirements, not a single-file Python library you `pip install` and forget. The trade-off is the operational footprint in exchange for pre-execution control on all three axes. @@ -165,7 +165,7 @@ Most Python AI agent stacks stop short on risk and audit because the layers most ## Related reading -- [Beyond Budget: How Cycles Controls Agent Actions, Not Just Spend](/blog/beyond-budget-how-cycles-controls-agent-actions) — the foundational "cost is one axis" post; introduces [action authority](/glossary#action-authority) alongside [budget authority](/glossary#budget-authority). +- [How Cycles Meters Caller-Assigned Action Exposure](/blog/beyond-budget-how-cycles-controls-agent-actions) — the foundational "cost is one axis" post; separates [action authority](/glossary#action-authority) from [budget authority](/glossary#budget-authority). - [The AI Agent Audit Trail You're Already Building](/blog/runtime-authority-byproducts-audit-trail-and-attribution-by-default) — the audit dimension treated in depth; how the runtime-authority ledger satisfies CFO, auditor, and FinOps questions in one place. - [AI Agent Risk Assessment](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk) — the risk-tier framework (read-only / write-local / write-external / mutation / execution) the post invokes for the action-side dimension. - [Runtime Authority vs Guardrails vs Observability](/blog/runtime-authority-vs-guardrails-vs-observability) — the lifecycle companion: why pre-execution decisions are a different job from post-hoc observability. diff --git a/blog/real-time-budget-alerts-for-ai-agents.md b/blog/real-time-budget-alerts-for-ai-agents.md index f7479bb4..4ac4be73 100644 --- a/blog/real-time-budget-alerts-for-ai-agents.md +++ b/blog/real-time-budget-alerts-for-ai-agents.md @@ -16,7 +16,7 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -Consider a common scenario: an infrastructure team has budget dashboards. Prometheus scrapes every 15 seconds. Grafana panels show utilization curves. Alert rules fire when thresholds cross 90%. +Consider an illustrative scenario: an infrastructure team has budget dashboards. Prometheus scrapes every 15 seconds. Grafana panels show utilization curves. Application alert rules fire when thresholds cross 90%. @@ -26,15 +26,15 @@ The monitoring worked. The problem was latency. Polling-based alerting has a str ## The detection gap -Representative detection latencies for a budget exhaustion event: +The delivery paths have different latency characteristics: -| Detection Method | Typical Time to Alert | Typical Time to Human Action | +| Detection Method | Detection boundary | Main delay | |---|---|---| -| Polling dashboard (60s interval) | 30-60s | 2-5 minutes | -| Prometheus alert (15s scrape + 1m for-duration) | ~75s | 3-6 minutes | -| Webhook event (push on state change) | <1s | 1-3 minutes | +| Polling dashboard | Next query or refresh | Poll interval | +| Prometheus alert | Next scrape plus alert rule evaluation | Scrape interval and configured `for` duration | +| Webhook event | Implemented state transition enqueues delivery | Queueing, receiver latency, and retries | -The webhook doesn't make humans faster. It eliminates the detection delay entirely. The event fires the instant the budget state changes — not on the next scrape, not after a for-duration averaging window. +A webhook can avoid waiting for the next metrics scrape, but it is still asynchronous and has no sub-second end-to-end guarantee. The events service must be running, the event type must have an implemented emission hook, and delivery latency depends on the queue and receiver. This is why we built a webhook event system into Cycles v0.1.25. @@ -42,7 +42,7 @@ This is why we built a webhook event system into Cycles v0.1.25. > **As of post date.** The current Admin API `EventType` enum registers **51 event types across 7 categories** (the `webhook` category and the tenant-close cascade types were added later). For the live count and per-category breakdown, see the [Event Payloads Reference](/protocol/event-payloads-reference). -Every observable state change in the system produces an event. We organized them into 6 categories covering the full lifecycle: +The original v0.1.25 schema organized 41 registered event types into 6 categories. Registration did not mean every type was emitted; the live reference distinguishes implemented hooks from planned types: | Category | Count | Covers | |---|---|---| @@ -53,18 +53,20 @@ Every observable state change in the system produces an event. We organized them | **policy** | 3 | Created, updated, deleted | | **system** | 5 | Store connection lost/restored, high latency, [webhook delivery](/glossary#webhook-delivery) failed, webhook test | -The six events that matter most for incident response: +Examples of currently emitted events useful for incident response: | Event | When It Fires | Why It Matters | |---|---|---| -| `budget.exhausted` | Remaining = 0 | All reservations for this scope will be denied until funded | -| `budget.over_limit_entered` | Debt exceeds overdraft limit | New reservations blocked; operator intervention required | -| `reservation.denied` | Agent can't reserve budget | Agents are failing — check if budget needs funding or if there's a runaway consumer | -| `budget.threshold_crossed` | Utilization crosses 80%, 95%, or 100% | Early warning before exhaustion | +| `budget.exhausted` | A matching ledger transitions from positive remaining to zero | New positive reservations that derive that scope and unit may be denied | +| `budget.over_limit_entered` | A ledger transitions into `is_over_limit` | New reservations matching that ledger are blocked until state is reconciled | +| `reservation.denied` | A dry-run reserve or decide evaluation resolves to a budget-state denial | Inspect the hypothetical reason and scope; use runtime metrics/application errors for live 4xx denials | +| `reservation.commit_overage` | Committed actual exceeds the reservation estimate | Recalibrate estimates and inspect the overage policy result | | `api_key.auth_failed` | Authentication attempt with invalid key | Security event — possible credential leak or misconfiguration | -| `system.store_connection_lost` | Redis connection failed | Infrastructure incident — budget enforcement depends on Redis availability | +| `system.webhook_delivery_failed` | A delivery permanently fails after retries | Inspect the event store or metrics; this loop-safe meta-event is not recursively delivered as another webhook | -Every event includes a standard payload: who caused it (`actor`), what changed (`data`), where it happened (`scope` path like `tenant:acme-corp/workspace:prod/agent:support-bot`), and a millisecond-precision `timestamp`. Events are emitted by both the runtime enforcement server (reserve/commit operations) and the admin control plane (CRUD operations) — a single [webhook subscription](/glossary#webhook-subscription) captures both. +`budget.threshold_crossed` and `system.store_connection_lost` are registered protocol event types but are not emitted by the current reference services. + +Every event includes the common envelope fields defined by the protocol, including its type, category, source, tenant, and timestamp. Optional fields such as `actor`, `data`, and `scope` depend on the event type and producer. The runtime and admin services emit selected registered lifecycle events; registering an event type does not guarantee that every state change produces one. A matching [webhook subscription](/glossary#webhook-subscription) can receive events from both planes. ## Architecture: why a separate delivery service @@ -86,9 +88,9 @@ Three services, three workloads, three scaling profiles: | Service | Workload | Latency Target | Scaling Driver | |---|---|---|---| -| Runtime (reserve/commit) | Synchronous, hot path | [Reserve 7.9ms and commit 5.7ms p99 in the published v0.1.25.3 benchmark](/blog/cycles-server-performance-benchmarks) | Agent request volume | -| Admin (CRUD) | Synchronous, operator-facing | <200ms | Human operator actions | -| Events (webhook delivery) | Asynchronous, variable latency | Best-effort | Subscription count × event rate | +| Runtime (reserve-commit) | Synchronous, hot path | [Reserve 7.9ms and commit 5.7ms p99 in the published v0.1.25.3 benchmark](/blog/cycles-server-performance-benchmarks) | Agent request volume | +| Admin (CRUD) | Synchronous, operator-facing | Deployment-defined | Human and automation requests | +| Events (webhook delivery) | Asynchronous, variable latency | At-least-once with retries; no fixed latency SLO | Subscription count × event rate | Why not embed delivery in the runtime server? Webhook endpoints are external HTTP services with unpredictable latency. A slow endpoint or DNS timeout can add hundreds of milliseconds to the reserve-commit path. The published v0.1.25.3 benchmark measured a reserve-plus-commit lifecycle at 18.4ms p99 on its stated hardware, so external delivery latency belongs off that hot path. Even a background executor needs strict isolation and backpressure to avoid resource contention with request processing. @@ -100,7 +102,7 @@ Multiple events service instances can safely share the queue. `BLMOVE` atomicall ## Delivery guarantees: at-least-once with HMAC signing -We chose at-least-once delivery over exactly-once. In a distributed system where the webhook receiver is an external HTTP service, exactly-once is impossible without two-phase commit — and two-phase commit across the internet is a fiction. The practical choice is: deliver at least once and give receivers the tools to deduplicate. +The reference service uses at-least-once delivery. Because it cannot atomically commit both the receiver's external side effect and its own acknowledgement, an ambiguous outcome can be redelivered. Receivers therefore need idempotent processing or event-ID deduplication. Every delivery includes an `X-Cycles-Event-Id` header containing the event's unique ID. Receivers store processed event IDs and skip duplicates, a standard webhook idempotency pattern. @@ -171,7 +173,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ }' ``` -The response includes a signing secret (returned once — store it). Your middleware transforms Cycles events into PagerDuty Events API v2 format, mapping `budget.exhausted` to severity `critical` and `reservation.denied` to `warning`. Use the `event_id` as PagerDuty's `dedup_key` to correlate retried deliveries to the same alert. +The response includes a signing secret (returned once—store it). Your middleware can map `budget.exhausted` to severity `critical` and `reservation.denied` to a nonpaging calibration signal. Use the `event_id` as PagerDuty's `dedup_key` to correlate retried deliveries to the same alert. Live reservation errors require a metric or application alert because the current exception path does not emit `reservation.denied`. We have full integration guides with code examples for [PagerDuty, Slack, Datadog, Microsoft Teams, Opsgenie, and ServiceNow](/how-to/webhook-integrations), plus a [custom receiver pattern](/how-to/webhook-integrations#integration-custom-receiver-direct) with signature verification in Python, Node.js, and Go. @@ -179,15 +181,9 @@ Tenants can also create their own webhook subscriptions via `/v1/webhooks` using Webhook URLs are validated at creation time with SSRF protection enabled by default: RFC 1918 private IP ranges, loopback, and link-local addresses are blocked, and HTTPS is required in production. These can be configured via `PUT /v1/admin/config/webhook-security` for environments that need internal endpoint access. -## What's next - -The v0.1.25 event system delivers threshold alerts at the default levels (80%, 95%, 100% utilization). Coming next on the implementation roadmap: - -- **Per-subscription threshold customization**: override the default 80%/95%/100% thresholds for specific subscriptions — e.g., a high-priority workspace that should alert at 50% -- **Burn rate anomaly detection**: alert when spend rate exceeds the rolling average by a configurable multiplier -- **Rate spike detection**: alert on reservation denial rate spikes and expiry rate spikes across rolling windows +## Registered but not yet emitted alerts -These are defined in the [v0.1.25 spec](https://github.com/runcycles/cycles-protocol/blob/main/cycles-governance-admin-v0.1.25.yaml) as `WebhookThresholdConfig`. The schema is finalized; server-side implementation is on the roadmap. +The v0.1.25 governance schema registers `WebhookThresholdConfig` and event types for utilization thresholds, burn-rate anomalies, and reservation denial/expiry spikes. The current reference services do not produce those automatic alerts. Use Prometheus or application telemetry for pre-exhaustion thresholds today, and consult the [Event Payloads Reference](/protocol/event-payloads-reference) before subscribing to a registered event type. --- diff --git a/blog/retry-storms-and-idempotency-in-agent-budget-systems.md b/blog/retry-storms-and-idempotency-in-agent-budget-systems.md index 96e23071..6af98898 100644 --- a/blog/retry-storms-and-idempotency-in-agent-budget-systems.md +++ b/blog/retry-storms-and-idempotency-in-agent-budget-systems.md @@ -125,7 +125,7 @@ Idempotency keys aren't just a dedup mechanism — they're a monitoring signal. **High attempt counts.** If your key format encodes attempt number, alert when attempt counts exceed expected retry depth. `run-*-attempt-10` appearing in logs means something is wrong. -**Key reuse rate.** Track the ratio of unique keys to total reservation requests. A healthy system sees 1:1 — every reservation has a unique key. A ratio climbing toward 2:1 or 3:1 indicates clients retrying rapidly. Climbing beyond 5:1 means a loop. +**Attempts per unique key.** Track total reservation attempts divided by unique idempotency keys. A healthy no-retry path is near 1:1. A ratio climbing toward 2 or 3 attempts per key indicates retries; sustained values above the retry policy's intended ceiling indicate a loop or recovery bug. A high ratio is expected only when the same logical operation is deliberately replayed. **Duplicate match rate on reserves.** Track duplicate-match rate on reserves in your implementation. A spike means retries are being deduplicated at the budget layer, which is often the clearest signal that a retry storm is underway. diff --git a/blog/rolling-out-action-authority-on-new-surfaces.md b/blog/rolling-out-action-authority-on-new-surfaces.md index 7898b590..169f2723 100644 --- a/blog/rolling-out-action-authority-on-new-surfaces.md +++ b/blog/rolling-out-action-authority-on-new-surfaces.md @@ -39,16 +39,16 @@ The shipped Cycles server can evaluate scoped budgets, including caller-assigned The reasons that make calendar-driven cutovers fail for budget enforcement apply unchanged to the new surfaces. The healthy pattern is the same: instrument first, observe, calibrate, then enforce — per surface, not in one big bang. The [synthesis post](/blog/what-four-new-surfaces-taught-us) framed reserve-commit as the stable layer the four surfaces preserved at their boundaries: every surface gets the same shape of rollout, with different specifics in the middle. -The four-week structure that fits most teams: +The following four-phase schedule is an illustrative planning aid: | Week | Goal | Output | |---|---|---| | 1 | Inventory the agent fleet's action surfaces | Surface-by-surface list with current gate state | -| 2 | Shadow-mode instrumentation, per surface | Dry-run decisions flowing for every surface | +| 2 | Shadow-mode instrumentation, per surface | Application logs combining host-rule preflight results with Cycles dry-run budget decisions | | 3 | Per-surface gate primitives + calibration | Application gate rules and Cycles budget amounts tuned against shadow data | | 4 | Cutover, surface by surface, in false-positive-cost order | Hard enforcement on the surface where a wrongful denial costs least first; remaining surfaces on a planned schedule | -The schedule is illustrative. Teams with mature shadow-mode tooling and a single surface in scope can move faster; teams adopting all four surfaces simultaneously will usually want two weeks per surface, not one. The structure is the load-bearing piece, not the calendar. +Choose the duration of each phase from traffic volume, workload variation, surface criticality, and the time needed to exercise rare failure cases. The structure matters more than the calendar. ## Week 1: Inventory the Action Surfaces @@ -67,23 +67,23 @@ For each agent in scope, list every consequential action it can take. The invent | Frequency | Calls per hour at production volume | ~200/hr | | Existing audit | Where the action shows up if it goes wrong | CRM audit log only | -The output is usually a mess of rows where the team realizes a third of the agent's actions don't have any pre-execution gate and another third have a gate that does the wrong thing (rate limit when it should be authority, content guardrail when it should be [action authority](/glossary#action-authority)). That mess is the actual inventory. +The output often exposes actions without a pre-execution authorization or budget gate, as well as controls that answer a different question—for example, a rate limit cannot validate a merge target, and a content guardrail cannot bound cumulative spend. -A useful sanity check at the end of week 1: every row in the inventory should map to exactly one of the canonical surfaces (outbound tool, memory write, merge, click, voice, or a sibling not yet covered). Rows that don't map are typically either misclassified or signal a new surface the corpus has not addressed yet. Either is worth a separate conversation before instrumentation. +Use the listed surfaces as prompts, not a closed taxonomy. If an action does not fit them, document its actual execution boundary, authorization control, exposure unit, and recovery path before instrumentation. ## Week 2: Shadow-Mode Instrumentation Per Surface -Once the inventory exists, every row needs a shadow-mode path. The general dry-run pattern from [How to Add Runtime Enforcement Without Breaking Your Agents](/blog/how-to-add-runtime-enforcement-without-breaking-your-agents) applies; what changes per surface is what gets instrumented. +Once the inventory exists, each protected row needs an application-side preflight path. Host rules evaluate authorization and action-specific conditions. A separate Cycles `decide()` or `reserve(dry_run: true)` call can evaluate the caller-assigned budget without persistence. The general dry-run pattern from [How to Add Runtime Enforcement Without Breaking Your Agents](/blog/how-to-add-runtime-enforcement-without-breaking-your-agents) applies; what changes per surface is what the host validates and records. | Surface | Shadow-mode call | What to log | |---|---|---| -| Outbound tool calls | `decide()` / `reserve(dry_run: true)` before the tool dispatches | Tool name, args, would-be decision, would-be caps | -| Memory writes | `decide()` against the write target + scope before the memory layer persists | Operation, scope, provenance fields, would-be decision | -| Merge buttons | Pre-execution hook on `gh pr merge` (or equivalent) in the agent harness | Source branch, target branch, head SHA, agent identity, would-be decision | -| Computer-use clicks | Pre-emission hook on the click event in the agent harness | URL pattern, DOM target (if available), action verb, screenshot crop, would-be decision | -| Voice frames | [Reservation](/glossary#reservation)-at-call-start probe, plus per-turn-boundary dry-run | Call-level features, predicted consumption, would-be reservation amount | +| Outbound tool calls | Host authorization plus `decide()` / `reserve(dry_run: true)` before dispatch | Tool identity, validated arguments or a safe digest, host-rule result, Cycles budget decision | +| Memory writes | Host validates tenant and write target; Cycles evaluates submitted exposure | Operation, scope, provenance fields, host-rule result, Cycles budget decision | +| Merge buttons | Pre-execution hook on `gh pr merge` (or equivalent) in the agent harness | Source branch, target branch, head SHA, agent identity, host-rule result, Cycles budget decision | +| Computer-use clicks | Pre-emission hook on the click event in the agent harness | URL pattern, DOM target (if available), action verb, host-rule result, Cycles budget decision | +| Voice calls | Call-start estimate plus host-controlled re-evaluation points | Call-level features, predicted consumption, host-rule result, Cycles budget decision | -The output of week 2 is a stream of dry-run decisions per surface. Teams with mature event pipelines route these into the same observability stack they use for everything else; teams without can start with structured logs. Either works for the calibration phase. +The output of phase 2 is an application-owned stream of preflight results per surface. Cycles dry runs create no reservation or balance mutation. The current server emits `reservation.denied` for denied evaluations, but the application must log all responses and later outcomes for a complete stream. A few practical things to watch for during week 2: @@ -99,12 +99,12 @@ With shadow data flowing, week 3 is where the per-surface specifics enter the pi | Gate primitive | What it does | Tune against | |---|---|---| -| Per-tenant write quota | Cap writes per run per tenant scope | Distribution of writes-per-run from shadow data | +| Per-tenant write quota | Host counter, or caller-assigned `RISK_POINTS` reserved by every protected write | Distribution of writes and assigned exposure from application shadow data | | TTL on unverified facts | Facts written without corroboration auto-expire | Survival rate of memory entries vs production retention need | | Per-write provenance | Run ID + agent identity + risk-budget context attached to every write | Existing audit trail — fields you already log | -| Scope isolation enforcement | Reject cross-tenant writes unless explicitly allowed | Shadow events showing cross-tenant attempts | +| Scope isolation enforcement | Host rejects cross-tenant writes unless explicitly authorized | Application preflight records showing cross-tenant attempts | -Calibration target (starting heuristic): when shadow mode is evaluating the proposed quotas, the would-be denial rate on memory writes typically sits in the 1–5% band once the calibration data has stabilized — which usually takes at least a week of representative production traffic. Substantially higher rates suggest the quota is too tight; substantially lower rates suggest the quota isn't constraining anything in practice. Tune from the shadow data, not from this band alone. (Calibrating four surfaces in parallel typically requires extending Week 3 to 2–3 weeks; the table above is the single-surface schedule.) +Calibration target: exercise legitimate writes, cross-tenant attempts, duplicate instrumentation, and the runaway pattern the boundary is meant to contain. Set acceptable false-denial and missed-detection rates from the memory product's own SLOs; Cycles defines no standard percentage or observation duration. ### Merge buttons @@ -137,41 +137,40 @@ Calibration target: the would-be denial rate on clicks should distinguish the ro | Tier-aware gating | Slow-path tool calls sync-gated; fast-path audio against predictive reservation | Shadow data on which calls trigger tool look-ups | | Per-turn-boundary re-check | Re-reservation lands at turn boundaries, not mid-utterance | Turn-boundary timing observed in shadow | -Calibration target: the reserve-to-commit ratio (per the [estimate-drift framework](/blog/estimate-drift-silent-killer-of-enforcement)) should sit between 0.8 and 1.2 on call-level reservations after the first week of shadow data. If it's drifting, recalibrate before cutover. +Calibration target: choose a reserve-to-commit tolerance from call-duration variance and the amount of concurrent headroom you need. The [estimate-drift framework](/blog/estimate-drift-silent-killer-of-enforcement) explains why Cycles defines no universal ratio band or observation duration. Across all four surfaces, the calibration signals echo the [shadow-to-enforcement decision tree](/blog/shadow-to-enforcement-cutover-decision-tree): false-positive denial rate, calibration accuracy (where it can be defined — voice has a true reserve-to-commit ratio; the others use cap-fire rate vs shadow baseline as the analogue), instrumentation coverage, and operational readiness. The thresholds vary by surface; the questions don't. ## Week 4: Cutover, Lowest-False-Positive-Cost Surface First -The cutover decision is per-surface, not all-or-nothing. The order below is ranked by *cost of a false-positive denial* — what happens if the gate denies an action the team meant to allow — not by the action's own blast radius (which the four siblings address). Lowest-cost-of-false-positive first: +The cutover decision is per-surface, not all-or-nothing. Rank your own surfaces by the cost of a false-positive denial, recoverability, traffic volume, and action blast radius. One illustrative order is: -1. **Memory writes** — the highest-volume surface, where a denied write is cheap to recover from. The agent can retry or work around; few false-positive denials produce immediate customer-facing damage. Good first. -2. **Computer-use clicks** — similar volume, more sensitive to false positives because a denied click can break a workflow mid-step. The fresh-screenshot cap is the one that needs the most calibration headroom. -3. **Voice frames** — lower per-call volume but higher per-failure visibility (a denied frame mid-conversation is audible). The predictive reservation pattern means most of the cutover risk is concentrated in the first 24 hours. -4. **Merge buttons** — lowest volume but highest cost per false-positive denial. A denied merge that should have been allowed produces a stuck PR and an engineering escalation. Last. +1. **Memory writes** — first only when a denied write is recoverable and does not discard required state. +2. **Computer-use clicks** — later when a denial can interrupt a workflow mid-step. +3. **Voice calls** — later when a failed call-start reservation or host re-check is immediately user-visible. +4. **Merge buttons** — later when a false denial blocks a time-sensitive delivery path. + +Your ordering may be different: for example, an ephemeral memory write can be less important than a merge, while a durable compliance record can be more important. Each cutover follows the same per-surface checklist: -- Shadow data shows the calibration targets met for at least the last 7 days -- The team has classified a sample of would-be denials. >85% in the intended class is a minimum triage bar before cutover; substantially higher fractions are the target for sensitive surfaces (merge, voice mid-conversation) +- Application-side shadow data covers representative normal traffic and the known failure cases +- The team has classified enough would-be denials to meet the surface's false-denial SLO - An on-call rotation knows what to do when the first denial fires (per the [operating-budget-enforcement guide](/blog/operating-budget-enforcement-in-production)) -- The kill switch is wired and tested — flipping the gate back to shadow mode should take seconds, not a deploy +- The application kill switch is wired, tested, and meets the surface's rollback-time objective without a redeploy - The rollback decision tree (below) is reviewed and signed off -The cutover itself is undramatic when the calibration is good. The first denial that fires looks like the shadow denials the team has been classifying for days. The on-call playbook handles it. The next 24 hours produce some scattered denials, mostly on patterns the team has seen before. By day 3, the metrics stabilize. - -The cutover is undramatic in a different way when the calibration is bad. The first hour produces a denial rate the team has not seen before. The on-call playbook handles five denials, then ten, then a hundred. The kill switch is for that hour. Treat its existence as a routine operational primitive, not a failure marker. +After cutover, compare live denials, user impact, and application-rule results with the calibrated baseline. Use the kill switch when the surface breaches its rollback objective; the required window depends on traffic and impact. ## The Rollback Decision Tree | Signal observed | Reaction | |---|---| -| Denial rate >2× shadow-data rate, sustained over 1 hour | Flip the gate back to shadow mode; resume tuning | -| Denial rate >5× shadow-data rate, sustained over 15 minutes | Kill switch — gate fully off; incident review | -| Reserve-to-commit ratio drifts outside 0.8–1.2 in a 24-hour window *(voice-specific; the ratio is well-defined for predictive reservation but not for the other surfaces' cap-fire denominators)* | Adjust reservation estimates; keep enforcement live | -| A specific tenant produces a disproportionate share of denials (rough starting heuristic: >20%) | Partial rollback — exempt that tenant scope, keep enforcement on the rest | -| The same agent identity produces a disproportionate share of denials (rough starting heuristic: >40%) | Scope rollback — exempt that agent, investigate separately | -| Surface-specific application rule fires above the calibrated baseline (rough starting heuristic: a 30%+ jump from shadow data, e.g. the host's fresh-screenshot rule) | Tune that rule, do not roll the whole surface back | +| Denial rate breaches the surface's user-impact or availability SLO | Restore the application gate to dry-run/bypass mode; resume tuning | +| A single denial causes an unacceptable irreversible outcome | Stop the affected surface and begin incident review | +| Reserve-to-commit ratio leaves the workload-specific tolerance *(voice-call estimate example)* | Re-evaluate estimates; roll back if the drift threatens availability or exposure objectives | +| One tenant or agent dominates unexpected denials | Investigate its scope mapping and workload; use a scoped rollback only if that exemption is authorized and safe | +| An application rule fires materially above its calibrated baseline | Tune or roll back that host rule based on its surface-specific SLO | The decision tree is per-surface. Memory writes rolling back does not require clicks to roll back. The point of cutting over one surface at a time is that the blast radius of a bad cutover is bounded to that surface. @@ -179,14 +178,14 @@ The decision tree is per-surface. Memory writes rolling back does not require cl The metrics that matter day-1 are not the same as the metrics that matter month-1. Both phases have their own [dashboards](/glossary#dashboard). -**First 72 hours:** +**Initial observation window:** - Sustained denial rate per surface, per tenant - Kill-switch state (boolean — is the gate live or off?) - Page volume from the on-call rotation - A sample of denied actions, manually classified — confirms the gate is denying the intended pattern -**First month:** +**Longer comparison window:** - Voice reserve-to-commit ratio, trending; for the other three surfaces, application-rule fire rates vs the shadow-mode baseline - Drift in (target, intent) distribution for clicks — A/B tests, new admin features, agent prompt updates all show up here first @@ -198,7 +197,7 @@ The metrics that matter day-1 are not the same as the metrics that matter month- - Anything in the surface-specific drift signals from [policy drift](/blog/policy-drift-in-ai-agents). Memory writes, in particular, are a slow-drift surface — what shadow data shows in week 1 may not be what production looks like in month 3. -A runbook entry per surface is the minimum bar. Most teams find they want one per surface plus one cross-surface entry for the kill switch. +A runbook entry per enforced surface keeps its host rules, budget integration, and rollback procedure explicit. A cross-surface entry can document shared application kill switches. ## A Short Runbook Template @@ -220,13 +219,13 @@ A few patterns to avoid, drawn from teams that have done this before: - **Don't cut over all surfaces simultaneously.** The whole point of per-surface gates is per-surface blast radius. A simultaneous cutover collapses that property. - **Don't skip shadow mode because "we already know what to enforce."** The team that knows what to enforce is the team that has been wrong before; the shadow phase is how you find out where you're wrong this time. -- **Don't treat the cutover date as the goal.** The goal is the enforcement that works in production. A cutover that has to roll back the same week was not a successful cutover. -- **Don't tune away the denial rate to zero.** A gate that never fires is a gate that is not protecting anything. Some denial rate is expected and healthy. -- **Don't separate authority from observability.** The same audit trail that catches drift in policy is the audit trail that proves to auditors the system was under control. They are not two systems. +- **Don't treat the cutover date as the goal.** The goal is enforcement that meets the surface's exposure and availability objectives; rollback is a designed response, not proof that the rollout failed. +- **Don't infer effectiveness from denial rate alone.** A zero production-denial rate can be valid, but test cases must demonstrate that the boundary denies the failures it is meant to catch. +- **Don't separate authority from observability.** Correlate host authorization records, Cycles lifecycle data, and action outcomes. Together they can support an audit, but budget records alone do not prove the action was authorized or compliant. ## What Action Authority Adoption Is -A minimum bar: the inventory from week 1, the shadow data from week 2, the per-surface application rules and budget amounts tuned in week 3, and the per-surface cutover in week 4 (or weeks 4–N for multi-surface adoption) in false-positive-cost order with a tested kill switch, runbook entries committed before the cutover, and the discipline of treating each new surface as its own rollout on its own schedule. +A practical readiness package includes the surface inventory, application-owned preflight records, tested host rules, calibrated budget amounts, a per-surface cutover order, a tested application kill switch, and runbook entries written before enforcement. The framework is the cheap part. The rollout is the work. diff --git a/blog/run-audit-evidence-drill-before-audit-day.md b/blog/run-audit-evidence-drill-before-audit-day.md index 7a38d8f0..ea9b9976 100644 --- a/blog/run-audit-evidence-drill-before-audit-day.md +++ b/blog/run-audit-evidence-drill-before-audit-day.md @@ -96,7 +96,7 @@ Capture these fields in the drill notes: |---|---| | `evidence_id` | Content address to verify and fetch | | `cycles_evidence_url` | Retrieval path used by the reviewer | -| `trace_id` / `correlation_id` | Join point across events, application logs, and audit records | +| `trace_id` / `request_id` | Join point across related Cycles requests, application logs, events, and audit records | | Denial code | The policy outcome being proven | | Subject scope | [Tenant](/glossary#tenant), workspace, app, workflow, agent, or toolset boundary | | Timestamp | Human-readable incident time, separate from envelope `issued_at_ms` | @@ -268,7 +268,7 @@ Minimum contents: | Key material reference | JWK Set URL or archived key-set snapshot used for verification | | Rotation note | Which `kid` covered `issued_at_ms`; active/retired status | | Decision summary | Human-readable statement of what was denied and why | -| Correlation links | `trace_id`, `correlation_id`, incident ticket, relevant audit/event IDs | +| Correlation links | `trace_id`, `request_id`, applicable admin-event `correlation_id`, incident ticket, relevant audit/event IDs | | Retention statement | Hot retention, cold archive, restore procedure, key-history retention | | Gaps found | Findings, owners, due dates, and follow-up drill date | diff --git a/blog/runaway-demo-agent-cost-blowup-walkthrough.md b/blog/runaway-demo-agent-cost-blowup-walkthrough.md index a34cca91..f4ababab 100644 --- a/blog/runaway-demo-agent-cost-blowup-walkthrough.md +++ b/blog/runaway-demo-agent-cost-blowup-walkthrough.md @@ -189,9 +189,9 @@ for SCOPE in \ done ``` -When the `@cycles` decorator calls `POST /v1/reservations`, the server walks from the agent scope up to the tenant root, checking each ancestor's budget. If any scope is exhausted, the server returns `409 BUDGET_EXCEEDED` and the [reservation](/glossary#reservation) is denied. No partial execution. No overrun. +When the `@cycles` decorator calls `POST /v1/reservations`, the server checks each applicable populated standard scope atomically. If any matching ledger lacks capacity, the server returns an error such as `409 BUDGET_EXCEEDED` and creates no reservation. The protected call is blocked only if the application makes this reservation a mandatory boundary; commit overage behavior remains governed by the selected overage policy. -In this demo every scope has the same $1.00 limit, so the agent-level budget is the binding constraint. In production, you would set different limits at different levels — a $1.00 per-run budget at the agent level, a $50/day budget at the workspace level, and a $500/month budget at the tenant level. The server enforces whichever limit is hit first. +In this demo every populated scope has the same $1.00 limit, so the agent-level budget is the binding constraint. In production, you might use a unique workflow value for a $1.00 per-run ledger, a $50/day workspace budget, and a $500/month tenant budget. The server checks every matching populated standard scope and rejects when any one lacks room. ## Why not just use a rate limit? diff --git a/blog/runtime-authority-vs-guardrails-vs-observability.md b/blog/runtime-authority-vs-guardrails-vs-observability.md index 60476692..fdfe532f 100644 --- a/blog/runtime-authority-vs-guardrails-vs-observability.md +++ b/blog/runtime-authority-vs-guardrails-vs-observability.md @@ -29,7 +29,7 @@ Every control worked as designed. None of them prevented the damage. The problem is not that the team lacked visibility or safety checks. The problem is that neither visibility nor safety checks answer the question that actually matters for autonomous systems: **should this next action proceed, given what the system has already consumed?** -Three approaches. Three different questions. Only one acts before execution. +Three approaches answer different questions. Guardrails and budget authorities can both act before execution; observability describes what was recorded. ## Three approaches to agent control @@ -37,9 +37,9 @@ Three approaches. Three different questions. Only one acts before execution. |---|---|---|---| | **Observability** | What happened? | After execution | Nothing — it reports | | **Guardrails** | Is this output or action acceptable? | During or after execution | Content quality, structural validity, per-action policy | -| **[Runtime authority](/glossary#runtime-authority)** | Should this happen at all? | Before execution | Cumulative spend, bounded [exposure](/glossary#exposure), action permissions | +| **[Runtime budget authority](/glossary#runtime-authority)** | Does this submitted operation fit the configured budget? | Before execution | Cumulative spend and caller-assigned [exposure](/glossary#exposure) | -A guardrail can say "this tool output looks unsafe." Observability can say "this run sent 200 emails." Runtime authority can say "this run is not allowed to send any more emails without approval." +A guardrail or application policy can say "this tool is not authorized." Observability can say "this run sent 200 emails." A runtime budget can say "the next submitted email-exposure estimate does not fit the configured allocation." Those controls must be composed; the budget response does not grant tool permission. These are not competing alternatives. They are distinct layers that solve different problems at different points in the execution lifecycle. @@ -83,7 +83,7 @@ The key properties that distinguish runtime authority from guardrails and observ - **Pre-execution.** The decision happens before the action, not after. - **Enforcement.** The system can block or constrain, not just observe and report. -- **Scoped.** Decisions apply at the right level — per tenant, per workflow, per agent, per run — not just globally or per-action. +- **Scoped.** Decisions apply at the right standard level — tenant, workspace, app, workflow, agent, or toolset. Applications can map a run ID to a workflow value when they need a per-run ledger. - **Concurrency-safe.** Atomic [reservations](/glossary#reservation) prevent race conditions. Two agents cannot both claim the same remaining budget. - **Reconciled.** Budget is reserved before execution, actual cost is committed after. The difference is released. @@ -97,7 +97,7 @@ For the full definition, see [What Is Runtime Authority for AI Agents?](/blog/wh **Guardrails alone:** They work until concurrency breaks the counter, retries multiply the cost, or fan-out exceeds the aggregate limit that no individual check tracks. -**Runtime authority alone:** You can enforce limits and block unauthorized actions. But without observability, you cannot debug denied requests, understand cost patterns, or set accurate limits. Without guardrails, you have no content-level validation — the system might stay within budget while producing unsafe outputs. +**Budget authority alone:** You can reject submitted amounts that do not fit configured ledgers. It does not authorize tools or arguments. Without observability, you cannot reconstruct external outcomes or tune estimates well; without content and action guardrails, the system might stay within budget while producing unsafe output or taking an otherwise disallowed action. No single approach covers the full control surface. They compose — they do not compete. @@ -105,11 +105,11 @@ No single approach covers the full control surface. They compose — they do not ``` Agent decides to act - → Runtime authority: reserve budget, check policy + → Host authorization and guardrails: validate identity, tool, arguments, and content + → Budget authority: reserve submitted amount → DENY → stop; return fallback or surface limit - → ALLOW_WITH_CAPS → proceed with constraints + → ALLOW_WITH_CAPS → host applies configured constraints → ALLOW → proceed normally - → Guardrails: validate inputs, check content policy → Execute action (model call, tool invocation, side effect) → Guardrails: validate output, check schema and safety → Observability: log trace, attribute cost, surface patterns @@ -122,9 +122,9 @@ Remove any one layer and a gap opens. Remove observability and you enforce blind ## What Cycles provides -Cycles is the runtime authority layer. It treats agent control as [runtime permissioning](/blog/what-is-runtime-authority-for-ai-agents#how-cycles-approaches-runtime-authority) over spend, risk, and actions — not as an after-the-fact reporting problem. +Cycles supplies the runtime budget-authority part of a composed control boundary. It enforces scoped spend and caller-assigned exposure limits before protected execution; IAM and application policy handle identity, tools, and arguments. -Before the next action executes, Cycles decides whether it is allowed, under what constraints, or not at all. That decision is made by a [protocol](/protocol/api-reference-for-the-cycles-protocol) — not by a proxy, not by application code, not by a dashboard with alerts. +Before a protected action executes, an integration submits a budget request. The [protocol](/protocol/api-reference-for-the-cycles-protocol) determines whether configured capacity is available and may return operator-configured caps. Application or harness code must place that call on the execution path, authorize the action separately, and enforce both results. Because it is protocol-based, Cycles works across frameworks, languages, and providers. It does not replace your observability platform or your content guardrails. It fills the layer between them that most teams are missing. diff --git a/blog/shadow-to-enforcement-cutover-decision-tree.md b/blog/shadow-to-enforcement-cutover-decision-tree.md index d6773f10..7817bfc1 100644 --- a/blog/shadow-to-enforcement-cutover-decision-tree.md +++ b/blog/shadow-to-enforcement-cutover-decision-tree.md @@ -27,7 +27,7 @@ Shadow mode has been instrumented on every model call for ten days. Dry-run deci A calendar-driven cutover — "it's been two weeks, flip the switch" — is the version that gets teams into trouble. The signal-driven version — "the shape of what we're seeing matches what hard enforcement looks like in production" — is the version that ends quietly. The difference between those two decisions is the difference between a clean cutover and a 3 AM rollback, and most teams don't know which version they made until afterwards. -This post is a decision tree for that call. Four signal categories, suggested threshold ranges, and explicit guidance on what to cut over first, when to stop, and how to reverse course if the signals turn against you. +This post is a decision tree for that call: four signal categories and explicit guidance on what to cut over first, when to stop, and how to reverse course if the signals turn against you. @@ -39,7 +39,7 @@ The failure isn't in the duration. The failure is that a calendar has no opinion Industry patterns learned this years ago. Stripe's rate-limiter post puts it plainly: ["Dark launch each rate limiter to watch the traffic they would block"](https://stripe.com/blog/rate-limiters). Istio ships an `istio.io/dry-run: "true"` annotation (Alpha status) that lets `AuthorizationPolicy` evaluate without blocking so teams can measure. OPA Gatekeeper's [`enforcementAction: dryrun`](https://open-policy-agent.github.io/gatekeeper/website/docs/violations/) does the same for Kubernetes admission, surfacing violations in the constraint's `status` field. Cloudflare's WAF offers a `Log` action before `Block`. Every maturing enforcement tool converges on the same shape — evaluate, measure, calibrate, then flip — and none of them recommend a fixed duration. They recommend a set of signals. -Cycles' shadow mode is `dry_run: true` on a reservation request: the server runs the full scope-derivation, budget-check, and caps-computation logic, returns the decision (`ALLOW`, `ALLOW_WITH_CAPS`, or `DENY`) along with affected scopes and optional balance snapshots, and leaves budget state untouched. No reservation is persisted, no balance is modified, and base `dry_run` does not emit a reservation event — the decision round-trips in the response. (Teams that want emission-driven observation can use the `observe_mode` extension introduced in v0.1.26, which emits `reservation.observed_allowed` / `reservation.observed_denied`; that's a different track from base dry-run.) Your agent proceeds regardless of the result. See [How to Add Runtime Enforcement Without Breaking Your Agents](/blog/how-to-add-runtime-enforcement-without-breaking-your-agents) for the basic instrumentation playbook. This post is about what to read off those dry-run responses before you stop reading and start blocking. +Cycles' shadow mode is `dry_run: true` on a reservation request: the server runs the scope-derivation, budget-check, and caps-computation logic, returns the decision (`ALLOW`, `ALLOW_WITH_CAPS`, or `DENY`) along with affected scopes and optional balance snapshots, and leaves budget state untouched. No reservation is persisted and no balance is modified. The current server emits `reservation.denied` for denied dry-run evaluations, but not a complete allowed-decision/outcome stream, so the application must log every response and actual outcome. The v0.1.26 governance extension specifies a separate tenant-level `observe_mode` and observed-event types, but current v0.1.25.x reference servers only accept the preview fields for compatibility; they do not apply observe mode or emit those preview events. See [How to Add Runtime Enforcement Without Breaking Your Agents](/blog/how-to-add-runtime-enforcement-without-breaking-your-agents). ## The four signal categories @@ -58,13 +58,13 @@ Each category is a veto. If any of them is red, cutover is premature regardless This is where most teams focus first, and where dry-run data is most directly useful. -**False-positive denial rate.** Not every `DENY` in shadow mode is a denial you actually want in production. Some fraction represent estimate errors, misconfigured budgets, or legitimate overages the team chose to tolerate. A reasonable target is the 3–8% band on the fraction of would-be denials that represent work you'd want to let through. Higher than that, and your first day of enforcement produces a tide of pages. The healthiest teams classify a sample of shadow denials manually for at least a few days before cutover — it's the only way to separate "the policy caught a real problem" from "the estimate was too tight." +**False-positive denial rate.** Not every `DENY` in shadow mode is a denial you actually want in production. Some represent estimate errors, misconfigured budgets, or overages the team chose to tolerate. Classify a representative sample, set the acceptable rate from the workflow's user-impact SLO, and do not cut over while unintended denials breach that objective. A note on terminology: *false-positive denial rate* is the percentage of shadow denials that were unintended. *Sustained denial rate*, referenced in the rollback table later, is the absolute frequency of denials after cutover. The two signals are distinct; don't compare them directly. -**Reserve-to-commit ratio.** When reservations commit with the actual usage they reserved, the ratio hovers near 1.0. The band that's safe to enforce on is roughly 0.8–1.2 — held steady for at least a week, not just a single two-day sample. A ratio trending downward (you're over-reserving) means enforcement will reject legitimate work because your estimates are inflated. A ratio trending upward (you're under-reserving) means enforcement will under-protect. See [Estimate Drift: The Silent Killer of Budget Enforcement](/blog/estimate-drift-silent-killer-of-enforcement) for the operator diagnostic on this ratio. +**Reserve-to-commit ratio.** Define this as total submitted estimates divided by total actual usage for completed reservations. A ratio trending upward means estimates are growing relative to actuals; a ratio trending downward means actuals are growing relative to estimates. Cycles defines no universal safe band or duration, so choose a workload-specific tolerance and validate the distribution as well as the aggregate. See [Estimate Drift: The Silent Killer of Budget Enforcement](/blog/estimate-drift-silent-killer-of-enforcement). -**Commitment overage rate.** The fraction of commits that exceeded their reservations. Under 1% is healthy. 1–5% is an amber signal — tune estimates, don't cut over yet. Over 5% and the estimates themselves are wrong, not the policy. +**Commitment overage rate.** Track the fraction of commits whose actual usage exceeded the estimate. Compare it with the calibrated workload baseline and investigate changes by model, workflow, and tenant; the acceptable rate depends on variance and the configured commit-overage policy. **Budget utilization distribution.** If your would-be denial rate is an average across tenants, the average is lying to you. Look at the distribution. One tenant at 95% utilization with the rest at 30% means enforcement will hit that one tenant hard and leave the others untouched — which might be fine, or might be a signal that the budget for that tenant was never right. Outlier tenants should be deliberately scoped in or out of the first cutover, not averaged into the decision. @@ -72,11 +72,11 @@ A note on terminology: *false-positive denial rate* is the percentage of shadow A budget policy that only sees 60% of the real work produces misleading dry-run data. -**Instrumentation coverage.** The ratio of code paths that call `reserve()` to code paths that call an LLM or a tool. If 30% of your agent calls bypass Cycles because they're in a legacy code path or a background job, the 4% denial rate on the instrumented path tells you approximately nothing about what enforcement will do to the whole system. Target: at least 90% of model calls and 80% of tool calls instrumented before cutover. +**Instrumentation coverage.** The ratio of code paths that call `reserve()` to code paths that call an LLM or a tool. If 30% of your agent calls bypass Cycles because they're in a legacy code path or a background job, the denial rate on the instrumented path says little about what enforcement will do to the whole system. Define a coverage threshold from your threat model and inventory; a high-risk deployment may require every protected path to be instrumented before cutover. **Scope derivation consistency.** The same logical operation should resolve to the same scope path every time. If a run from agent A sometimes reports `tenant:X/workflow:Y/agent:A` and sometimes reports just `tenant:X`, enforcement against the narrower scope will behave inconsistently. Shadow data is the audit surface for this — run a daily diff over scope paths for a known-fixed workflow. -**Policy freshness.** Does every tenant and workflow have a budget policy that was authored this quarter, or are you still running day-one defaults for half your scopes? Outdated policies are more dangerous under enforcement than under shadow, because shadow just logs them and enforcement blocks on them. +**Policy freshness.** Verify that every ledger and scope included in the cutover still reflects the intended exposure, current workflow behavior, and current pricing. Age alone does not make a policy wrong, but an unreviewed default should not silently become a hard production boundary. ## Operational readiness signals @@ -86,19 +86,19 @@ Signal category most often underweighted. When the first legitimate denial fires **Degradation paths.** For every high-traffic workflow, has the team decided what happens when a reservation is denied? The options are well-understood — model downgrade, capability narrowing, queueing, checkpoint-and-resume, inform-and-stop — and the choice depends on the workflow. See [When Budget Runs Out: Graceful Degradation Patterns](/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents) for the decision matrix. A workflow without a degradation path should not be part of the first cutover. -**Runbook familiarity.** Whoever is on call needs to recognize a `BUDGET_EXCEEDED` error, a `BUDGET_FROZEN` error, and an `OVERDRAFT_LIMIT_EXCEEDED` error, and know which of the three requires a budget top-up versus a policy review versus paging the tenant. See [Operating Budget Enforcement in Production](/blog/operating-budget-enforcement-in-production) for the reason-code-to-response mapping. +**Runbook familiarity.** Whoever is on call needs to recognize `BUDGET_EXCEEDED`, `BUDGET_FROZEN`, and `OVERDRAFT_LIMIT_EXCEEDED`, inspect the denying scope, and distinguish a legitimate limit from bad estimates, lifecycle state, or concurrent debt. See [Operating Budget Enforcement in Production](/blog/operating-budget-enforcement-in-production). ## Reversion readiness The last category is the one that's often skipped because it feels defeatist. It isn't. It's the category that lets you cut over *aggressively* on the signals above, because you have a clean exit if reality disagrees with the data. -**Kill-switch design.** A feature flag, a config toggle, or a small code path that flips every call back to `dry_run: true` without a deploy. On self-hosted Cycles, this is usually a process environment variable or an admin-API budget setting toggled per scope. Either way, the engineer on call shouldn't have to push code to roll back. +**Kill-switch design.** Put `dry_run` behind an application or integration feature flag that can be changed without a deploy. `dry_run` is a request property, not an admin budget setting. The current v0.1.25.x server accepts tenant `observe_mode` configuration but does not apply it to runtime decisions, so do not rely on that field as a cutover switch. -The effect you're after is a scope-level freeze via the admin API: once a budget is frozen, subsequent reservations against that scope return `BUDGET_FROZEN` until the scope is unfrozen, and in-flight commits are not affected. The exact admin route shape varies by deployment — check your admin API surface for the freeze/unfreeze endpoints it exposes; the semantics matter more than the literal path. +An admin budget freeze is a separate emergency stop, not a rollback to shadow mode. Once a budget is frozen, subsequent live reservations and direct events against that scope fail with `BUDGET_FROZEN` until it is unfrozen; existing reservations can still commit or release. Use the documented admin freeze/unfreeze route for that behavior. -The hard freeze isn't always the right first move. The softer version — flipping the scope's policy back to `dry_run: true` without losing the data path — is usually preferable, because it leaves the shadow signal intact while stopping the blocking behavior. +The softer rollback is to make the integration send dry-run evaluations and continue execution while recording the decision in application telemetry. That keeps the evaluation signal without mutating the Cycles ledger. -**Rollback plan written down.** Two steps minimum: (1) flip the kill switch to restore shadow mode; (2) triage the signals that prompted the rollback before attempting re-enforcement. Teams that write this down in advance spend minutes on rollback, not hours. +**Rollback plan written down.** At minimum: (1) use the application feature flag to restore dry-run/bypass behavior; (2) triage the signals that prompted the rollback before attempting enforcement again. Test that path against the rollback-time objective for the workflow. **Canary scopes.** A small subset of tenants or workflows you're willing to cut over first and watch closely. If the signals on the canary set don't match the shadow data, the decision-tree's veto fires *before* you expand enforcement. @@ -107,7 +107,7 @@ The hard freeze isn't always the right first move. The softer version — flippi Cutover isn't a single on/off switch across the whole stack. When the four signal categories are green, cut over in an order that minimizes blast radius: 1. **Low-traffic, high-cost workflows first.** An overnight batch job or a rarely-used research agent. Enforcement errors here are loud and easy to diagnose. -2. **High-estimate-quality paths next.** The workflows where your reserve-to-commit ratio was tightest in shadow. These are the paths where enforcement does exactly what the data predicted. +2. **High-estimate-quality paths next.** Prefer workflows whose estimate distributions stayed within their chosen tolerance during calibration. 3. **High-risk tenants last.** The one tenant with 95% utilization isn't where you want to debug the first week of enforcement. Bring them into hard enforcement after the other paths are running clean. This is the same shape as a canary deploy. You're looking for disagreements between your pre-cutover model of the system and the post-cutover reality, and you want those disagreements to surface in the lowest-blast-radius environment first. @@ -116,25 +116,25 @@ This is the same shape as a canary deploy. You're looking for disagreements betw Signals that enforcement is misbehaving post-cutover — and therefore reasons to flip the kill switch back to shadow: -| Signal | Rollback threshold (rough guide) | +| Signal | Rollback criterion | |---|---| -| Denial rate | Sustained 3× shadow baseline for >10 minutes | -| Business-critical workflow error rate | Any noticeable spike in a monitored production flow | +| Denial rate | Breaches the workflow's calibrated user-impact or availability SLO | +| Business-critical workflow error rate | Reaches the incident threshold declared in the rollout plan | | `BUDGET_FROZEN` responses | Any appearance on a scope you didn't explicitly freeze | -| Commit-overage rate on a single scope | Sustained >2% — usually means a model change invalidated the reserve-to-commit estimate for that scope | -| Escalation volume from tenants | Any concentrated cluster, especially within the first hour | +| Commit-overage rate on a single scope | Leaves the workload-specific baseline enough to threaten availability or exposure objectives | +| Escalation volume from tenants | Breaches the customer-support or incident SLO | A rollback isn't a failure — it's the plan working. The follow-up is: what category of signal turned out to be under-calibrated, and what needs to change in the shadow data before the next cutover attempt? ## The scorecard -Put the four categories together as a single cutover readiness check. If every row is green, cut over. If any row is amber, fix that category first. If any row is red, cutover is premature regardless of how the others look. +Put the four categories together as a single cutover readiness check. Proceed only when every required category is ready. -| Category | Green | Amber | Red | +| Category | Ready | Needs work | Blocks cutover | |---|---|---|---| -| **Cost calibration** | False-positive denials <5%, R/C ratio 0.8–1.2 steady ≥1 week, overage <1% | Overage 1–5%, ratio drifting | Overage >5%, ratio outside 0.8–1.2 | -| **Policy coverage** | ≥90% model calls, ≥80% tool calls instrumented; scope derivation stable | 70–90% coverage; occasional scope inconsistency | <70% coverage or day-one policies still in place | -| **Operational readiness** | Alerts calibrated to shadow baseline; degradation paths defined for high-traffic workflows; runbook familiar | Alerts on templates; some workflows without degradation path | No one on call has responded to a dry-run alert | +| **Cost calibration** | False denials, estimate ratio, and overage rate meet workload-specific objectives across representative traffic | A metric is still moving or edge cases are under-sampled | A known legitimate path fails or a known runaway path passes | +| **Policy coverage** | Every protected path in the cutover inventory is instrumented and scope derivation is stable | Some lower-impact paths are explicitly deferred | A required protected path bypasses the boundary or maps inconsistently | +| **Operational readiness** | Alerts use application-side dry-run baselines; degradation paths and runbooks are tested | A noncritical workflow lacks a tested fallback | Operators cannot recognize, mitigate, or roll back an enforcement failure | | **Reversion readiness** | Kill-switch tested; rollback plan written; canary scopes selected | Kill-switch designed but untested | No rollback mechanism | ## The takeaway diff --git a/blog/state-of-ai-agent-governance-2026.md b/blog/state-of-ai-agent-governance-2026.md index 2e9260d9..577d94c6 100644 --- a/blog/state-of-ai-agent-governance-2026.md +++ b/blog/state-of-ai-agent-governance-2026.md @@ -40,10 +40,10 @@ The full 2026 incident catalog is documented in detail in [The State of AI Agent | Category | Example | What's Missing | |---|---|---| -| **Cost explosions** | $847K POC-to-production runaway; $47K enrichment loop (2.3M calls); $4,200 coding agent loop | Pre-execution budget enforcement | -| **Action failures** | [Replit production DB deletion](https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/) ($2 token cost); 200 wrong customer emails ($1.40 token cost, $50K+ damage); [OpenAI Operator unauthorized $31 purchase](https://incidentdatabase.ai/cite/1028/) | Action-level authority (RISK_POINTS) | -| **Security incidents** | 84.2% MCP tool poisoning success rate; [postmark-mcp supply chain attack](https://cyberpress.org/malicious-mcp-server/) (~300 orgs); 1,862 exposed MCP servers without auth | Tool governance + scoped identity | -| **Multi-agent cascades** | [MAST failure rates of 41-86.7%](https://arxiv.org/abs/2503.13657) across 7 frameworks; DeepMind 17x error amplification | [Authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) at delegation boundaries | +| **Cost explosions** | Self-published $847K/month and $47K anecdotes; checked $52.80 constructed loop model | Mandatory pre-execution budget enforcement on covered paths | +| **Action failures** | [Replit production DB deletion](https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/); constructed 200-email scenario with low token spend; [OpenAI Operator unauthorized $31 purchase](https://incidentdatabase.ai/cite/1028/) | Application authorization plus optional caller-assigned exposure budgets | +| **Security evidence** | MCP-ITP induced selected target-tool calls in up to 84.2% of evaluated MCPTox prompts; [postmark-mcp supply-chain attack](https://cyberpress.org/malicious-mcp-server/) (~300 orgs); 1,862 exposed MCP servers without auth | Tool governance + scoped identity | +| **Multi-agent cascades** | [MAST configuration-level failure rates of 41–86.7%](https://arxiv.org/abs/2503.13657) across seven selected frameworks; independent-agent error amplification up to 17.2x in Google's controlled benchmark | Coordination and validation; scoped budgets can separately bound resource exposure | The common thread is that each category has controls that could have reduced likelihood or blast radius. The mapping is not one-to-one, and no single runtime control can prove that a historical incident would have been prevented. @@ -151,19 +151,19 @@ The alternative — observability, dashboards, post-hoc alerts — catches failu The atomic enforcement primitive. The agent reserves capacity before acting, executes if approved, commits actual usage after. Atomic across concurrent operations. This is how payment processors, capacity planners, and database transactions have handled resource accounting for decades. -The reserve-commit pattern solves three failure modes agents run into constantly: TOCTOU races (two concurrent agents reading the same balance and both proceeding), retry storms (the same logical operation charged multiple times), and crashed clients leaving orphaned state. Every write needs an idempotency key, every reservation has a TTL, every commit reconciles estimate vs. actual. Agents need the same discipline payment systems have had for decades — the math is the same, the stakes are comparable, the pattern is proven. +The reserve-commit pattern addresses three failure modes agents run into constantly: TOCTOU races (two concurrent agents reading the same balance and both proceeding), duplicate accounting when retries reuse a stable idempotency key, and crashed clients leaving holds that eventually expire. Every write needs an idempotency key, every reservation has a TTL, and every successful commit reconciles estimate versus actual. Agents benefit from the same lifecycle discipline used in payment and capacity systems, while downstream side effects still need their own idempotency controls. ### 3. RISK_POINTS: Action Control Beyond Cost -Dollar budgets don't capture [the risk of an action](/blog/ai-agent-action-control-hard-limits-side-effects). Sending 200 emails costs $1.40 in tokens but can do $50K in damage. Running a database `DELETE` costs $0.02 in compute but can destroy 1,200 customer records. +Dollar budgets don't capture [the risk of an action](/blog/ai-agent-action-control-hard-limits-side-effects). In constructed examples, sending 200 emails can have low token cost but substantial external impact, and a cheap database `DELETE` can destroy many records. The business-impact figures depend on the actual system and are not inferred from token spend. `RISK_POINTS` is a budget unit an application can assign by blast radius — for example, one point for a read, 20 for a mutation, or 50 for a deploy. A mandatory handler can reserve those points to distinguish cheap harmful actions from expensive harmless ones. The current server does not infer tiers, count action kinds automatically, or maintain the action registry described by the not-yet-implemented governance extension. ### 4. Authority Attenuation for Delegation -In [multi-agent systems](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation), a useful application policy is to narrow authority with delegation depth. A parent can carve out a sub-budget and the orchestrator can apply a restricted action mask. This can bound delegated resource exposure, but it does not by itself prevent downstream agents from propagating incorrect information. +In [multi-agent systems](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation), a useful application policy is to narrow authority with delegation depth. Before enabling a child, the orchestrator can restrict its tool inventory and require its calls to match a smaller, explicitly provisioned agent ledger under the shared workflow scope. Cycles does not transfer balances at handoff. -The principle is borrowed from capability-based security: a correctly implemented orchestrator propagates authority downward and allows each hop only to narrow what the child inherits. A parent agent with a $100 budget and permission to send emails could delegate a $30 budget with email disabled; the application must prevent it from delegating authority it does not hold. This limits the resource and permission blast radius covered by those controls, though it does not bound every failure mode or prevent bad information from propagating. Recent research on [scaling agent systems](https://arxiv.org/abs/2512.08296) quantifies the stakes: in the study's settings, independent agents amplified errors 17.2x while centralized coordination limited amplification to 4.4x. +The principle is borrowed from capability-based security: a correctly implemented orchestrator should only narrow the child's capabilities. A $100 parent workflow might use a $30 child agent ledger while the host disables email for that child. Budget and permission controls remain separate, and neither prevents bad information from propagating. Recent research on [scaling agent systems](https://arxiv.org/abs/2512.08296) quantifies the stakes: in the study's settings, independent agents amplified errors up to 17.2x while centralized coordination reached up to 4.4x. ### 5. Three-Way Decision Model @@ -234,7 +234,7 @@ Five things are visible on the 2026 horizon: The state of AI agent governance in 2026 is a race condition. Agents are already in production. Regulations are catching up. The primitives for governing them exist, but adoption is uneven and framework support is partial. -The organizations that do this well in 2026-2027 will share a common pattern: **pre-execution enforcement as the foundational layer, hierarchical scopes from tenant to run, action-level risk controls beyond cost, and authority attenuation for delegation chains**. They'll be able to show auditors the logs, show executives the saved incidents, and show developers the graceful degradation that kept the agent useful when budgets got tight. +The organizations that do this well in 2026-2027 will share a common pattern: **pre-execution enforcement as the foundational layer, hierarchical scopes from tenant through toolset, application mappings for run-level limits, action-level risk controls beyond cost, and authority attenuation in the host's delegation design**. They'll be able to show auditors the logs, show executives the saved incidents, and show developers the graceful degradation that kept the agent useful when budgets got tight. The organizations that don't will show up in next year's incident catalog. diff --git a/blog/state-of-ai-agent-incidents-2026.md b/blog/state-of-ai-agent-incidents-2026.md index 8f73b3aa..eeead609 100644 --- a/blog/state-of-ai-agent-incidents-2026.md +++ b/blog/state-of-ai-agent-incidents-2026.md @@ -24,9 +24,9 @@ This report separates **documented incidents** sourced to public reporting or re ## Key findings - **20+ documented incidents and recurring patterns** across cost, action, security, and multi-agent categories -- **Costs in this report range from $1.40 to $12,400 per incident** in direct model spend (documented incidents and constructed scenarios), with business impact reaching $50,000+ from a single $1.40 agent run -- **Some of the most damaging incidents cost very little in [tokens](/glossary#tokens).** A $1.40 model run caused [$50K+ in pipeline damage](/blog/ai-agent-action-control-hard-limits-side-effects). A $0.80 run triggered an [unauthorized purchase](https://www.washingtonpost.com/technology/2025/02/07/openai-operator-ai-agent-chatgpt/). A $2.00 run [deleted a production database](https://techcrunch.com/2025/10/02/after-nine-years-of-grinding-replit-finally-found-its-market-can-it-keep-it/). Dollar budgets alone cannot prevent the worst failures. -- **Up to 84.2% attack success rate** for tool poisoning in benchmark settings under auto-approval ([MCP-ITP](https://arxiv.org/abs/2601.07395)) +- **Constructed cost scenarios range from $32.40 to $2,300** under their stated token assumptions; self-published anecdotal reports below describe larger figures but are not independently verified here. +- **Some damaging documented incidents can have little model spend relative to business impact.** A reported $0.80 run triggered an [unauthorized purchase](https://www.washingtonpost.com/technology/2025/02/07/openai-operator-ai-agent-chatgpt/). Replit's coding-agent database incident shows why dollar budgets alone cannot prevent action failures, although the public reporting does not establish a precise model-cost figure for that event. +- **Up to 84.2% selected target-tool-call rate** across evaluated MCPTox prompts and 12 model/agent settings ([MCP-ITP](https://arxiv.org/abs/2601.07395)); the benchmark did not test live production execution - **41–87% failure rates** in multi-agent coordination ([UC Berkeley MAST study](https://arxiv.org/abs/2503.13657)) - **64% of surveyed organizations** experienced more than $1M in losses from AI-related risks broadly ([2025 EY survey](https://www.ey.com/en_uk/insights/ai/how-can-responsible-ai-bridge-the-gap-between-investment-and-impact)) @@ -48,46 +48,46 @@ Incidents are categorized as: Agents that spend more than expected — through loops, retries, [fan-out](/glossary#fan-out), or scope creep. These are constructed scenarios based on recurring failure modes; see Categories B and C for externally documented incidents from named companies and security researchers. -### A1. Coding agent retry loop — $4,200 (constructed) +### A1. Coding agent retry loop — $52.80 (constructed) -A coding agent hit an ambiguous error, retried with expanding context windows, and [looped 240 times over three hours](/blog/ai-agent-failures-budget-controls-prevent). Total cost: $4,200. Three dashboards showed the spend in real time. None could stop it. +A coding agent hit an ambiguous error and [looped 240 times over three hours](/blog/ai-agent-failures-budget-controls-prevent). At the source scenario's stated flat average of 12,000 input and 2,500 output tokens per call, total cost is $52.80. | | Detail | |---|---| -| Model cost | $4,200 | -| Business impact | Budget exhausted, all agents blocked by provider cap | -| Root cause | Provider cap is monthly/org-wide — doesn't enforce per-run | +| Model cost | $52.80 | +| Business impact | Unattended spend and delayed failure detection | +| Root cause | No mandatory per-run boundary; provider controls use a broader vendor scope | | Mitigation | **Budget gate** — a mandatory $15 per-run reservation boundary rejects later iterations once configured exposure is exhausted | -### A2. Weekend backlog processing — $12,400 (constructed) +### A2. Weekend backlog processing — $2,300 (constructed) A coding agent [deployed Friday afternoon processed a 2,300-item backlog over the weekend](/blog/ai-agent-failures-budget-controls-prevent) without budget enforcement. Context windows grew per item, retries compounded, and nobody checked until Monday. | | Detail | |---|---| -| Model cost | $12,400 | +| Model cost | $2,300 | | Business impact | Weekend budget consumed, Monday recovery | | Root cause | No per-batch or per-task budget limit | -| Mitigation | **Budget gate** — a mandatory per-task cap of $5 bounds reserved exposure to roughly $2,500 | +| Mitigation | **Budget gate** — a mandatory $500 batch budget bounds the unattended run; per-task budgets can contain outliers | -### A3. Concurrent agent burst — 6.4x overrun (constructed) +### A3. Concurrent agent burst — 6.5x overrun (constructed) -Twenty concurrent agents [processing 200 documents simultaneously](/blog/ai-agent-failures-budget-controls-prevent) hit a TOCTOU race condition. All read "budget remaining: $500" and all proceeded. Actual spend: $3,200. +Twenty concurrent agents [processing 200 documents simultaneously](/blog/ai-agent-failures-budget-controls-prevent) hit a TOCTOU race condition. All read "budget remaining: $5" before delayed counter updates converged. Modeled spend: $32.40. | | Detail | |---|---| -| Model cost | $3,200 (budget was $500) | -| Business impact | 6.4x budget overrun | +| Model cost | $32.40 (remaining budget was $5) | +| Business impact | About 6.5x the remaining budget | | Root cause | Application-level counter lacks atomicity | | Mitigation | **Atomic [reservation](/glossary#reservation)** — budget locked before execution, concurrent requests see accurate remaining | -### A4. Retry storm during CRM outage — $1,800 (constructed) +### A4. Retry storm during CRM outage — $33.86 (constructed) -A CRM returns 500 errors for 12 minutes. [Retry logic at tool, step, and orchestration layers compound](/blog/ai-agent-failures-budget-controls-prevent) — 27x multiplication across 45 active conversations. Cost: $1,800 in 12 minutes. +A CRM returns 500 errors for 12 minutes. [Retry logic at tool, step, and orchestration layers compounds](/blog/ai-agent-failures-budget-controls-prevent) — up to 27 calls for each of 38 affected conversations. At the stated token volumes, cost is $33.86. | | Detail | |---|---| -| Model cost | $1,800 | +| Model cost | $33.86 | | Business impact | All [tenant](/glossary#tenant) budgets affected during the storm | | Root cause | Retry multiplier at each layer; no cumulative check | | Mitigation | **Budget gate** — mandatory per-conversation reservations bound configured exposure | @@ -96,7 +96,7 @@ A CRM returns 500 errors for 12 minutes. [Retry logic at tool, step, and orchest Two widely cited cost incidents come from self-published sources and should be treated as pattern-confirming rather than independently verified: - **POC-to-production scaling — $847K/month.** A proof-of-concept agent costing $500/month [scaled to $847,000/month](https://medium.com/@klaushofenbitzer/token-cost-trap-why-your-ai-agents-roi-breaks-at-scale-and-how-to-fix-it-4e4a9f6f5b9a) in production due to call volume assumptions that didn't account for context window growth, retries, and fan-out. (Source: Medium, Klaus Hofenbitzer) -- **Data enrichment API loop — $47,000.** A data enrichment agent [misinterpreted an API error and ran 2.3 million calls over a weekend](https://rocketedge.com/2026/03/15/your-ai-agent-bill-is-30x-higher-than-it-needs-to-be-the-6-tier-fix/). The API returned 200 OK with an error body; the agent treated it as success and retried the entire batch. (Source: RocketEdge) +- **Data enrichment API loop — $47,000.** A data enrichment agent [misinterpreted an API error and ran 2.3 million calls over a weekend](https://rocketedge.com/2026/03/15/ai-agent-cost-control/). The API returned 200 OK with an error body; the agent treated it as success and retried the entire batch. (Source: RocketEdge) Both illustrate the same failure mode as A1–A4: no cumulative spend enforcement. ::: @@ -105,14 +105,14 @@ Both illustrate the same failure mode as A1–A4: no cumulative spend enforcemen Agents that take wrong, excessive, or unauthorized actions — where the damage is in the consequence, not the tokens. -### B1. 200 wrong emails — $1.40 in tokens, $50K+ in damage (constructed) +### B1. 200 wrong emails — low token spend, high potential impact (constructed) -A support agent [sent 200 collections emails instead of welcome emails](/blog/ai-agent-action-control-hard-limits-side-effects). A prompt regression changed the template selection. Total model spend: $1.40. Business impact: 34 support tickets, 12 social media complaints, $50K+ in lost pipeline. +In this [illustrative scenario](/blog/ai-agent-action-control-hard-limits-side-effects), a support agent sends 200 collections emails instead of welcome emails after a prompt regression changes template selection. If the model calls cost about $1.40, the customer and business impact can still be much larger; the original scenario has no sourced ticket, complaint, or pipeline-loss measurements. | | Detail | |---|---| | Model cost | $1.40 | -| Business impact | $50,000+ in lost pipeline | +| Business impact | Illustrative and unquantified; potentially much larger than token spend | | Root cause | No action-level enforcement — dollar budget was nowhere near exhausted | | Mitigation | **Handler authorization + risk budget** — an application can require approval and reserve caller-assigned [RISK_POINTS](/concepts/action-authority-controlling-what-agents-do) before each email | @@ -214,22 +214,22 @@ Researchers [found 341 malicious ClawHub skills](https://thehackernews.com/2026/ ### C4. Exposed MCP servers — zero authentication -Trend Micro [found 492 internet-exposed MCP servers](https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data) with no client authentication or traffic encryption. Separately, Knostic [reported 1,862 exposed MCP servers](https://www.knostic.ai/blog/mapping-mcp-servers-study), sampled 119, and found all 119 exposed internal tool listings without authentication. +Trend Micro [found 492 internet-exposed MCP servers](https://www.trendaisecurity.com/en-us/resources-insights/research/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data) with no client authentication or traffic encryption. Separately, Knostic [reported 1,862 exposed MCP servers](https://www.knostic.ai/blog/mapping-mcp-servers-study), sampled 119, and found all 119 exposed internal tool listings without authentication. | | Detail | |---|---| -| Scale | 492 exposed ([Trend Micro](https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data)) + 1,862 exposed ([Knostic](https://www.knostic.ai/blog/mapping-mcp-servers-study)) | -| Source | [Trend Micro](https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data), [Knostic](https://www.knostic.ai/blog/mapping-mcp-servers-study), 2026 | +| Scale | 492 exposed ([Trend Micro](https://www.trendaisecurity.com/en-us/resources-insights/research/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data)) + 1,862 exposed ([Knostic](https://www.knostic.ai/blog/mapping-mcp-servers-study)) | +| Source | [Trend Micro](https://www.trendaisecurity.com/en-us/resources-insights/research/mcp-security-network-exposed-servers-are-backdoors-to-your-private-data), [Knostic](https://www.knostic.ai/blog/mapping-mcp-servers-study), 2026 | | Root cause | Deployments exposed MCP servers without authentication, authorization, or transport protection | | Mitigation | **MCP authorization where supported, application authentication and authorization, TLS, Origin validation, and network restriction**; budgets are defense in depth, not a substitute | -### C5. Tool poisoning — 84% success rate +### C5. Tool poisoning — 84.2% target-tool-call rate -The [MCP-ITP benchmark](https://arxiv.org/abs/2601.07395) achieved up to 84.2% attack success rate (ASR) in benchmark settings under auto-approval. Attacks include rug pulls (tool changes behavior post-install), schema poisoning (hidden instructions in descriptions), and tool shadowing (malicious tool overrides legitimate one). +On MCPTox, the [MCP-ITP benchmark](https://arxiv.org/abs/2601.07395) induced the selected legitimate target-tool call in up to 84.2% of evaluated prompts across 12 model/agent settings. It measured tool selection, not production auto-approval prevalence or confirmed live execution. Related MCP threats include rug pulls, schema poisoning, and tool shadowing, but those categories should not all be attributed to this one benchmark. | | Detail | |---|---| -| Success rate | 84.2% with auto-approval | +| Measured result | Up to 84.2% selected target-tool calls on evaluated MCPTox prompts | | Source | MCP-ITP framework (Ruiqi Li et al., 2026) | | Root cause | Agent trusts tool descriptions and auto-approves calls | | Mitigation | **Tool scanning and pinning, restricted tool inventory, approval or external authorization, argument validation, sandboxing, and egress controls** | @@ -258,7 +258,7 @@ A vulnerability in GitHub Copilot [enabled prompt injection to execute arbitrary ### C8. Rogue agent collaboration -Researchers [demonstrated](https://www.theregister.com/2026/03/12/rogue_ai_agents_worked_together/) that compromised agents in multi-agent architectures can coordinate to escalate privileges and compromise downstream systems. +Researchers [demonstrated](https://www.theregister.com/security/2026/03/12/rogue-ai-agents-can-work-together-to-hack-systems/5228926) that compromised agents in multi-agent architectures can coordinate to escalate privileges and compromise downstream systems. | | Detail | |---|---| @@ -273,22 +273,22 @@ Failures that emerge from agent interactions, coordination, and systemic propert ### D1. UC Berkeley MAST — 41–87% failure rates -UC Berkeley's [MAST study](https://arxiv.org/abs/2503.13657) analyzed 1,600+ execution traces across 7 multi-agent frameworks and found 14 distinct failure modes with 41–87% failure rates. Failure categories: system design issues (44.2%), inter-agent misalignment (32.3%), task verification failures (23.5%). +UC Berkeley's [MAST study](https://arxiv.org/abs/2503.13657) analyzed 1,600+ execution traces across seven selected multi-agent frameworks and identified 14 failure modes. Configuration-level failure rates in the evaluated model/task settings ranged from 41% to 86.7%; they are not a universal production failure rate. | | Detail | |---|---| -| Failure rate | 41–87% across frameworks | +| Failure rate | 41–86.7% across evaluated framework/model/task configurations | | Source | [UC Berkeley MAST](https://arxiv.org/abs/2503.13657), NeurIPS 2025 Spotlight | | Root cause | System design, inter-agent misalignment, and task-verification failures | | Mitigation | **Architecture-specific coordination, validation, and evaluation**; hierarchical budgets can separately bound resource exposure | -### D2. Google DeepMind — 17x error amplification +### D2. Google Research — architecture-dependent error amplification -[Google DeepMind research](https://research.google/blog/towards-a-science-of-scaling-agent-systems-when-and-why-agent-systems-work/) found that multi-agent networks amplify errors by 17x. A 95% per-agent reliability rate yields only 36% overall reliability in a 20-step chain. +In a controlled evaluation of 180 configurations, [Google Research](https://research.google/blog/towards-a-science-of-scaling-agent-systems-when-and-why-agent-systems-work/) found that independent agents amplified errors by up to 17.2x, while centralized coordination limited amplification to 4.4x. Separately, a simple independent-step model with 95% reliability per step yields about 36% probability that all 20 steps succeed; that calculation is illustrative and was not the paper's measured result. | | Detail | |---|---| -| Amplification | 17x error multiplication | +| Amplification | Up to 17.2x independent; 4.4x centralized in the evaluated settings | | Source | Google Research, January 2026 | | Root cause | Errors propagate and compound across agent boundaries | | Mitigation | **Validation and centralized coordination where appropriate**; per-agent budgets can keep an error cascade from also exhausting shared spend | diff --git a/blog/true-cost-of-uncontrolled-agents.md b/blog/true-cost-of-uncontrolled-agents.md index ce4ae928..b84f9951 100644 --- a/blog/true-cost-of-uncontrolled-agents.md +++ b/blog/true-cost-of-uncontrolled-agents.md @@ -16,7 +16,7 @@ head: > **Part of: [LLM Cost Runtime Control Reference](/guides/llm-cost-runtime-control)** — the full pillar covering causes, enforcement patterns, multi-tenant boundaries, and unit economics. -A development team ships a coding agent on Friday afternoon. It works beautifully in staging — summarizing PRs, generating tests, refactoring modules. By Monday morning, the agent has made 14,000 API calls, consumed 380 million [tokens](/glossary#tokens), and run up a $12,400 bill against a model provider. No one noticed because the dashboard updates hourly and the alerts were configured for _daily_ spend thresholds. The agent wasn't malicious. It wasn't buggy in the traditional sense. It simply did what agents do: it kept working. +Consider an illustrative development team that ships a coding agent on Friday afternoon to summarize PRs, generate tests, and refactor modules. By Monday morning, the modeled workload has made 14,000 API calls and consumed 380 million [tokens](/glossary#tokens). At an assumed blended rate of about $32.63 per million tokens, that is a $12,400 bill. The figure is a scenario input, not a reported customer invoice. > **Open the uncontrolled-agent scenario in the calculator:** [Open with these numbers pre-loaded →](/calculators/claude-vs-gpt-cost-standalone#s=eyJ3b3JrbG9hZE5hbWUiOiJVbmNvbnRyb2xsZWQgcHJvZHVjdGlvbiBhZ2VudCIsIndvcmtsb2FkRGVzY3JpcHRpb24iOiJBZ2VudCBpbiBwcm9kdWN0aW9uIHdpdGggbm8gcGVyLWNhbGwsIHBlci10ZW5hbnQsIG9yIHBlci1ydW4gYnVkZ2V0LiBDb3N0cyBzY2FsZSB3aXRoIHdoYXRldmVyIHRoZSBtb2RlbCBlbWl0cy4iLCJpbnB1dFRva2VucyI6ODAwMCwib3V0cHV0VG9rZW5zIjoxNTAwLCJjYWxsc1BlckRheSI6NTAwMH0) @@ -94,7 +94,7 @@ The fundamental gap looks like this: - **Dashboards** show spend _after_ it occurs (minutes to hours of delay) - **Rate limits** cap throughput but don't understand _cost_ — a rate limit of 100 RPM doesn't distinguish between a $0.01 call and a $5.00 call -- **Provider caps** are monthly or daily, far too coarse for per-run control +- **Provider controls** use vendor-defined scopes and semantics that may not map to an application run - **In-app counters** are single-process and collapse under concurrency We wrote extensively about this progression in [From Observability to Enforcement](/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority) and [Why Rate Limits Are Not Enough for Autonomous Systems](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems). @@ -119,7 +119,7 @@ The cost of building budget controls is small. The cost of not having them compo ## From cost visibility to cost control -Cost overruns are a symptom. The root cause is the absence of a pre-execution enforcement layer — a system that asks "is there budget for this?" before every action, not after. That's what [runtime authority](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) provides: deterministic budget decisions at the point of execution, not retroactive alerts on a dashboard. +The key risk is variance: retries, fan-out, and context growth can make two nominally identical runs cost very different amounts. A [runtime budget boundary](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) does not remove that variance, but it can cap submitted spend for each instrumented run before the next costly operation starts. ## Next steps diff --git a/blog/usage-based-pricing-for-ai-agents.md b/blog/usage-based-pricing-for-ai-agents.md index 29f7863e..dbfa824b 100644 --- a/blog/usage-based-pricing-for-ai-agents.md +++ b/blog/usage-based-pricing-for-ai-agents.md @@ -87,7 +87,7 @@ For most agent products, start with three budgets. | Budget | What it protects | Example | |---|---|---| | [Tenant](/glossary#tenant) budget | Customer-level allowance and isolation | Acme gets $500 of AI usage this month | -| Run budget | Single-task blast radius | One research run can consume at most $3 | +| Run-mapped workflow budget | Submitted estimate for one task | A mandatory path rejects the first estimate that exceeds the configured $3 headroom | | Toolset budget | Expensive or risky actions | Web search, code execution, or refunds get separate limits | Tenant budgets align with pricing. Run budgets protect margin inside a tenant. Toolset budgets stop a cheap-looking plan from accidentally permitting expensive side effects. diff --git a/blog/vibe-coding-budget-wrapper-vs-budget-authority.md b/blog/vibe-coding-budget-wrapper-vs-budget-authority.md index eb640fe2..970952c3 100644 --- a/blog/vibe-coding-budget-wrapper-vs-budget-authority.md +++ b/blog/vibe-coding-budget-wrapper-vs-budget-authority.md @@ -64,7 +64,7 @@ A lot of internal implementations are really **checkers**. They observe local st Cycles is built to be an **authority**. -That means the system deciding whether work is allowed is not just another helper library embedded in the runtime. It is the place where budget is **reserved, committed, and released** in a way that remains correct under concurrency, retries, and partial failure. The [reserve/commit lifecycle](/blog/ai-agent-budget-control-enforce-hard-spend-limits) is designed specifically for this — atomic [reservations](/glossary#reservation) that prevent concurrent actors from claiming the same budget. +That means the system deciding whether work is allowed is not just another helper library embedded in the runtime. It is the place where budget is **reserved, committed, and released** in a way that remains correct under concurrency, retries, and partial failure. The [reserve-commit lifecycle](/blog/ai-agent-budget-control-enforce-hard-spend-limits) is designed specifically for this — atomic [reservations](/glossary#reservation) that prevent concurrent actors from claiming the same budget. A checker can say "I think there is still budget available." @@ -165,7 +165,7 @@ If the answer is no, that is exactly why Cycles exists. ## Next steps - **[What is Cycles?](/quickstart/what-is-cycles)** — start here if you are new to Cycles -- **[End-to-End Tutorial](/quickstart/end-to-end-tutorial)** — see the reserve/commit lifecycle in action +- **[End-to-End Tutorial](/quickstart/end-to-end-tutorial)** — see the reserve-commit lifecycle in action - **[How Reserve/Commit Works](/protocol/how-reserve-commit-works-in-cycles)** — the protocol mechanics behind runtime authority ## Related how-to guides diff --git a/blog/voice-agent-budgets-when-you-cant-pause-to-reserve.md b/blog/voice-agent-budgets-when-you-cant-pause-to-reserve.md index a48c157c..69e09db8 100644 --- a/blog/voice-agent-budgets-when-you-cant-pause-to-reserve.md +++ b/blog/voice-agent-budgets-when-you-cant-pause-to-reserve.md @@ -11,7 +11,7 @@ tags: - agents - engineering - RISK_POINTS -description: "OpenAI Realtime, Vapi, Retell AI: voice agents can't wait 300ms for ALLOW. Patterns for budget authority when reserve-commit can't sync on the hot path." +description: "How to budget realtime voice agents with call-level reservations, local metering, turn-boundary checks, separately gated tools, and provider usage records." blog: true sidebar: false featured: false @@ -23,11 +23,11 @@ head: # Reserving Authority When You Can't Pause -A retail-support voice agent gets a call from a customer who is talking about returns, late shipping, and a damaged item. Twelve minutes in, the conversation hits an edge case in the prompt — the agent cannot find the right wrap-up template, so it produces a long, careful, summarizing response. Then another. Then another. The customer is patient; the agent is on-brand; the conversation continues. By minute 17 the session has spent $90 across the realtime model, premium TTS, repeated tool look-ups, the orchestrator, and the carrier minutes. The dollar cap on the customer's plan is $50. +A retail-support voice agent gets a call from a customer who is talking about returns, late shipping, and a damaged item. Twelve minutes in, the conversation hits an edge case in the prompt — the agent cannot find the right wrap-up template, so it produces a long, careful, summarizing response. Then another. Then another. The customer is patient; the agent is on-brand; the conversation continues beyond the spending envelope the application intended for one call. Nobody is at fault on the call. The customer's question was reasonable. The agent's response was reasonable. The model's output was, in some local sense, *good*. The failure is one layer down: nothing between the agent's intent to generate the next audio frame and the actual audio leaving the WebSocket asked whether the session was still allowed to pay for it. -This is the constraint that breaks the reserve-commit pattern the rest of the corpus assumes. The [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) — reserve, wait for an accepted result or budget error, then act — assumes the agent can pause for the decision. Voice and realtime agents often cannot do that per audio frame. The end-of-user-speech to start-of-AI-audio latency budget is roughly 700 ms for a conversation to feel natural, and current pipelines may already spend much of that on the model itself. +This is the constraint that prevents the reserve-commit pattern from running per audio frame. The [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) — reserve, wait for an accepted result or budget error, then act — assumes the application can pause for the decision. Voice and realtime agents usually cannot add a network decision before every frame without affecting the conversation. So the question is not *whether* voice agents need [runtime authority](/glossary#runtime-authority). They need it as much as tool-calling agents do, and in some ways more — the per-minute cost is high, the failure modes are unattended, and sessions can run for many minutes. The question is how to enforce it when the gate cannot sit synchronously in the path. @@ -35,7 +35,7 @@ So the question is not *whether* voice agents need [runtime authority](/glossary ## What's Different About the Voice Surface -Traditional tool-calling agents have natural pause points. A function call returns; the agent decides what to do next; before the next call goes out, a [runtime authority](/glossary#runtime-authority) gate can run. The 50–150 ms a Cycles-style gate adds is invisible to the user, because the user is not waiting on the agent's loop in real time. +Traditional tool-calling agents have natural pause points. A function call returns; the agent decides what to do next; before the next call goes out, a [runtime authority](/glossary#runtime-authority) gate can run. Whether that extra round trip is acceptable depends on the application's latency budget. Voice agents do not have those pauses. The architecture is roughly: @@ -44,20 +44,11 @@ Voice agents do not have those pauses. The architecture is roughly: 3. Audio out (TTS or end-to-end audio model → WebSocket → speaker) 4. Repeat — except (1) and (3) overlap continuously, because the user can interrupt at any moment -The whole loop runs at the cadence of speech. Production deployments on the [OpenAI Realtime API](https://openai.com/index/delivering-low-latency-voice-ai-at-scale/) typically land in a 300–500 ms response window from end-of-user-speech to start-of-agent-audio, with conversation-feel guidance from voice-AI platforms and turn-taking research converging around a ~700 ms ceiling before exchanges start feeling stilted. Inside that budget, the audio buffers are streaming continuously — there is no "before the next call" point where a sync gate can sit. +The whole loop runs at the cadence of speech. Realtime APIs stream audio and support interruption, so there is no discrete "before the next model call" point where a synchronous gate naturally sits. The exact latency budget varies by provider, transport, model, region, and application. -The cost shape is also different. A single conversation costs real money in a way an LLM-only agent does not: +The cost shape is also different. A single conversation may combine model input and output, speech services, orchestration, telephony, and tool calls. Those prices and billing units change frequently, and assembled platforms may include some components while charging others separately. Use each provider's current pricing and usage records when building the estimate; do not copy a static per-minute number into enforcement logic. -| Stack | Typical all-in cost | Notes | -|---|---|---| -| OpenAI Realtime API | ~$0.18–$0.46/min uncached, $0.05–$0.10/min with prompt caching | Per [recent pricing analysis](https://callsphere.ai/blog/vw2c-openai-realtime-cost-per-minute-math-2026) | -| ElevenLabs Agents | $0.08/min hosting flat (or $0.16/min burst), plus LLM and telephony at cost — typically $0.08–$0.24/min end-to-end depending on those add-ons | Hosting + premium TTS bundled | -| Retell AI | $0.07–$0.31/min depending on configuration | Single price for the assembled pipeline; lower end for a basic single-pipeline setup, higher with premium LLM and voice add-ons | -| Vapi | $0.05/min orchestration plus BYOK provider stack at cost; derived all-in typically estimated in the $0.115–$0.42/min range depending on choices | Customer wires their own ASR / LLM / TTS | - -A 17-minute conversation at premium settings lands somewhere between roughly $1.50 and $8.00 against the per-minute stack rates above, before any tool-call surcharges, carrier minutes, or compliance overhead. A runaway loop with repeated tool look-ups on every turn, premium TTS, and a premium model can multiply that several-fold. The opener's $90 figure assumes a session triggering an expensive lookup tool on every turn — well within what an unattended loop can produce in under twenty minutes. - -The conventional treatment in the corpus — [enforce a dollar cap, reserve before each model call](/blog/ai-agent-budget-control-enforce-hard-spend-limits) — does not directly fit. There is no discrete "model call" in a voice session. There is a continuous stream of audio frames, each of which has been billed by the time the next one ships. +The conventional treatment in the corpus — reserve estimated cost before each model call — does not directly fit. There may be no discrete model call in a voice session, and provider usage can accrue continuously while audio is streaming. ## Where the Action Surface Splits @@ -77,20 +68,22 @@ This is the same shape as the [tier model in action authority](/blog/ai-agent-ac ## Pattern 1: Predictive Reservation, True-Up Later -The simplest pattern. At call start, the agent reserves an upper-bound amount of authority for the call — enough to cover the expected duration plus headroom. Audio frames consume the [reservation](/glossary#reservation) as they ship, with no per-frame round-trip to the gate. At call end (or on graceful timeout), the agent commits the actual consumption. +The simplest application pattern is to reserve a conservative upper-bound estimate before the call starts, then meter provider usage locally while the call runs. Cycles holds the full [reservation](/glossary#reservation); audio frames do not decrement its amount individually. At call end, the application commits the actual amount and Cycles releases the unused portion. + +Cycles has no wall-clock-seconds unit. Convert projected telephony and session time to `USD_MICROCENTS` or to an application-defined `CREDITS` amount. Keep token accounting in a separate `TOKENS` budget when that distinction matters. | Step | When | Latency contribution | |---|---|---| -| Reserve N minutes of authority at call start | Before WebSocket open | One sync gate, latency-budget-free | -| Stream audio frames, decrement local counter | Throughout call | Zero — local check only | -| Local counter approaches reservation limit | At ~80% consumed | Re-reserve incrementally (sync, but the conversation has a natural turn boundary) | -| Commit actual consumption | At call end | Async, no user-visible latency | +| Reserve the estimated whole-call cost | Before the provider session starts | One synchronous Cycles request | +| Stream audio and update an application-local meter | Throughout the call | No per-frame Cycles request | +| Local estimate approaches its reserved amount | At an application-selected threshold | At a turn boundary, obtain a second reservation before allowing another bracket | +| Commit actual consumption | At call end | Outside the audio hot path | -The big variable is *N*. A reservation that is too small forces frequent re-reservations, each of which is a sync gate at an awkward moment in the conversation. A reservation that is too big over-holds budget against the [tenant's](/glossary#tenant) cap and bounds concurrent calls poorly. +The big variable is the estimate. A reservation that is too small forces the application to obtain additional reservations at awkward moments. A reservation that is too big holds more budget than the call is likely to use and reduces capacity available for other work. The honest answer is empirical. After a few hundred calls, the team has distributions: median call length, 95th percentile, ratio of premium-feature use, etc. The reservation should target the 95th percentile of expected consumption plus a safety margin. The same [estimate drift](/blog/estimate-drift-silent-killer-of-enforcement) considerations apply — reserve-to-actual ratios should be monitored and recalibrated, just at coarser granularity than for tool-calling agents. -The re-reservation step is where most voice teams get the user-visible latency wrong. The naive implementation re-reserves when the counter hits zero, which often coincides with the agent speaking. A better implementation re-reserves at the next turn boundary after crossing the 80% threshold — the conversation already pauses at turn boundaries, so the sync gate fits the existing latency budget. +Do not use the reservation-extension endpoint to add amount: extension changes expiry time, not the reserved quantity. If the local estimate approaches the first reservation, obtain a second reservation before authorizing the next bracket of work. A turn boundary is often a useful place to do that, but the threshold and bracket size are application choices. Track and commit or release every reservation separately. ## Pattern 2: Tier-Aware Gating @@ -100,34 +93,34 @@ The fast-path audio cannot sync-gate. The slow-path tool calls can. Pattern 2 ma - Tool calls go through standard [reserve-commit](/protocol/how-reserve-commit-works-in-cycles). - Premium-tier escalations (mid-call) get their own sync gate. -The implementation lives in the agent harness, not in the runtime authority. The harness routes each proposed action to the appropriate gate. For OpenAI Realtime, that means wrapping the WebSocket so that function-call output items and their `response.function_call_arguments.*` events get a sync gate before the tool call dispatches, while audio events do not. For Vapi, where the orchestrator already mediates tool dispatch, the gate plugs into the orchestrator's tool-call layer. +The implementation lives in the agent harness, not in Cycles. The harness routes each proposed action to the appropriate gate. For an end-to-end realtime API, the application handles a function-call event, obtains authority, and only then dispatches the tool. For an assembled voice platform, the gate belongs in the customer-controlled tool endpoint or dispatcher. The corpus has a parallel argument in [Cycles vs LLM Proxies and Observability Tools](/blog/cycles-vs-llm-proxies-and-observability-tools): the position of the gate determines what kinds of actions it can govern. For voice, the gate has two positions — one in the per-call reservation lifecycle (slow path) and one at the audio buffer (fast path). They are different gates with different latency budgets, both enforcing the same authority. ## Pattern 3: Time-Bounded Floor Authority -For very high-throughput voice deployments where even per-turn re-reservation feels intrusive, a different shape: each session holds a base authority floor that auto-replenishes at a steady rate (per-second, per-N-[tokens](/glossary#tokens)). The agent does not ask for ALLOW; it draws against the floor as long as the floor is non-zero. +This is an application scheduler pattern, not a current Cycles feature. An application can divide a call into prepaid brackets and require a live Cycles reservation before starting each bracket. It can compute a local replenishment schedule, but Cycles does not provide an auto-replenishing session floor. ``` -session_authority(t) = min( - base_floor + (replenish_rate × seconds_since_grant), - hard_cap -) - cumulative_consumed +local_bracket_allowance(t) = min( + base_allowance + (replenish_rate × seconds_since_grant), + application_ceiling +) - locally_metered_consumption ``` -When `session_authority` reaches zero or the [tenant](/glossary#tenant)'s hard cap fires, the session is denied at the *next bracket boundary* — typically a turn end — and the conversation transitions to a graceful close. The agent does not stop mid-frame; it finishes the current utterance and signals end-of-call. +When the local allowance reaches zero, the application asks Cycles for the next reservation. If that request is denied, the application must avoid starting the next bracket and use its own provider-specific close or transfer behavior. Cycles does not terminate the voice session. -This is the noisy-neighbor pattern from [multi-tenant cost control](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) applied to the time domain. A tenant under one runaway call still has its other concurrent calls bounded, because each call's floor draws from the shared replenish budget — the more calls running, the slower the floor refills per call. +To bound aggregate concurrent-call exposure, include the tenant on every call's Subject so all calls charge the tenant scope. If per-call enforcement is also needed, put the call ID in a standard field such as `workflow`; the same reservation then checks and charges both derived scopes atomically when both budgets exist. A call ID in `dimensions` is attribution only and does not derive a budget scope. -The pattern trades fine-grained authority for fast-path responsiveness. A runaway loop within a single call can over-consume by up to one bracket's worth of authority before the gate fires. That is a small constant, not an unbounded loss, and it is recoverable through the post-call commit reconciliation. +The pattern trades fine-grained control for fast-path responsiveness. The application can consume up to the amount it authorizes for the current bracket before the next check. A commit reconciles accounting, but it cannot undo provider usage or an externally observed action. -## Pattern 4: Speculative Commit with a Deny Window +## Pattern 4: Post-Execution Metering Is Not Authority -The most aggressive pattern, for systems where even a turn-boundary check is too slow. The agent acts immediately; the commit lands as soon as the action is observed; a short *deny window* — typically 100–300 ms — allows the runtime to send a `cancel-session` message if a hard cap fires retroactively. +An application can let work happen first and submit the actual amount afterward, but that is post-execution accounting rather than pre-execution authority. The current Cycles runtime does not send a `cancel-session` message or provide a retroactive deny window. -The catch is that audio is *unrecoverable*. By the time the cancel arrives, the previous half-second of speech has already reached the customer's ear. The pattern is acceptable for parts of the surface where rollback is meaningful (a tool call that has not yet been observed externally, a write-back that has not yet been persisted) and dangerous for parts where it is not (audio out). +The catch is that audio is *unrecoverable*. Once speech reaches the customer or provider usage accrues, a later debit cannot reverse it. Use post-execution metering for reporting and reconciliation, not as a substitute for a reservation when the action must be bounded before it starts. -This pattern is usually safer on the slow-path tool layer, layered on top of pattern 2 — it is named here mostly to be explicit about what it does not solve. Fast-path audio cannot be sped up by going asynchronous if the action itself is irreversible. +Slow-path tool calls should still use reserve-then-act when denial must prevent the side effect. Fast-path audio cannot be made reversible by moving its accounting off the hot path. ## Where Each Voice Stack Lets the Gate Sit @@ -135,14 +128,14 @@ A practical view of where a Cycles-style runtime authority gate can be inserted | Stack | Slow-path gate (tool calls) | Fast-path gate (audio frames) | Mediation point | |---|---|---|---| -| OpenAI Realtime API | Custom WebSocket relay intercepting function-call output items / `response.function_call_arguments.*` events | Reservation at call start; the customer's relay tracks consumption | Customer's relay server (a forwarding hop sized to fit inside the conversation's latency budget) | -| Vapi | Plug into Vapi's tool-server layer | Reservation against Vapi's per-minute billing surface; orchestration fee tracked separately | Vapi server-side webhook + per-minute polling | -| Retell AI | Custom function endpoints registered with Retell | Reservation against assembled-pipeline price; provider chain tracked through Retell's webhooks | Retell webhook layer | -| ElevenLabs Agents | Tool calls dispatched to the customer's backend | Reservation against ElevenLabs' all-in per-minute price | ElevenLabs webhook + tool dispatcher | +| [OpenAI Realtime API](https://developers.openai.com/api/docs/guides/realtime-conversations) | Application intercepts function calls before executing them | Call-start reservation plus an application-local usage meter | Customer application or relay | +| [Vapi](https://docs.vapi.ai/tools/custom-tools) | Custom tool server obtains authority before performing the tool | Call-start reservation plus provider usage records | Customer's tool server and backend | +| [Retell AI](https://docs.retellai.com/build/conversation-flow/custom-function) | Custom function endpoint obtains authority before the side effect | Call-start reservation plus provider usage records | Customer's custom function endpoint and backend | +| [ElevenLabs Agents](https://elevenlabs.io/docs/eleven-agents/customization/tools/webhook-tools) | Webhook tool endpoint obtains authority before the side effect | Call-start reservation plus conversation usage records | Customer's webhook tool endpoint and backend | -The architecture has a common shape. Each stack exposes a *control surface* (webhook, relay, tool-dispatcher) separate from the audio fast path. The runtime authority gate sits on the control surface for slow-path actions, and the predictive-reservation pattern bounds the fast path through pre-call budgeting. +The architecture has a common shape. Each stack exposes some customer-controlled path for tools, while the audio fast path remains separate. The Cycles gate sits on the customer-controlled path for slow actions, and a call-start reservation bounds the amount the application intentionally authorizes for the fast path. -The amount of work to wire this varies more by team than by stack. Vapi's tool-server model and Retell AI's webhook flow are similar enough that the same Cycles client can wrap both with adapter code. OpenAI Realtime's WebSocket relay needs more custom plumbing but offers the most direct control. ElevenLabs sits closest to a turnkey path with the least customization surface. +Provider APIs and control surfaces change. Verify the current provider documentation before choosing an interception point, and test whether denial actually prevents the downstream action. ## Voice-Specific Failure Modes a Gate Should Catch @@ -150,15 +143,15 @@ The slow-path / fast-path split changes which failure modes are catchable at whi | Failure mode | Where it appears | Where to gate | |---|---|---| -| Talking-to-itself loop (VAD failure, the agent interrupts itself) | Continuous audio with no user turn | Per-turn reservation re-check; time-bounded floor | -| Stuck conversation (the agent cannot end the call gracefully) | Long sessions with low per-turn cost but high cumulative | Predictive reservation hard cap; bracket-boundary deny | +| Talking-to-itself loop (VAD failure, the agent interrupts itself) | Continuous audio with no user turn | Application meter plus prepaid bracket boundary | +| Stuck conversation (the agent cannot end the call gracefully) | Long sessions with low per-turn cost but high cumulative | Call-start estimate ceiling plus application/provider duration limit | | Premium-tier escalation runaway | Per-tier surcharges accumulate | Sync gate on each escalation request | -| Cross-call cost amplification (many parallel calls) | Concurrent sessions exceed tenant cap | Hierarchical reservation: per-call floor draws from per-tenant cap | +| Cross-call cost amplification (many parallel calls) | Concurrent sessions exceed intended aggregate spend | Reserve each call against the same tenant-scoped ledger | | Tool-call [retry storm](/glossary#retry-storm) inside a long call | A single tool's retry loop runs forever | Standard [retry-storm idempotency](/blog/retry-storms-and-idempotency-in-agent-budget-systems) at the tool gate | -| Hold music / silence not bounded | The agent waits on a transfer for minutes | Wall-clock cap, not just token-cost cap | +| Hold music / silence not bounded | The agent waits on a transfer for minutes | Application timer and cost conversion, plus provider duration controls where available | | Provider chain cost drift | Vapi-style BYOK with one provider 3× more expensive than expected | Per-provider reservation accounting | -The wall-clock cap is the one voice teams forget most often. Token-denominated budgets behave well when the agent is talking. They behave poorly during silence — hold music, transfer waits, the customer thinking — because the meter on the carrier line keeps running even when the model is idle. A complete voice gate needs both a [token](/glossary#tokens) budget and a wall-clock budget. +The wall-clock dimension is easy to miss. Token-denominated budgets do not account for silence, hold music, transfer waits, or customer thinking time when the carrier line is still billable. Track elapsed time in the application and convert it to a supported Cycles budget unit; also use provider duration controls where they exist. ## The PocketOS Pattern at the Voice Layer @@ -166,44 +159,44 @@ The [two-layer fix from PocketOS](/blog/pocketos-aftermath-delete-delay-vs-scope **Provider-layer fixes (the voice equivalent of scoped tokens):** -- Per-call or per-session caps at the provider billing layer, to whatever degree each provider exposes them — typically through per-session budget headers, dashboard caps, or programmatic limits. Use whatever the provider offers as the outer envelope. -- Carrier-minute caps at the SIP / Twilio / telephony layer — independent of the agent's authority, scoped to the call. +- Per-call, per-session, or account controls at the provider billing layer, to the degree the selected provider exposes them. Availability and semantics vary. +- Carrier or telephony duration controls, where the selected service exposes them. - Premium-feature flags that require per-call enablement rather than session-wide grants. **Agent-layer fixes (the voice equivalent of runtime authority):** - Predictive reservation per call (pattern 1), with the upper-bound number set against the per-call provider cap. - Tier-aware gating on slow-path tool calls (pattern 2). -- Floor authority with wall-clock budgeting for the fast path (pattern 3, where the cadence demands it). -- Audit records of the call-level reservation, the runtime decision, and the actual consumption — separate from the provider's billing trail. +- Application-managed prepaid brackets for the fast path (pattern 3), when one whole-call reservation is too coarse. +- Cycles audit/evidence for submitted operations plus application records that connect call IDs, provider usage, local meter readings, and every reservation. -Treating these as alternatives is the same framing trap from PocketOS. A provider-level per-call cap without an agent-side gate leaves the agent free to burn the entire cap in a runaway loop. An agent-side gate without provider-level caps leans entirely on the gate's reservation accuracy. Both layers together make each other tractable. +Treating these as alternatives is the same framing trap from PocketOS. A provider control may still allow the session to consume its full envelope, while an application-side reservation depends on estimate coverage and correct integration. Use both when the provider offers a suitable control. ## A Short Checklist for Voice Agent Budgets For each voice agent the team runs in production: -1. **Is there a per-call hard cap?** If not, a single runaway session has unbounded [exposure](/glossary#exposure). -2. **Is the cap denominated in both tokens and wall-clock seconds?** A session that holds the line silent still burns telephony minutes. +1. **Is there a conservative per-call estimate ceiling?** It bounds what the integration intentionally authorizes, subject to estimate accuracy, coverage, and overage policy. +2. **Does the estimate include tokens and elapsed-time costs?** Cycles has no seconds unit, so convert billable time to `USD_MICROCENTS` or `CREDITS`. 3. **Are tool calls and audio frames on separate gates?** Mixing them means either the audio is slow or the tools are ungoverned. 4. **Does the reservation re-check land on a turn boundary?** If not, the user hears the gate. -5. **Are concurrent calls hierarchical against a tenant cap?** A single tenant should not be able to consume the platform with a hundred parallel calls. -6. **Is the audit trail recording the call-level reservation, the runtime decision, and the per-tier consumption separately?** Provider billing alone collapses these. +5. **Do concurrent calls include the shared tenant scope?** Add a standard per-call scope only if you also need a call-level boundary; keep the call ID in application logs either way. +6. **Can you join Cycles operations to the call and provider records?** Persist the call ID, trace ID, reservation IDs, estimates, and actual provider usage in application logs. 7. **Are premium-tier escalations a distinct sync-gated action?** A voice-cloning toggle or a model upgrade mid-call should not be free against the audio reservation. -A team that can answer "yes" to all seven is running runtime authority on the voice surface. Many production voice deployments today rely on the provider's per-call cap alone — which is necessary but not sufficient. +A team that can answer "yes" to all seven has the main pieces needed to test runtime authority on the voice surface. Confirm the behavior with denial, timeout, expiry, disconnect, and estimate-overrun tests before treating the boundary as enforced. ## What Changes When the Gate Moves Off the Hot Path -The shift the patterns above make explicit: the gate can be present without being synchronous. The audit trail still records every consequential action. The authority decision still binds. What changes is *when* the decision is made and *what unit* it is denominated in. +The shift the patterns above make explicit: the gate can move to a coarser boundary. Cycles records the operations submitted to it, but the application must record the per-frame and provider activity that occurs inside a prepaid bracket. -Per-call predictive reservation moves the decision from per-frame to per-call. The reservation is large; the latency is paid once; the accuracy comes from calibration, not from every-frame round-trips. The team accepts a small amount of authority slippage (within one bracket / one turn) in exchange for the conversation feeling natural. +Per-call predictive reservation moves the decision from per-frame to per-call. The reservation is larger; the round trip happens once; and accuracy comes from calibration rather than per-frame decisions. If the application uses brackets, its maximum authorized interval is the current prepaid bracket, while provider billing and irreversible effects still require separate measurement. -Tier-aware gating preserves the sync gate exactly where it fits — at the slow-path tool layer — and routes the fast path through pre-budgeted authority. The reserve-commit lifecycle the rest of the corpus uses applies unchanged to the slow path. The fast path uses a different lifecycle that produces equivalent audit evidence at the boundaries. +Tier-aware gating preserves the synchronous gate where it fits — at the slow-path tool layer — and routes the fast path through pre-budgeted authority. The reserve-commit lifecycle applies unchanged to the slow path. The fast path does not produce equivalent evidence automatically; application and provider records must fill that gap. The unifying observation is the same one that drives the [memory-writes](/blog/agent-memory-writes-are-actions-too), [merge-button](/blog/when-coding-agents-press-merge), and [click-surface](/blog/computer-use-agents-have-no-tool-boundary) extensions: the [action authority](/glossary#action-authority) lifecycle is more general than the surface it was first written for. Voice agents add a latency constraint the other surfaces did not have. The lifecycle absorbs it by adjusting when the decision happens — not by abandoning the decision. -The next time a voice agent burns $90 on a 17-minute call, the question worth answering should not be "why didn't the model end the call?" It should be "why did the harness keep streaming audio after the session's authority was exhausted?" That question has a clean answer with a predictive reservation in place. It has no answer at all without one. +The next time a voice agent exceeds its intended call envelope, the question worth answering should not stop at "why didn't the model end the call?" Ask why the harness started another prepaid interval, whether the local meter matched provider usage, and whether denial prevented the next action. Those questions are answerable only when Cycles operations, application logs, and provider records share identifiers. ## Next Steps diff --git a/blog/w3c-trace-context-ai-agent-debugging.md b/blog/w3c-trace-context-ai-agent-debugging.md index 6236c604..de3c8d8c 100644 --- a/blog/w3c-trace-context-ai-agent-debugging.md +++ b/blog/w3c-trace-context-ai-agent-debugging.md @@ -9,7 +9,7 @@ tags: - runtime-authority - production - operations -description: "Trace AI agent budget decisions across Cycles admin, runtime, events, and audit planes using W3C Trace Context and shared correlation identifiers end to end." +description: "Trace AI agent budget decisions across Cycles admin, runtime, event, and audit records with W3C Trace Context, reservation IDs, and application propagation." blog: true sidebar: false featured: false @@ -23,24 +23,24 @@ head: It's 2 AM. Your agent stack just tripped a spend alert. The monthly bill on one tenant is climbing at 4× the usual rate, and the incident channel wants to know which agent, which workflow, and which tool call is responsible — in time to intervene before the budget actually runs out. -You have four log sources to stitch together. The admin plane recorded the budget reservations. The runtime plane authorized the calls. The events service fanned [webhooks](/how-to/webhook-integrations) to your on-call hooks. Your audit store captured the approvals. Each one has timestamps and tenant IDs, and each one captured its own request ID. None of them agree on which rows belong to the same operation. +You have four log sources to stitch together. The admin plane recorded budget configuration changes. The runtime plane recorded reservation lifecycle calls. The events service fanned [webhooks](/how-to/webhook-integrations) to your on-call hooks. Your application audit store captured authorization decisions. Each one has timestamps and tenant IDs, and each one captured its own request ID. None of them agree on which rows belong to the same operation. Thirty minutes of `jq` later, you have a theory. Forty-five minutes in, you have a second theory. By then, the budget has leaked another $400. -This post is about what that debugging loop looks like when every plane shares a W3C Trace Context identifier, and what it takes to get there. The short version: Cycles treats `trace_id` as a first-class correlation key on every response header, every emitted event, every webhook delivery, and every audit row. A single 32-character hex string is enough to pull the whole causal picture out of a running stack — without a new agent, a new SDK, or a bespoke schema. +This post is about what that debugging loop looks like when every Cycles plane shares a W3C Trace Context identifier, and what it takes to get there. The short version: Cycles treats `trace_id` as a first-class correlation key on response headers and persisted records that support the field. A single 32-character hex string can join the Cycles-side budget lifecycle; application authorization and external outcomes still require correlated application telemetry. ## Observability alone hits a wall on multi-plane systems -Most LLM observability tools — Langfuse, LangSmith, Helicone, and their cousins — live in one slice of the stack: they proxy the LLM call, capture prompt and response, and surface cost and latency. That's real value for a single-agent workload. It falls short the moment your system has more than one plane of decision-making. +LLM observability tools capture model calls, prompts, responses, cost, and latency. Some also ship gateway controls—LangSmith's private-beta LLM Gateway and Helicone's custom limits are examples. Even then, a provider gateway sees a different slice from an application's budget, admin, event, and tool-authorization planes. An agent budget system has at least four: | Plane | What it decides | Example operation | |---|---|---| | **Admin** | Whether a budget/policy exists and what it allows | `POST /v1/admin/budgets` | -| **Runtime** | Whether a specific call is authorized *right now* | `POST /v1/reservations`, `POST /v1/reservations/{id}/commit` | +| **Runtime** | Whether a submitted amount fits the matching budget ledgers | `POST /v1/reservations`, `POST /v1/reservations/{id}/commit` | | **Events** | Which downstream consumers hear about a decision | Webhook delivery to PagerDuty, Slack, Datadog | | **Audit** | What the operator-facing record of the decision looks like | `GET /v1/admin/audit/logs` | @@ -52,7 +52,7 @@ W3C Trace Context solves this by making the correlation identifier *travel with ## What Cycles carries end-to-end -As of Cycles protocol revision 2026-04-18, every plane in the stack participates in one `trace_id`. When a request arrives, Cycles takes this identifier from one of three sources, in strict order: +The current protocol lets every Cycles HTTP plane participate in one `trace_id` when the caller propagates the same context across related requests. When a request arrives, Cycles takes this identifier from one of three sources, in strict order: 1. **`traceparent` header** — adopted when present and well-formed. The current format is identified by a `version` field of `00`, per the W3C Trace Context Recommendation (23 Nov 2021). 2. **`X-Cycles-Trace-Id` header** — a 32-character lowercase hex string, used when no valid `traceparent` is present. @@ -60,15 +60,15 @@ As of Cycles protocol revision 2026-04-18, every plane in the stack participates A malformed correlation header never causes Cycles to reject the request. The server silently falls through to the next rule. If both headers are present, valid, and disagree, `traceparent` wins — an upstream W3C-aware gateway is the authoritative source of truth. -From there, the same `trace_id` lands on every downstream artifact: +From there, the request's `trace_id` lands on the artifacts that support it: - **Every HTTP response** carries `X-Cycles-Trace-Id: <32-hex>`, whether the response was 2xx, 4xx, or 5xx. - **Every `ErrorResponse` body** populates a `trace_id` field (optional in the schema, populated by `cycles-server` v0.1.25.14+ and `cycles-server-admin` v0.1.25.31+). -- **Every emitted event** on the event stream — reservation created, committed, released, expired, overdraft, etc. — carries the `trace_id` of the originating request. -- **Every `AuditLogEntry`** persists the `trace_id` of the HTTP request that triggered it. -- **Every webhook delivery** POSTs an outbound `traceparent: 00--<16-hex-span>-` header plus an `X-Cycles-Trace-Id` mirror. The span-id is freshly generated per delivery; the trace-flags byte preserves the inbound W3C sampling decision when `traceparent_inbound_valid` was true, and defaults to `01` (sampled) otherwise. +- **Implemented emitted events** carry the originating request's `trace_id`; sweeper events use internally generated trace context. +- **Admin `AuditLogEntry` records**, plus the runtime's admin-on-behalf-of release audit entry, persist the request's `trace_id`. +- **Webhook deliveries for those events** POST an outbound `traceparent: 00--<16-hex-span>-` header plus an `X-Cycles-Trace-Id` mirror. The span-id is freshly generated per delivery; the trace-flags byte preserves the inbound W3C sampling decision when `traceparent_inbound_valid` was true, and defaults to `01` (sampled) otherwise. -That one identifier is the end-to-end thread through admin → runtime → events → webhook consumer. And because the webhook outbound headers follow the current `traceparent` format, your consumer's tracing infrastructure — Datadog APM, Honeycomb, Jaeger, Tempo, OpenTelemetry Collector — picks up the span automatically, without a custom adapter. +The reservation model itself does not persist `trace_id`, and successful reserve/commit/release operations do not each emit lifecycle events in the current runtime. Keep application logs keyed by trace ID and reservation ID to bridge those gaps. When a webhook is produced, an instrumented consumer configured for W3C propagation can continue its trace without a Cycles-specific header adapter. ## Three identifiers, three different questions @@ -78,19 +78,26 @@ Experienced operators sometimes ask why Cycles doesn't just collapse everything |---|---|---|---|---| | `request_id` | One HTTP request | Milliseconds | Cycles server | "Which log line does this one response belong to?" | | `trace_id` | One logical operation | Seconds to minutes | Upstream or Cycles | "Which reserve-commit pair + downstream events belong to this agent call?" | -| `correlation_id` | Operator-defined cluster | Arbitrary | Operator | "Which budget decisions all belong to this overnight batch run?" | +| `correlation_id` | Event cluster or admin fan-out | Operation-dependent | Cycles server | "Which emitted admin events belong to this server-composed operation?" | -A reserve-commit lifecycle — for example, reserving budget before an LLM call, committing on success, releasing on error — spans multiple HTTP requests. Each request has its own `request_id`; all three share one `trace_id`. `correlation_id` is the layer above that: the operator groups a whole multi-hour batch by stamping it, and the trace IDs within that batch can still be used to drill into any individual decision. +A reserve-commit lifecycle spans multiple HTTP requests. Each request gets its own `request_id`; all related calls share one `trace_id` only when the caller sends the same trace context. `correlation_id` is not operator-stamped: the protocol requires deterministic runtime event clusters, although the current reference runtime leaves that field absent, while selected admin operations populate explicit server-composed IDs. Put any application batch ID in request metadata and your own logs. -This mirrors the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/) for distributed systems, where trace is the causal axis and correlation is the grouping axis. Nothing novel — just applied consistently through an agent budget stack. +The practical split is trace for causal propagation, request ID for one Cycles call, and optional application metadata for business grouping. ## A 2 AM debug, with trace_id -Here's the incident loop when correlation is wired in. An alert fires on elevated reservation-release rate: +Here's the incident loop when tracing is wired in. An application alert identifies a failed commit: ```bash -# On-call engineer grabs the trace_id off the failing response -curl -i -H "X-Cycles-API-Key: $CYCLES_API_KEY" \ +# The application propagated this trace across reserve, provider call, and commit. +TID=4bf92f3577b34da6a3ce929d0e0e4736 + +curl -i -X POST \ + -H "X-Cycles-API-Key: $CYCLES_API_KEY" \ + -H "X-Cycles-Trace-Id: $TID" \ + -H "X-Idempotency-Key: commit-res-abc-1" \ + -H "Content-Type: application/json" \ + -d '{"actual":{"amount":480000,"unit":"USD_MICROCENTS"},"idempotency_key":"commit-res-abc-1"}' \ "http://localhost:7878/v1/reservations/res-abc/commit" # → HTTP 409 Conflict # → X-Cycles-Trace-Id: 4bf92f3577b34da6a3ce929d0e0e4736 @@ -111,9 +118,9 @@ curl -s -H "X-Admin-API-Key: $ADMIN_KEY" \ "http://localhost:7979/v1/admin/events?trace_id=4bf92f3577b34da6a3ce929d0e0e4736" | jq ``` -Two queries — executed in parallel — give you the full causal picture: the reservation that consumed the budget, the commit that tipped over the cap, the events emitted to downstream webhooks, and the audit rows stamped with `actor` + `scope` + `trace_id`. No time-range guessing. No tenant-by-tenant cross-join. A thirty-minute debug session compresses into a couple of API calls. +These queries return any audit rows and events retained for the trace. A live commit rejection may have no corresponding event, and the reservation object has no `trace_id` field, so use the reservation ID from the application error path and join it to trace-linked application/provider logs. Trace filtering narrows Cycles records; it does not manufacture lifecycle records that were never emitted. -Cycles' [Where Did My Tokens Go?](/blog/where-did-my-tokens-go-debugging-agent-spend) post walks a similar debug using `correlation_id` and scope path. `trace_id` is the layer below that: for a *single operation*, it pinpoints the exact reservation + commit + event chain; `correlation_id` scales that up to a whole batch run. +Cycles' [Where Did My Tokens Go?](/blog/where-did-my-tokens-go-debugging-agent-spend) post walks a similar investigation using trace ID and scope path. For a single operation, the propagated `trace_id` joins the records that carry it; keep the reservation ID and application metadata alongside it for the rest. ## Webhook consumers inherit the trace for free @@ -140,7 +147,7 @@ async def handle_webhook(request: Request): # ...route to PagerDuty / Slack / Datadog per your runbook ``` -The span this handler produces is a *child* of the span that originated in the agent request that triggered the budget event. That means in your tracing UI of choice, you can click from "webhook delivery to PagerDuty" back up through the reservation commit that caused it, back up through the original agent HTTP request — without a single custom correlation field, and without a Cycles SDK. +The span this handler produces is a child of the context carried by the budget event. If the application propagated one trace through the protected operation, the tracing UI can join the webhook consumer to application spans. Cycles does not create missing provider or application spans, so those components still need normal tracing instrumentation. For signature verification, idempotency, and delivery retries, see [Operational Runbook: Using Cycles Runtime Events](/blog/operational-runbook-using-cycles-runtime-events). @@ -157,31 +164,31 @@ Not every plane shipped `trace_id` at the same time. When you're upgrading in a Pre-v0.1.25.14 rows will not have `trace_id` populated. During a phased rollout, queries should also fall back to `request_id` for backward compatibility on older audit and event rows. Every version listed is available as a published image on `ghcr.io/runcycles/...`; the [Full Stack Deployment Guide](/quickstart/deploying-the-full-cycles-stack) pins an aligned combination if you're bringing the whole stack up at once. -## Where this fits against observability-only tools +## Where this fits alongside observability and gateways The LLM observability market is crowded, and it's easy to confuse "tracing" with "observability." They solve different problems. -| | Observability-only (Langfuse, LangSmith, Helicone) | Runtime authority + trace context (Cycles) | +| | LLM observability or provider gateway | Budget authority + trace context (Cycles) | |---|---|---| -| **Timing of decision** | Post-execution (logs what happened) | Pre-execution (decides what's allowed) | -| **Scope of visibility** | LLM call + immediate proxy | Admin policy → runtime reserve/commit → events → audit → webhook consumers | -| **Correlation surface** | Trace within the proxy's captures | W3C Trace Context across every Cycles plane + into your downstream tracing stack | -| **What it prevents** | Nothing — visibility only | Overspend, unauthorized actions, uncontrolled delegation | -| **What it explains** | Prompt, response, latency, cost of one call | The full causal chain: which policy, which reservation, which commit, which events, which webhook consumers | +| **Timing** | Tracing records execution; an enabled gateway may also evaluate provider policy before forwarding | Reservation holds budget before protected work; commit/release settles it | +| **Scope of visibility** | Instrumented LLM and application traces; gateway-proxied provider calls | Admin budget changes → runtime reserve/commit → events → audit → webhook delivery | +| **Correlation surface** | Product trace IDs and supported W3C propagation | W3C Trace Context across Cycles planes and into downstream tracing | +| **What it can block** | Product-dependent gateway traffic | Submitted budget requests on mandatory instrumented paths | +| **What it explains** | Prompt, response, latency, model cost, and traced tool runs | Which budget request, settlement, events, and webhook records share a trace | -The complement matters. Observability tells you what *did* happen; runtime authority decides what *will* happen; trace context is the thread that stitches the two views together — so the post-execution capture you already trust can reach into the pre-execution decisions an authorization layer makes. See [Runtime Authority vs. Guardrails vs. Observability](/blog/runtime-authority-vs-guardrails-vs-observability) for the broader pattern. +The complement matters. Tracing explains execution, gateway policies can govern traffic they proxy, and reserve-commit records explain how application budget was held and settled. Trace context stitches those views together. Tool permission and argument authorization remain separate application records. See [Runtime Authority vs. Guardrails vs. Observability](/blog/runtime-authority-vs-guardrails-vs-observability) for the broader pattern. ## The operational takeaway -Distributed tracing isn't a new idea. What's new is applying it rigorously to the decisions an AI agent stack makes *about* an LLM call, not just the LLM call itself. A 32-character hex string — handed to you by your load balancer, your API gateway, or Cycles itself — is enough to collapse four planes of logs into one causal picture, turn a 30-minute incident into a 30-second query, and hand every downstream webhook consumer a span they can parent their own work under. +Distributed tracing isn't a new idea. The useful step here is applying it to the budget lifecycle around an LLM or tool call, not just the call itself. A 32-character hex string—handed to you by your load balancer, API gateway, or Cycles—can collapse several Cycles queries into one correlation path and give downstream webhook consumers a trace they can continue. -If you're already emitting `traceparent` from an upstream W3C-aware gateway, Cycles will pick it up. If you aren't, Cycles will mint one and stamp it on everything it touches. Either way, the correlation surface is the same. +If you're already emitting `traceparent` from an upstream W3C-aware gateway, Cycles will pick it up. If you are not, Cycles mints a trace ID for that individual request and returns it, but separate reserve and settlement calls will not share the generated value automatically. Propagate one upstream trace when you need a multi-request join. ## Related reading - [Correlation and Tracing in Cycles](/protocol/correlation-and-tracing-in-cycles) — the full spec, including the `request_id` / `trace_id` / `correlation_id` contract and schema fields - [Operational Runbook: Using Cycles Runtime Events](/blog/operational-runbook-using-cycles-runtime-events) — webhook handler patterns, severity tiers, and incident triage -- [Where Did My Tokens Go? Debugging Agent Spend at Production Scale](/blog/where-did-my-tokens-go-debugging-agent-spend) — attribution patterns using `correlation_id` + scope path +- [Where Did My Tokens Go? Debugging Agent Spend at Production Scale](/blog/where-did-my-tokens-go-debugging-agent-spend) — attribution patterns using `trace_id` + scope path - [Runtime Authority vs. Guardrails vs. Observability](/blog/runtime-authority-vs-guardrails-vs-observability) — why tracing and enforcement are complements, not alternatives - [W3C Trace Context specification](https://www.w3.org/TR/trace-context/) — the authoritative reference for `traceparent` / `tracestate` header format - [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/) — the broader standards ecosystem Cycles plugs into diff --git a/blog/we-built-a-custom-agent-rate-limiter-heres-why-we-stopped.md b/blog/we-built-a-custom-agent-rate-limiter-heres-why-we-stopped.md index 77c47357..42926a7c 100644 --- a/blog/we-built-a-custom-agent-rate-limiter-heres-why-we-stopped.md +++ b/blog/we-built-a-custom-agent-rate-limiter-heres-why-we-stopped.md @@ -85,7 +85,7 @@ We ended up with a fragmented system: - No way to enforce provider-specific limits (*"this user can spend $50/month on video but unlimited on search"*) - Cost estimates drifted: what we predicted vs. what the provider actually billed diverged over time -Worse, OpenAI's own rate limits are [organization- and project-scoped](https://platform.openai.com/docs/guides/rate-limits), vary by model, and some model families share limits. That meant our single-budget design couldn't even represent OpenAI's constraints cleanly, let alone the five other providers we were integrating. +Worse, OpenAI's own rate limits are [organization- and project-scoped](https://developers.openai.com/api/docs/guides/rate-limits), vary by model, and some model families share limits. That meant our single-budget design couldn't even represent OpenAI's constraints cleanly, let alone the five other providers we were integrating. Every provider addition was a week of work. Every provider pricing change was a fire drill. We called this the **whack-a-mole phase**. @@ -104,7 +104,7 @@ v3 held for a while. Then the third wall showed up — and this one changed how **The wall: risk wasn't cost.** -An agent got stuck in a loop and [sent 200 emails](/blog/ai-agent-action-control-hard-limits-side-effects) to customers. Token cost: $1.40. Business damage: much larger. +In a constructed failure scenario, an agent gets stuck in a loop and [sends 200 emails](/blog/ai-agent-action-control-hard-limits-side-effects) to customers. Assumed token cost: $1.40. Potential business damage: much larger and not quantified by that inference bill. Our rate limiter never fired. Because it measured dollars, and the emails were cheap. The harm was in the action, not the spend. diff --git a/blog/webhook-delivery-operators-can-trust.md b/blog/webhook-delivery-operators-can-trust.md index b5c67b0c..c437b104 100644 --- a/blog/webhook-delivery-operators-can-trust.md +++ b/blog/webhook-delivery-operators-can-trust.md @@ -3,7 +3,7 @@ title: "Webhook Delivery That Operators Can Trust" date: 2026-05-01 author: Albert Mavashev tags: - - webhook + - webhooks - operations - reliability - incident-response @@ -20,7 +20,7 @@ head: # Webhook Delivery That Operators Can Trust -A budget denial event fires at 2:14 AM. PagerDuty never opens an incident. Slack never receives the message. The agent owner keeps seeing retries, but the receiver logs are empty because the reverse proxy dropped the request body on the way in. +A `budget.exhausted` event fires at 2:14 AM. PagerDuty never opens an incident. Slack never receives the message. The agent owner sees live reservation failures, but the receiver logs are empty because the reverse proxy dropped the webhook request on the way in. The problem is not that the event system failed to be clever. The problem is that the delivery contract was not explicit enough for operators to debug it under pressure. @@ -73,9 +73,9 @@ For the full delivery reference, see [Webhook Event Delivery Protocol](/protocol Cycles webhooks are delivered at least once. Duplicates can happen when a network timeout hides a successful receiver response, when the [events service](/glossary#events-service) restarts during delivery, or when an operator replays a delivery. -Events for the same tenant are dispatched in order. Cross-tenant ordering is not guaranteed, so a receiver that aggregates across tenants must not assume a global timeline from arrival order. +Webhook arrival order is not guaranteed, including within one tenant: concurrent consumers and retries can reorder deliveries. Consumers should deduplicate by `event_id` and use the event timestamp or stored event log when reconstructing a sequence. -That is the right reliability tradeoff for budget and governance events. Losing a `reservation.denied` event is worse than delivering it twice, as long as the receiver deduplicates correctly. +That is the right reliability tradeoff for budget and governance events. Losing a `budget.exhausted` event is worse than delivering it twice, as long as the receiver deduplicates correctly. The receiver contract is simple: @@ -106,7 +106,7 @@ This predictability matters during incidents. If the receiver returns 500 for te ## Stale delivery cutoff prevents old control signals -Some events are no longer useful after enough time has passed. A `budget.threshold_crossed` event from yesterday may be useful for reporting, but it should not page someone as if the threshold crossed right now. A `system.webhook_delivery_failed` event from an old outage can create false urgency if it lands after the system recovered. +Some events are no longer useful after enough time has passed. A `budget.exhausted` event from yesterday may be useful for reporting, but it should not page someone as if the ledger reached zero right now. A `system.webhook_delivery_failed` event from an old outage can create false urgency if it lands after the system recovered. Cycles marks deliveries older than the configured maximum delivery age as `FAILED` without another HTTP attempt. The default is 24 hours. diff --git a/blog/webhook-idempotency-patterns-for-ai-agent-budget-events.md b/blog/webhook-idempotency-patterns-for-ai-agent-budget-events.md index 1da2157c..b489f081 100644 --- a/blog/webhook-idempotency-patterns-for-ai-agent-budget-events.md +++ b/blog/webhook-idempotency-patterns-for-ai-agent-budget-events.md @@ -3,7 +3,7 @@ title: "Webhook Idempotency for Agent Budget Events" date: 2026-04-24 author: Albert Mavashev tags: - - webhook + - webhooks - engineering - reliability - production @@ -22,7 +22,7 @@ head: # Webhook Idempotency Patterns for AI Agent Budget Events -At 11:42 PM, PagerDuty fires on a `reservation.denied` event. At 11:42 PM, PagerDuty fires again on the same `reservation.denied` event. The on-call engineer pages an agent owner twice, who calmly notes that there's only one denial in the Cycles audit log. The webhook delivered twice, the pager receiver processed both deliveries, and the duplicate turned one incident into two acknowledgments and twice the noise budget. +At 11:42 PM, PagerDuty fires on a `budget.exhausted` event. At 11:42 PM, PagerDuty fires again on the same event. The on-call engineer pages an agent owner twice, who confirms that there is only one event in the Cycles event log. The webhook delivered twice, the pager receiver processed both deliveries, and the duplicate turned one incident into two acknowledgments and twice the noise budget. This is what at-least-once delivery actually looks like in production. It's not a bug in the webhook pipeline — it's the contract. Cycles' event delivery, like Stripe's webhooks, GitHub's deliveries, and AWS SNS messages, offers at-least-once semantics, which is another way of saying "assume your receiver will see the same event more than once and design accordingly." Teams that skip the dedup step aren't absent from the contract; they're just the receiver that didn't hold up their end. @@ -45,7 +45,7 @@ The delivery contract the rest of this post assumes: - **At-least-once delivery.** Retries happen on network timeouts, events-service restarts, and operator-triggered event replay. The same `X-Cycles-Event-Id` may arrive multiple times. - **Exponential backoff.** Default schedule is 1s, 2s, 4s, 8s, 16s — capped at 60s — across up to six total attempts (initial + five retries). Success is any HTTP 2xx; any other response is a retry. -- **Auto-disable after ten consecutive failures.** The subscription is paused and must be re-enabled via `PATCH /v1/admin/webhooks/{id}`. This prevents a permanently broken receiver from accumulating a backlog of stale deliveries. +- **Auto-disable after ten consecutive failures.** With the default threshold, the subscription enters `DISABLED` and must be re-enabled via `PATCH /v1/admin/webhooks/{id}`. This prevents a permanently broken receiver from accumulating a backlog of stale deliveries. - **Stale-delivery timeout.** Deliveries older than 24 hours are marked `FAILED` without another HTTP attempt. The full spec lives in [Webhook Event Delivery Protocol](/protocol/webhook-event-delivery-protocol). The [Security](/security#webhook-security) page covers signing-secret rotation and SSRF protection. This post focuses on the receiver side. diff --git a/blog/what-four-new-surfaces-taught-us.md b/blog/what-four-new-surfaces-taught-us.md index cab4b09e..81846d15 100644 --- a/blog/what-four-new-surfaces-taught-us.md +++ b/blog/what-four-new-surfaces-taught-us.md @@ -31,7 +31,7 @@ A month on, the conclusion is firmer than I expected. The lifecycle generalized. ## The Primitive That Held -[Reserve-commit](/glossary#reservation) is propose → ALLOW / ALLOW_WITH_CAPS / DENY → act → commit. It was originally written for one shape of action: a tool call with structured arguments, with a discrete moment between intent and execution where the gate could sit. +[Reserve-commit](/glossary#reservation) is propose → accepted hold (`ALLOW` or configured `ALLOW_WITH_CAPS`) / rejection → act → commit. `DENY` is the response used by `decide` and dry-run evaluation. The lifecycle fits work with a discrete moment between intent and execution where a mandatory gate can sit. Across four surfaces, that abstract description held. What changed: @@ -46,7 +46,7 @@ Voice is the load-bearing case. The other three sites preserve the per-action ga Four things varied. None broke the primitive. -**The feature vector.** Outbound tool calls had a tool name and structured arguments to gate on. Memory writes had an operation + a scope. Merges had a branch + an author + an approver. Clicks had a URL pattern + a DOM target + an action verb + (for pixel agents) a screenshot crop. Voice had a tier + a duration + a wall-clock budget. The rule body looked at different things; the rule shape stayed. +**The feature vector.** Outbound tool calls had a tool name and structured arguments to gate on. Memory writes had an operation + a scope. Merges had a branch + an author + an approver. Clicks had a URL pattern + a DOM target + an action verb + (for pixel agents) a screenshot crop. Voice had a tier + an application-measured duration converted to a supported cost unit. The rule body looked at different things; the rule shape stayed. **The blast radius shape.** Outbound side effects radiate outward — an email reaches one recipient. Memory writes radiate forward in time — one bad fact reaches every future retrieval. Merges fan out through CI/CD — one merge triggers a deploy matrix. Clicks land at exactly one DOM element but the wrong DOM element changes the entire blast radius. Voice frames don't have a discrete radius at all; they accumulate cost continuously. The rule body absorbed each, but the *risk scoring* had to be surface-aware. diff --git a/blog/what-goes-in-an-ai-agent-audit-packet.md b/blog/what-goes-in-an-ai-agent-audit-packet.md index 3c3135b8..40b71207 100644 --- a/blog/what-goes-in-an-ai-agent-audit-packet.md +++ b/blog/what-goes-in-an-ai-agent-audit-packet.md @@ -55,7 +55,7 @@ Start with the reviewer questions, not with the systems that produced the record | What did the authority decide? | Decision response, denial code, `ALLOW`, `ALLOW_WITH_CAPS`, or `DENY` summary | | Can the bytes be verified? | `evidence_id` recomputation, canonical JSON check, Ed25519 signature result | | Who signed it? | `server_id`, `signer_did`, key id, JWK Set reference, signer-authority disposition | -| Where do operational records join? | `trace_id`, `correlation_id`, event id, audit id, incident ticket, dashboard link | +| Where do operational records join? | `trace_id`, `request_id`, applicable admin-event `correlation_id`, event id, audit id, incident ticket, dashboard link | | How long will it survive? | Evidence retention, audit-log retention, archive restore path, retired-key retention | The first two questions explain the control outcome. The next two make it independently verifiable. The last two make it operationally useful after the people and systems around the event have changed. @@ -74,7 +74,7 @@ For a Cycles-controlled agent action, the packet should be small enough to revie | Verification output | Hash result, signature result, artifact-type check, verifier version or command | Shows the technical result, not just a conclusion | | Signer authority note | `signer_did`, `kid`, JWK Set URL or snapshot, authority disposition | Separates "signed by a key" from "authorized for this server then" | | Decision chain | Relevant `decide`, `reserve`, `commit`, `release`, or `error` artifacts | Explains where the control fired | -| Correlation bundle | `trace_id`, `correlation_id`, audit/event IDs, incident or change ticket links | Connects proof to operations without dumping every log | +| Correlation bundle | `trace_id`, `request_id`, applicable admin-event `correlation_id`, audit/event IDs, incident or change ticket links | Connects proof to operations without dumping every log | | Retention statement | Hot store duration, archive path, restore procedure, retired-key retention | Shows the proof will survive the review window | | Findings log | Gaps, owner, due date, follow-up drill date | Turns weak evidence into managed risk | diff --git a/blog/what-is-runtime-authority-for-ai-agents.md b/blog/what-is-runtime-authority-for-ai-agents.md index a289f62c..1bb4f1d9 100644 --- a/blog/what-is-runtime-authority-for-ai-agents.md +++ b/blog/what-is-runtime-authority-for-ai-agents.md @@ -45,7 +45,7 @@ The key properties of a runtime authority are: - **Pre-execution.** The decision happens before the action, not after. - **Enforcement.** The system can block or constrain, not just observe and report. -- **Scoped.** Decisions apply at the right level — per [tenant](/glossary#tenant), per workflow, per agent, per run — not just globally. +- **Scoped.** Decisions apply at the right standard level — [tenant](/glossary#tenant), workspace, app, workflow, agent, or toolset. An application can map a run ID to a workflow value for a per-run ledger. - **Concurrency-safe.** Correct even when multiple agents share the same budget and act simultaneously. - **Reconciled.** Estimated cost is reserved before execution; actual cost is committed after. The difference is released. @@ -75,13 +75,11 @@ Most teams building on LLMs assemble a stack that addresses three concerns. Each |---|---|---|---| | **Routing** | *Which* model handles this? | Before execution (model selection) | LiteLLM, Portkey, Manifest | | **Visibility** | *What* happened? | After execution (logging, tracing) | Helicone, Langfuse, LangSmith | -| **Authority** | *Should* this happen at all? | Before execution (permission, limits, and policy check) | Cycles | +| **Authority** | *May this principal perform this action, and is configured capacity available?* | Before execution | Application/IAM policy plus a budget authority such as Cycles | Routing is well-understood. Visibility is well-understood. -Authority — the pre-execution enforcement decision — is the layer most teams are missing. - -It is also the only layer that can **prevent** overspend and uncontrolled side effects rather than **report** them. +Authority is a pre-execution decision layer. IAM systems, gateways, policy engines, and application checks can all block classes of action. Cycles adds a cumulative, ledger-backed reserve-commit budget across instrumented spend and caller-assigned exposure. These three layers compose — they are not alternatives. Remove any one and a gap appears. @@ -105,11 +103,11 @@ A counter incremented after each call is a [checker, not an authority](/blog/vib **Runtime authority is not prompt-level safety.** Content filters and guardrails govern what a model says. Runtime authority governs what the system around the model is allowed to do — reserve resources, invoke tools, trigger side effects. One shapes output. The other shapes execution. -## The reserve/commit lifecycle +## The reserve-commit lifecycle -The mechanism behind runtime authority is the [reserve/commit lifecycle](/protocol/how-reserve-commit-works-in-cycles). Instead of tracking spend after the fact, budget is reserved before execution and actual cost is committed after. +The mechanism behind runtime authority is the [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles). Instead of tracking spend after the fact, budget is reserved before execution and actual cost is committed after. -1. **Reserve** — before work starts, the agent declares an estimated cost. The authority checks all applicable scopes (tenant, workflow, run) atomically and either reserves the budget or denies the request. +1. **Reserve** — before work starts, the agent declares an estimated cost. The authority checks every applicable populated standard scope atomically and either creates the reservation or returns an error. 2. **Execute** — work proceeds only if the [reservation](/glossary#reservation) succeeded. The reserved amount is held against the budget, visible to all concurrent actors. 3. **Commit** — after work completes, the agent reports the actual cost. If actual cost was lower than the estimate, the unused remainder is released automatically. 4. **Release** — if work is canceled before completion, the reservation is explicitly released. @@ -120,18 +118,18 @@ This lifecycle solves the problems that simple counters cannot: - **Concurrency.** Two agents cannot both claim the same $20 of remaining budget. The reservation is atomic. - **Retries.** A retried operation uses the same reservation, preventing double-spend. -- **Partial failure.** If work fails halfway, the uncommitted portion is released — not lost. -- **Uncertainty.** You rarely know the exact cost of a model call before it happens. Reserve/commit handles the gap between estimated and actual cost cleanly. +- **Partial failure.** If work starts and then fails, the host commits the best-known actual usage and releases only the unused remainder. A full release is appropriate only when the work did not start or consumed nothing. +- **Uncertainty.** You rarely know the exact cost of a model call before it happens. Reserve-commit handles the gap between estimated and actual cost cleanly. -The lifecycle also enables **hierarchical scopes**. A single reservation checks multiple levels — organization, tenant, workspace, workflow, run — in one atomic operation. If any scope is exhausted, the reservation is denied. Per-tenant limits, per-workflow caps, and per-run budgets compose without custom enforcement logic in the application. +The lifecycle also enables **hierarchical scopes**. A single reservation checks the applicable levels of the submitted subject — tenant, workspace, app, workflow, agent, and toolset — in one atomic operation. If any matching budget is exhausted, the reservation is rejected. Applications can map their own run identifier into an appropriate subject level or metadata, but `run` is not a protocol subject field. ## How Cycles approaches runtime authority -Cycles is not a dashboard for agent costs. It is a protocol for runtime permissioning over spend, risk, and actions. +Cycles is not a dashboard for agent costs. It is a protocol for runtime budget authority over spend and caller-assigned action exposure. -Before an agent takes its next action, Cycles answers whether that action is allowed, under what constraints, and what happens if limits are reached. That decision is made by a [protocol](/protocol/api-reference-for-the-cycles-protocol) — not by a proxy, not by application code, not by a dashboard with alerts. The protocol defines the reserve/commit lifecycle, hierarchical scopes, three-way decisions, and idempotency guarantees that make runtime authority operational. +Before a protected action, Cycles answers whether the configured budget can cover the submitted amount and scope and can return operator-configured caps with an accepted decision. The host remains responsible for authorizing the tool and arguments and for applying returned caps. The [protocol](/protocol/api-reference-for-the-cycles-protocol) defines the reserve-commit lifecycle, hierarchical scopes, three-way decisions, and idempotency guarantees that make that budget boundary operational. -Because it is protocol-based, Cycles works across frameworks, languages, and providers. It does not care whether the agent is built with LangGraph, CrewAI, a custom loop, or a coding agent like Claude Code. It cares whether the next action is permitted. +Because it is protocol-based, Cycles works across frameworks, languages, and providers. It does not care whether the agent is built with LangGraph, CrewAI, a custom loop, or a coding agent like Claude Code. It evaluates the budget request the integration submits. ## Next steps diff --git a/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents.md b/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents.md index f293bb85..cad4970b 100644 --- a/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents.md +++ b/blog/when-budget-runs-out-graceful-degradation-patterns-for-ai-agents.md @@ -21,7 +21,7 @@ Runtime enforcement solves one problem and creates another. Once your guardrails A hard stop — "budget exceeded, goodbye" — is better than a runaway agent. But it's not a good user experience. The agent was in the middle of something. The user was waiting for a result. A bare error message doesn't help either of them. -And budget isn't the only reason an agent gets blocked. [Runtime authority](/blog/what-is-runtime-authority-for-ai-agents) enforces both **cost limits** (you've spent your $10) and **risk limits** (you've used your 3 allowed `send_email()` calls, or your [RISK_POINTS](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk) budget is exhausted). The DENY is the same signal. The recovery path is different. +And budget isn't the only reason an agent gets blocked. [Runtime authority](/blog/what-is-runtime-authority-for-ai-agents) can combine **cost limits** (you've spent your $10) with caller-assigned [RISK_POINTS](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk) exposure budgets. Per-tool quotas such as "three `send_email()` calls" still belong in the application or agent harness; Cycles does not count tool calls natively. The recovery path depends on which boundary fired. This post covers five patterns for handling enforcement decisions gracefully — whether the trigger is cost, risk, or both, and whether the response is a full **DENY**, a constrained **ALLOW_WITH_CAPS**, or a soft signal that limits are approaching. The goal is the same in every case: **the agent completes as much useful work as possible within the boundaries it's given.** @@ -49,7 +49,7 @@ Every pattern below depends on the three [enforcement decisions](/blog/ai-agent- |---|---|---|---| | **ALLOW** | — | Proceed normally | Execute the planned action | | **ALLOW_WITH_CAPS** | Cost, risk, or both | Proceed, but with constraints | Adapt — cheaper model (cost), narrower tools (risk), or both | -| **DENY** | Cost, risk, or both | Action not permitted | Stop, degrade, queue, or inform the user | +| **DENY** | Cost, risk, or both | A decision request was rejected | Stop, degrade, queue, or inform the user | The binary allow/deny model forces hard stops. The three-way model gives agents room to adapt. Most of the patterns below depend on **ALLOW_WITH_CAPS** — the middle ground where enforcement narrows what the agent can do without killing the session entirely. @@ -107,14 +107,14 @@ This is the [progressive capability narrowing](/blog/ai-agent-action-control-har - **Full access:** Normal operation - **High-risk tools denied:** Agent continues but skips actions that send external messages, mutate data, or trigger deployments. It can still research, draft, and prepare outputs for human review. - **Read-only mode:** Agent can answer questions, retrieve information, and summarize — but can't take any action with side effects. -- **DENY:** Graceful stop (see Pattern 5). +- **Rejected request:** Graceful stop (see Pattern 5). **Trade-offs:** - The agent degrades gracefully instead of hard-stopping - It can still complete useful work — reading files, running searches, generating summaries — while dangerous capabilities are removed - Requires defining risk tiers for your tools (see [risk assessment guide](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk)) - Users may not realize the agent is operating in a constrained mode unless you tell them -- **Critical: the agent must know why a tool was denied.** If the enforcement layer returns a generic "tool execution failed" error, a persistent agent will retry the tool in an expensive loop or hallucinate alternatives. The DENY or ALLOW_WITH_CAPS response should include context the LLM can reason about — e.g., *"send_email capability revoked due to risk limits. Proceed with read-only operations."* — so the agent changes strategy instead of retrying blindly +- **Critical: the agent must know why the application withheld a tool.** If the harness returns a generic "tool execution failed" error, a persistent agent may retry the tool in an expensive loop or hallucinate alternatives. Translate the Cycles decision or reservation error into application-level context the model can reason about — for example, *"`send_email` is unavailable under the current exposure policy. Proceed with read-only operations."* — so the agent changes strategy instead of retrying blindly --- @@ -245,7 +245,7 @@ Patterns 2 and 3 add sophistication for agents with complex tool access or batch The trigger matters because the recovery options are different: -| | Cost-driven DENY | Risk-driven DENY | +| | Cost-driven rejection | Exposure-driven rejection | |---|---|---| | **Why it fired** | Dollar or token budget exhausted | RISK_POINTS budget exhausted or specific tool denied | | **Can a cheaper model help?** | Yes — Pattern 1 | No — the model isn't the problem, the action is | @@ -253,7 +253,7 @@ The trigger matters because the recovery options are different: | **Can it be deferred?** | Yes — wait for next budget window | Depends — some actions are time-sensitive | | **Typical recovery path** | Downgrade model → partial completion → stop | Narrow capabilities → read-only mode → stop | -The key insight: **a cost DENY means the agent can't afford to act. A risk DENY means the agent isn't allowed to act.** Agents that handle both need to inspect the DENY reason and choose the right degradation path. +The key insight: **a cost rejection means the protected operation does not fit the remaining cost budget. An exposure rejection means the caller-assigned risk estimate does not fit the configured exposure budget.** Application authorization remains a separate control. Agents that handle both need to inspect the reason and choose the right degradation path. In practice, enforcement decisions often involve both simultaneously — an agent running low on dollar budget *and* approaching its RISK_POINTS ceiling. The patterns compose: downgrade the model (cost) while narrowing tool access (risk), complete what you can (partial completion), and queue the rest (defer). diff --git a/blog/when-coding-agents-press-merge.md b/blog/when-coding-agents-press-merge.md index 952bcb54..400f16c5 100644 --- a/blog/when-coding-agents-press-merge.md +++ b/blog/when-coding-agents-press-merge.md @@ -45,7 +45,7 @@ Two years ago, "coding agent" meant something that edited files in your IDE. By | Claude Code (Auto mode) | Same surface; a Sonnet-based classifier blocks dangerous tool calls | Merge calls are subject to classifier judgment per call | | GitHub Copilot Coding Agent | Open PRs against assigned issues; push iterative commits | Merges through repo policies once a maintainer (or a bot) approves | -Cognition has reported that its own team merged [659 Devin-generated PRs in a single week](https://cognition.ai/blog/how-cognition-uses-devin-to-build-devin) in early 2026, up from a best week of 154 the year before. Separately, in its [2025 performance review](https://cognition.ai/blog/devin-annual-performance-review-2025), Cognition reported Devin's PR merge rate rising from 34% to 67% over the year. Codex Cloud's design assumption is asynchronous parallelism — multiple PRs in flight at once, each task in its own cloud environment, each landing as a separate review request that the team merges through its normal flow. The merge button itself is still the team's, but the cadence at which it gets pressed is increasingly set by the agent's output. The shift is structural: the agent is no longer the contributor handing a diff to a human. The agent is closer to a team of contributors funneling work into the same merge bottleneck. +Cognition has reported that its own team merged [659 Devin-generated PRs in a single week](https://cognition.com/blog/how-cognition-uses-devin-to-build-devin) in early 2026, up from a best week of 154 the year before. Separately, in its [2025 performance review](https://cognition.com/blog/devin-annual-performance-review-2025), Cognition reported Devin's PR merge rate rising from 34% to 67% over the year. Codex Cloud's design assumption is asynchronous parallelism — multiple PRs in flight at once, each task in its own cloud environment, each landing as a separate review request that the team merges through its normal flow. The merge button itself is still the team's, but the cadence at which it gets pressed is increasingly set by the agent's output. The shift is structural: the agent is no longer the contributor handing a diff to a human. The agent is closer to a team of contributors funneling work into the same merge bottleneck. That is a different control problem than the IDE-file-write era. The corpus has covered the agent's spending surface ([Budget Limits for Claude Code, Cursor, and Windsurf via MCP](/blog/claude-code-cursor-windsurf-budget-limits-mcp)) and the credential surface ([Coding Agents Need Runtime Authority](/concepts/coding-agents-need-runtime-budget-authority), [Least-Privilege API Keys for AI Agents](/blog/least-privilege-api-keys-for-ai-agents)). The merge surface has not had its own treatment. diff --git a/blog/where-did-my-tokens-go-debugging-agent-spend.md b/blog/where-did-my-tokens-go-debugging-agent-spend.md index d8076d9e..1ce1b040 100644 --- a/blog/where-did-my-tokens-go-debugging-agent-spend.md +++ b/blog/where-did-my-tokens-go-debugging-agent-spend.md @@ -3,7 +3,7 @@ title: "Debugging AI Agent Spend in Production" date: 2026-04-17 author: Albert Mavashev tags: [engineering, debugging, observability, agents, cost-attribution, runtime-authority] -description: "Debug AI agent spend with scope paths, event streams, and correlation IDs, then trace unexpected cost increases across tenants, runs, and providers reliably." +description: "Debug AI agent spend with scope paths, event streams, and trace IDs, then trace unexpected cost increases across tenants, workflows, and providers reliably." blog: true sidebar: false featured: false @@ -19,15 +19,15 @@ head: The bill just tripled. Your agents aren't doing anything new. You open the LLM proxy dashboard and see the total — yes, token usage is up — but the dashboard only shows you *how much*, not *who, where, or why*. An engineer sitting in front of that dashboard at 9am on a Tuesday has maybe thirty minutes to figure out which tool call cost the most before finance escalates. -This is LLM token attribution at production scale — debugging AI agent spend when the proxy can't tell you *which agent, which workflow, which tool call* drove the spike. This post is about the data model you actually need to answer the question. Not which observability tool to buy — which **fields on which events**, and which **balance queries**, let you drill from "total spend tripled" down to "this workflow, this agent, this tool call, this API key, this correlation ID." The answer in Cycles is three primitives captured at enforcement time — **scope path, actor, correlation ID** — surfaced through the event stream and the balance API. Everything else is filtering. +This is LLM token attribution at production scale — debugging AI agent spend when the proxy can't tell you *which agent, which workflow, which tool call* drove the spike. This post is about the data model you actually need to answer the question. Not which observability tool to buy — which **fields on which events**, and which **balance queries**, let you drill from "total spend tripled" down to "this workflow, this agent, this tool call, this API key, this trace ID." The answer in Cycles is three primitives captured at enforcement time — **scope path, actor, trace ID** — surfaced through the event stream and the balance API. Everything else is filtering. -Cycles narrows the suspect set structurally; exact tool-call reconstruction still depends on your own application logs keyed by correlation ID. The post covers both halves. +Cycles narrows the suspect set structurally; exact tool-call reconstruction still depends on your own application logs keyed by trace ID. The post covers both halves. ## Why LLM-proxy observability stops short -Proxy tools (LiteLLM, Helicone, OpenRouter, Langfuse) sit between your code and the model provider. They see *every request*, so they can tell you totals, per-model breakdowns, per-API-key breakdowns. That's genuinely useful for a single-agent app. +When all model traffic is routed through them, proxy and gateway tools can report totals and product-dependent breakdowns by model, key, user, or custom metadata. That is useful, but it is a different data boundary from Cycles scopes and application tool execution. It stops being useful the moment your system has any of: @@ -43,25 +43,25 @@ That's not a tool problem. That's a **data-model problem**. The tree has to be c ## The three primitives for LLM token attribution -Cycles is designed to carry three structural attribution fields through the reserve/commit flow and onto events when your integration provides them: +Cycles is designed to carry three structural attribution fields through the reserve-commit flow and onto events when your integration provides them: -**`scope` (a path).** Not a flat label. A path like `tenant:acme-corp/workspace:prod/app:support-bot/workflow:handoff/agent:planner/toolset:web-search`. Six levels deep is the current shape; the depth is a design choice that makes prefix queries cheap. You filter at any depth: "everything under `tenant:acme-corp/workspace:prod`" or "just this one agent instance." +**`scope` (a path).** Not a flat label. A path like `tenant:acme-corp/workspace:prod/app:support-bot/workflow:handoff/agent:planner/toolset:web-search`. Six standard levels are available, and ordered paths make prefix queries cheap. You can filter at any populated depth: "everything under `tenant:acme-corp/workspace:prod`" or "just this agent segment." **`actor` (the principal).** Type, key ID, source IP. This is who/what initiated the action at the API boundary — an API key, a service account, a system process. Two agents sharing a budget scope can still be separated if they arrive through distinct actors or correlation paths, so "who spent the money" is separable from "whose budget paid for it." -**`correlation_id` and `request_id`.** The trace primitives. Cycles supports `correlation_id` and `request_id` on events (both are optional fields in the schema, populated when the caller provides them). When your integration threads them through consistently — on the reserve, on the commit, on the LLM request, in your own logs — they become the pivot from "this event happened" to "this exact request in your code" in one hop. +**`trace_id` and `request_id`.** The trace primitives. Send the same valid W3C `traceparent` or `X-Cycles-Trace-Id` on related reserve, provider, and commit calls, and record the resulting trace ID in application logs. Each Cycles HTTP request also has its own request ID. `correlation_id` is a separate, server-managed event field: the protocol defines deterministic runtime clusters, but the current reference runtime does not populate it on the six event paths below. -These three fields are what make the event stream navigable — a structural commitment that *everything* that affects spend carries enough metadata to find its origin. +Together, these fields make implemented event paths navigable. Exact tool-call reconstruction still depends on the integration propagating correlation identifiers into its own logs. ## What the event stream tells you today (v0.1.25) -Cycles' protocol defined [40 event types across six categories](/protocol/event-payloads-reference) as of post date — the current Admin API `EventType` enum registers **47 across seven categories** with the `webhook` category added later (see the live [Event Payloads Reference](/protocol/event-payloads-reference) for the per-category breakdown). **At publication, the runtime spend-debugging surface this article focuses on emitted six events**; the rest were protocol-defined and scheduled for later admin-service and runtime hooks. This section is about the six that fired then and what each one tells you about spend. +The current Admin API `EventType` enum registers **51 event types across seven categories**; only a subset is emitted by current reference-service paths, and the runtime also publishes several additive lifecycle payloads. See the live [Event Payloads Reference](/protocol/event-payloads-reference) for per-event status. This section focuses on six runtime signals that are useful for spend investigations. -These are signal events — they fire on budget-health *transitions*. They are not a per-debit ledger. For per-debit spend numbers you query the balance API (covered in the next section); the events tell you when something *changed*. +These are signal events, not a per-commit spend ledger. The three budget events fire on ledger changes; denial, overage, and expiry describe reservation decisions or lifecycle outcomes. For current quantities, query the balance API (covered in the next section). -### 1. `reservation.denied` — who got blocked, and why +### 1. `reservation.denied` — what shadow evaluation would block -Fires when a reserve or decide request returns DENY. Today's payload is deliberately small: +Fires when a reservation request with `dry_run: true` or `/v1/decide` returns `DENY`. A live reservation denial is an HTTP error such as `409 BUDGET_EXCEEDED`; the current controller does not emit this event for that exception path. ```json { @@ -70,30 +70,45 @@ Fires when a reserve or decide request returns DENY. Today's payload is delibera "actor": {"type": "api_key", "key_id": "key_abc123"}, "data": { "scope": "tenant:acme-corp/workspace:prod/workflow:support", + "unit": "USD_MICROCENTS", "reason_code": "BUDGET_EXCEEDED", - "requested_amount": 500000 + "requested_amount": 500000, + "remaining": 100000, + "action": {"kind": "llm.chat", "name": "support-reply"}, + "subject": { + "tenant": "acme-corp", + "workspace": "prod", + "workflow": "support" + } }, - "correlation_id": "req_0af3" + "trace_id": "0af7651916cd43dd8448eb211c80319c" } ``` -`reason_code` values include `BUDGET_EXCEEDED`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, `BUDGET_FROZEN`, `BUDGET_CLOSED`. When you're debugging a spend spike, a denial stream is the *inverse* signal — it's spend that didn't happen. But if denials are up on `scope X` and the total bill is also up, the implication is a client retrying after denials and somehow committing under a different scope. +`reason_code` values include `BUDGET_EXCEEDED`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, `BUDGET_FROZEN`, `BUDGET_CLOSED`, and `TENANT_CLOSED`. A denied reservation dry run derives `remaining` from returned balances; `/v1/decide` currently omits it. This stream answers which shadow evaluations would be blocked. For live denial volume, use application errors or `cycles_reservations_reserve_total{decision="DENY"}`. ### 2. `reservation.commit_overage` — the estimator is wrong -Fires when a commit's actual cost exceeded the original estimate. The current emission is minimal on purpose — the full payload is still being wired up — so what you get today is: +Fires when a commit's requested actual cost exceeds the original estimate. The current runtime sets the envelope scope and populates the full eight-field payload: ```json { "event_type": "reservation.commit_overage", + "scope": "tenant:acme-corp/workspace:prod/workflow:support", "data": { "reservation_id": "res_a1b2c3d4", - "actual_amount": 480000 + "scope": "tenant:acme-corp/workspace:prod/workflow:support", + "unit": "USD_MICROCENTS", + "estimated_amount": 400000, + "actual_amount": 480000, + "overage": 80000, + "overage_policy": "ALLOW_IF_AVAILABLE", + "debt_incurred": 0 } } ``` -Note: today's emission does *not* set the envelope `scope` field on this event, so a scope-filtered webhook subscription won't receive it. Take all `commit_overage` events and correlate `reservation_id` back to your reserve-request logs to get the scope; the full in-payload shape (`scope`, `unit`, `estimated_amount`, `overage`, `overage_policy`, `debt_incurred`) is protocol-defined and will populate in future releases. The debugging value today is volume: a rising commit_overage rate for a tenant is an estimator-drift signal, covered in depth in [estimate drift: the silent killer of enforcement](/blog/estimate-drift-silent-killer-of-enforcement). +The debugging value is both concentration and magnitude: group by scope, then inspect `estimated_amount`, `actual_amount`, `overage`, and `overage_policy`. A rising overage rate for a workload is an estimator-drift signal, covered in depth in [estimate drift: the silent killer of enforcement](/blog/estimate-drift-silent-killer-of-enforcement). ### 3. `reservation.expired` — reserved but never committed @@ -115,15 +130,15 @@ Fires from the background expiry sweeper. This one has the full payload today: } ``` -A healthy system produces a trickle of these (a client crash, a timeout). A flood of them from one scope means a specific workload has broken commit-path plumbing — reservations are being made but the commit call never lands. That reservation-to-commit ratio is the signal. Most LLM proxies can't show it at all, because they only see completed requests. +An occasional expiry can follow a client crash, timeout, or deliberately abandoned reservation. A workload-specific increase means reservations are being made but neither commit nor release reaches the server before TTL expiry. That reservation-to-terminal-operation ratio is the signal. A proxy that only sees completed upstream requests cannot reconstruct this client-side lifecycle on its own. ### 4. `budget.exhausted` — remaining hit zero -Fires when a budget's remaining reaches zero. `data` is `null`; the envelope carries the context — `scope`, `tenant_id`, `actor`, `timestamp` identify which budget exhausted and what triggered it. Query the budget's current state via the balance API for the actual numbers. This event is the "something just maxed out" alarm; the interesting follow-up is "which scope, and what was the preceding burn rate." +Fires once when a budget's remaining transitions from above zero to zero. The payload contains `scope`, `unit`, `threshold: 1.0`, `utilization`, `allocated`, `remaining: 0`, `spent`, `reserved`, and `direction: rising`; the envelope adds tenant, actor, and request context. Query the balance API before remediation because the ledger may have changed since emission. ### 5. `budget.over_limit_entered` — debt crossed the ceiling -Fires when debt on an `ALLOW_WITH_OVERDRAFT` budget exceeds the configured overdraft limit. Full payload today: +Fires when debt on an `ALLOW_WITH_OVERDRAFT` budget enters over-limit state. Full payload today: ```json { @@ -141,9 +156,9 @@ Fires when debt on an `ALLOW_WITH_OVERDRAFT` budget exceeds the configured overd If this fires in production, debt is actively accumulating on a scope faster than expected. Combined with `budget.debt_incurred` below, it's the "the agents are eating the overdraft" signal. -### 6. `budget.debt_incurred` — a commit created new debt +### 6. `budget.debt_incurred` — an operation created new debt -Fires when a commit goes through under `ALLOW_WITH_OVERDRAFT` policy and creates new debt (actual cost exceeded remaining budget). Today's emission populates: +Fires when a reservation commit or direct debit under `ALLOW_WITH_OVERDRAFT` creates new debt. Today's emission populates: ```json { @@ -151,13 +166,16 @@ Fires when a commit goes through under `ALLOW_WITH_OVERDRAFT` policy and creates "data": { "scope": "tenant:acme-corp/workspace:prod", "unit": "USD_MICROCENTS", + "reservation_id": "res_a1b2c3d4", + "debt_incurred": 250000, "total_debt": 750000, - "overdraft_limit": 1000000 + "overdraft_limit": 1000000, + "overage_policy": "ALLOW_WITH_OVERDRAFT" } } ``` -A stream of these on one scope means the overage policy is routinely borrowing — which is fine by design, but the *rate* is a spend signal. If `total_debt` on a scope is climbing faster than it gets repaid, the long-run picture is a budget that's structurally under-allocated for its workload. +`reservation_id` is omitted when a direct debit, rather than a reservation commit, caused the debt. A stream of these on one scope means the overage policy is routinely borrowing — which can be intentional, but the *rate* is a spend signal. If `total_debt` climbs faster than it gets repaid, the budget may be structurally under-allocated or the workload may be behaving unexpectedly. ## What the balance API fills in @@ -168,9 +186,9 @@ curl -s "http://localhost:7878/v1/balances?tenant=acme&workspace=prod&include_ch -H "X-Cycles-API-Key: $KEY" ``` -Run that at minute-zero and again at minute-sixty on the same subtree and subtract — that's spend-in-the-hour by scope, from ground truth, no event correlation required. (The admin plane on port 7979 has a separate `GET /v1/admin/budgets?scope_prefix=...&unit=...` query; that's for operator/governance workflows and uses a different parameter shape. This post stays on the runtime plane.) +Run that at two points on the same subtree and subtract `spent` to calculate interval consumption, provided no `RESET_SPENT` operation occurs between snapshots. (The admin plane on port 7979 has a separate `GET /v1/admin/budgets?scope_prefix=...&unit=...` query; that's for operator/governance workflows and uses a different parameter shape. This post stays on the runtime plane.) -The division of labor is clean: **events tell you transitions, the balance API tells you quantities.** A mature monitoring setup uses both — events to wake you up, balances to answer "how much." +The division of labor is clean: **events tell you decisions and lifecycle changes; the balance API tells you current quantities.** A mature monitoring setup uses both — events to wake you up, balances to answer "how much." ## Four debugging moves that work today @@ -178,23 +196,23 @@ Concretely — what do you type when the bill is 3× and you have thirty minutes **Move 1: Top-N by scope, via runtime balances.** `GET /v1/balances?tenant=X&include_children=true` on the runtime plane (add `workspace`, `app`, etc. to narrow); sort the returned scopes by `spent` descending. The top child scope is the first candidate. If the top is a tenant/workspace you didn't expect to spike, the investigation is now "what changed for that customer." If it's a single workflow or agent scope, drill deeper. This is the first move you run — and it's a balance query, not an event subscription, because today's event stream doesn't carry per-debit amounts. -**Move 2: Health transitions in the window.** Subscribe to or query stored events for `budget.exhausted`, `budget.over_limit_entered`, and `budget.debt_incurred` in the last N hours. Any scope that fired these is at or near a budget boundary — the bill probably won't be news, but *which scope* and *when* will be. Cross-reference with `reservation.denied` volume: if denials and debt both spiked on the same scope, a retry loop is eating the overdraft. +**Move 2: Health changes in the window.** Subscribe to or query stored events for `budget.exhausted`, `budget.over_limit_entered`, and `budget.debt_incurred` in the last N hours. Any scope that fired these reached a budget boundary or created debt. Cross-reference live application errors and `cycles_reservations_reserve_total{decision="DENY"}` to find enforcement retries. Use `reservation.denied` separately to analyze dry-run or `/v1/decide` calibration behavior. **Move 3: Estimator drift.** Pull `reservation.commit_overage` volume for the window. A rising rate on a specific client or reservation-id prefix is the estimator on that scope over-committing. The fix is recalibration — not capacity. This is a different root cause than "traffic genuinely increased," and you will misdiagnose it if you only look at totals. -**Move 4: Correlation ID trace.** Pick the expensive scope from the balance API, then grab a `correlation_id` from the event stream or your own commit logs and pivot into application logs. You now have the exact function call, prompt, tool invocation. This is the step that closes the loop — the balance API and event stream tell you *which scope*, but `correlation_id` is what tells you *what code* ran. The balance API gives you ledger quantities, not commit-level records; threading a correlation ID through on every reserve/commit call is what makes this move work. +**Move 4: Trace ID join.** Pick the expensive scope from the balance API, then grab a `trace_id` from the event stream or your own request logs and pivot into application logs. This is the step that closes the loop — the balance API and event stream tell you *which scope*, but the propagated trace ID tells you *what code* ran. The balance API gives you ledger quantities, not commit-level records; carrying the same trace context through each related reserve, provider, and settlement call is what makes this move work. Without step 4, the first three tell you where to look but not what to fix. With step 4, you end with a diff in a specific file. ## What the admin dashboard does on top of this -The [Cycles admin dashboard](/quickstart/deploying-the-cycles-dashboard) is a UI over the same event stream and balance APIs. It doesn't see anything the APIs don't see. What it adds is the filter/group/sort operations prebuilt — "show me spend by scope for the last 24 hours" is a page, not a balance sweep you script. For a one-off incident at 9am, that matters. The APIs are authoritative; the dashboard is fast. +The [Cycles admin dashboard](/quickstart/deploying-the-cycles-dashboard) is a UI over admin budgets, events, and related APIs. It does not reconstruct commit-level history that those APIs do not expose. What it adds is prebuilt filtering, sorting, and current-state inspection for budgets and events. The APIs remain authoritative; the dashboard shortens routine triage. The reason to publish the stream and balance APIs as the primary interfaces — not the dashboard — is that every team eventually wants to pipe this data into their own SIEM, data warehouse, or oncall system. If the dashboard is the only view, that integration is a scraping project. If the APIs are, it's a webhook subscription plus a cron. ## What's on the roadmap -A few protocol-defined events would make the debugging story richer when they come online — `budget.debited` for a per-commit ledger, `budget.burn_rate_anomaly` as a passive spike detector, `reservation.denial_rate_spike` and `reservation.expiry_rate_spike` for rate anomalies, and the planned `budget.threshold_crossed` for proactive warnings at configurable utilization levels. The [event payloads reference](/protocol/event-payloads-reference) tracks which of these are live; the four moves above are structured so they keep working as more of the stream lights up. +A few protocol-defined events would make runtime debugging richer when they come online — `budget.burn_rate_anomaly` as a passive spike detector, `reservation.denial_rate_spike` and `reservation.expiry_rate_spike` for rate anomalies, and `budget.threshold_crossed` for configurable pre-exhaustion warnings. The admin server currently emits `budget.debited` for an operator `DEBIT` funding operation; that is not a per-commit runtime ledger. The [event payloads reference](/protocol/event-payloads-reference) tracks each event's status. ## The non-goal: cost *prediction* @@ -204,7 +222,7 @@ That distinction matters because **you cannot attribute spend you didn't structu ## Bottom line -When the bill surprises you, the question you're asking is structural: which part of the system produced this spend, who initiated it, and what code ran? The answer needs three fields captured at the moment of authority — scope path, actor, correlation ID — on every event the system emits and every balance it records. Cycles captures those. The six spend-debugging events above signal the *transitions*, the balance API answers *how much*, and `correlation_id` closes the loop back to code. +When the bill surprises you, the question you're asking is structural: which part of the system produced this spend, who initiated it, and what code ran? Cycles supplies scope and actor context on implemented runtime paths and propagates trace context when your integration provides it. The six signals above identify decisions and lifecycle changes, the balance API answers *how much*, and application logs keyed by `trace_id` close the loop back to code. The observability tools that only see totals aren't wrong. They're answering a different question. Attribution is a data-model commitment you make upstream, not a chart you add downstream. diff --git a/blog/why-ai-agent-tests-pass-but-production-keeps-failing.md b/blog/why-ai-agent-tests-pass-but-production-keeps-failing.md index 62ad57a5..cf38a640 100644 --- a/blog/why-ai-agent-tests-pass-but-production-keeps-failing.md +++ b/blog/why-ai-agent-tests-pass-but-production-keeps-failing.md @@ -52,7 +52,7 @@ Evals only see the final output. They cannot inspect whether intermediate steps ### Blind Spot 2: Evals don't run at production scale -Running evals is expensive. [One data team reported](https://www.montecarlodata.com/blog-ai-agent-evaluation/) that their evaluation costs reached **10x the cost of running the agent itself**. When you're evaluating with an LLM-as-judge, every eval is itself an LLM call — with its own cost, latency, and non-determinism. +Running evals is expensive. [One data team reported](https://montecarlo.ai/blog-ai-agent-evaluation) that their evaluation costs reached **10x the cost of running the agent itself**. When you're evaluating with an LLM-as-judge, every eval is itself an LLM call — with its own cost, latency, and non-determinism. The math doesn't work at scale. The following estimates are illustrative, based on typical per-call LLM pricing: @@ -70,7 +70,7 @@ In traditional software, when you fix a bug, you add a regression test. The test The industry is scrambling to solve this. [Docker launched Cagent](https://www.infoq.com/news/2026/01/cagent-testing/) in January 2026, using record-and-replay to make agent tests deterministic. [TestMu AI launched an agent-to-agent testing platform](https://www.globenewswire.com/news-release/2026/03/24/3261494/0/en/TestMu-AI-Unveils-Major-Enhancements-to-AI-Agent-to-Agent-Testing-Platform-Empowering-Organizations-to-Validate-AI-Agents-Across-Real-World-Scenarios.html) in March 2026 with adversarial evaluators. [Anthropic published guidance](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents) emphasizing that "teams without evals get bogged down in reactive loops — fixing one failure, creating another." -But every solution adds complexity, cost, and another non-deterministic layer to test. The eval-for-the-eval problem is real: one team [reported having to test their tests](https://www.montecarlodata.com/blog-ai-agent-evaluation/) — running each eval multiple times and discarding results when the delta was too large. +But every solution adds complexity, cost, and another non-deterministic layer to test. The eval-for-the-eval problem is real: one team [reported having to test their tests](https://montecarlo.ai/blog-ai-agent-evaluation) — running each eval multiple times and discarding results when the delta was too large. ## The Gap Evals Can't Close @@ -82,9 +82,9 @@ Your eval suite tests step 1 in isolation and it passes. Tests step 2 in isolati ## Enforcement: The Deterministic Layer for Non-Deterministic Systems -The fix isn't better evals. It's a different architectural layer — one that is deterministic by design and can run on every request rather than a sampled subset. +Better evals are still necessary, but they do not replace deterministic controls on the execution path. -Instead of evaluating outputs after the fact, you enforce constraints *before every action*. Every tool call, every LLM invocation, every side effect passes through a checkpoint that verifies the action is authorized, within budget, and structurally valid before it executes. +For each protected tool call, LLM invocation, or side effect, the host can require application authorization and argument validation plus a successful Cycles budget reservation before execution. These are complementary checks: Cycles evaluates the submitted unit, amount, and scope; it does not decide whether a tool or its arguments are permitted. This is [runtime authority](/blog/what-is-runtime-authority-for-ai-agents). Here's why it addresses problems that evals can't: @@ -92,33 +92,34 @@ This is [runtime authority](/blog/what-is-runtime-authority-for-ai-agents). Here | | Evaluation (evals) | Enforcement ([runtime authority](/glossary#runtime-authority)) | |---|---|---| -| **When** | After execution, on sampled traffic | Before every action, on all traffic | -| **What it checks** | Output quality (semantic) | Behavioral correctness (structural) | -| **Cost per check** | ~$0.01-$0.10 (est. LLM-as-judge call) | Fraction of a cent (est. policy lookup) | -| **Deterministic** | No (LLM judge varies run-to-run) | Yes (policy rules produce same result) | -| **Catches loops** | No (agent never reaches the eval) | Yes (budget cap stops iteration N+1) | -| **Catches fabrication** | Sometimes (if the judge notices) | Flags anomalies (near-zero cost on a step that should have external API cost is a strong signal, though not proof — caching or free-tier tools can also produce low-cost commits) | -| **Catches scope violations** | No (eval sees output, not action) | Yes (unauthorized action kind blocked at reserve) | +| **When** | Often after execution or on sampled traffic | Before each action placed behind the control | +| **What it checks** | Output quality (semantic) | Configured structural rules; Cycles checks submitted budget dimensions | +| **Cost per check** | Often an additional model or evaluator call | A runtime service call whose latency and infrastructure cost depend on deployment | +| **Deterministic** | An LLM judge can vary run-to-run | Deterministic for the same rule, budget state, and request | +| **Catches loops** | May detect them in traces or offline evaluation | A cumulative budget can reject the first protected iteration that exceeds its limit | +| **Catches fabrication** | Sometimes, if the evaluator detects it | Not by itself; settlement anomalies can be an investigation signal when joined to application telemetry | +| **Catches scope violations** | Only if the evaluation covers them | Cycles contains submitted spend by budget scope; application authorization must block disallowed actions | Enforcement doesn't replace evals. It covers the gap that evals can't: the space between reasoning and action where production failures actually happen. ## How Reserve-Commit Turns Agent Runs into Testable Sequences -The [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) creates something no eval framework provides: a **deterministic, inspectable record of every action an agent attempted and what actually happened**. +The [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) creates a **deterministic, inspectable record of persisted budget holds and settlement** for actions the host instruments. ``` -1. Reserve → Agent requests permission + budget before acting +1. Reserve → Host requests a budget hold before acting 2. Execute → Agent performs the action -3. Commit → Agent reports actual outcome; unused budget released +3. Commit → Host reports the actual charge; unused budget released ``` -Each cycle produces structured data: what was requested, what was allowed, what was executed, and what it cost. This creates three capabilities that agent testing currently lacks: +Each persisted cycle records the submitted scope and action context, what amount was reserved, and how the hold settled. It does not prove that the action executed, capture its arguments or result, or replace application telemetry. When the host propagates the same trace context across related Cycles calls and retains the reservation ID in its own logs, the combined data enables useful structural checks: ### 1. Structural anomaly detection without LLM judges -Every reserve-commit pair produces a cost and latency signature. Deviations from expected patterns flag problems automatically — no expensive LLM judge required: +Every reserve-commit pair produces a budget-usage signature. Application telemetry can add latency and outcome data. Deviations from expected patterns can flag cases for investigation without first requiring an LLM judge: ```jsonc +// Illustrative application-derived summaries, not Cycles API response shapes. // Expected: research agent calls 3 APIs, ~$0.45 total // Actual: 3 reserves, 3 commits, costs match estimates { "run_summary": { "steps": 3, "total_cost": "$0.47", "status": "normal" } } @@ -133,15 +134,15 @@ Every reserve-commit pair produces a cost and latency signature. Deviations from { "run_summary": { "steps": 200, "status": "budget_exhausted" } } ``` -These signals are deterministic, generated automatically, and catch structural failures that semantic evals miss entirely. For the full taxonomy of cost-anomaly signals, see [the cost-as-reliability-signal pattern](/blog/ai-agent-silent-failures-why-200-ok-is-the-most-dangerous-response#the-broader-pattern-budget-as-a-reliability-signal). +These summaries can be computed deterministically from correlated budget and application records. Cycles does not generate the illustrated run summaries or determine that a low settlement means fabrication. For a broader taxonomy of cost-anomaly signals, see [the cost-as-reliability-signal pattern](/blog/ai-agent-silent-failures-why-200-ok-is-the-most-dangerous-response#the-broader-pattern-budget-as-a-reliability-signal). ### 2. Scope-based behavioral boundaries -Where evals ask "was the output good?", enforcement asks "was the agent allowed to do this?" — a fundamentally different and complementary question. +Where evals ask "was the output good?", an application authorization layer asks "may this principal use this tool with these arguments?" Cycles adds "does the configured budget cover the submitted amount and scope?" These are different and complementary questions. -A coding agent authorized to read test files and write source files is blocked at the [reservation](/glossary#reservation) step if it tries to modify tests. A support agent scoped to read customer records is blocked before it can issue a refund above its authority tier. A research agent limited to 10 API calls per run is stopped at call 11. +A coding agent can be blocked by application policy if it tries to modify tests. A support agent can be blocked by an authorization rule before issuing a refund above its tier. Separately, a research agent whose host reserves one `CREDIT` for every API call against a 10-credit budget has its 11th reservation rejected. Cycles does not inspect file paths, refund arguments, or tool permissions. -These aren't heuristics. They're deterministic rules evaluated before execution. The agent's non-deterministic reasoning produces a deterministic allow/deny decision at the enforcement layer. For concrete [action authority](/glossary#action-authority) patterns, see [AI Agent Action Control: Hard Limits on Side Effects](/blog/ai-agent-action-control-hard-limits-side-effects). +These can be deterministic rules evaluated before execution, provided the host routes every protected call through them and enforces each result. For concrete [action authority](/glossary#action-authority) patterns and the boundary between authorization and exposure budgets, see [AI Agent Action Control: Hard Limits on Side Effects](/blog/ai-agent-action-control-hard-limits-side-effects). ### 3. Integration testing via budget topology @@ -161,19 +162,19 @@ Enforcement isn't a replacement for evals. It's the layer that covers the traffi The practical stack: -- **Enforcement** catches structural failures on all traffic: loops, scope violations, budget overruns, anomalous cost patterns. Cost per check is estimated at a fraction of a cent. +- **Enforcement** can bound loops and cumulative usage on the protected traffic routed through it. Application policy handles permissions and argument-level scope; correlated telemetry can surface cost anomalies. Measure runtime cost and latency in your deployment. - **Evals** catch semantic failures on sampled traffic: wrong answers, poor tone, factual errors. Cost: high but necessary for quality signal. - **Observability** provides forensic data for debugging. Cost: moderate. Essential for tuning both other layers. -Most teams have observability. About half have evals. Far fewer teams have enforcement than either observability or evals. That's why tests pass and production fails. +Teams need all three layers in proportions that match their risks; passing semantic tests alone does not prove that production execution is bounded. ## What To Do This Week -1. **Start with [shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production)** — Run enforcement alongside your existing agents without blocking anything. Collect per-step cost data. Shadow mode often surfaces structural failures quickly that evals never caught — and it covers all traffic at a fraction of eval cost. +1. **Start with [shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production)** — Send dry-run reservations alongside your existing agents without blocking on the returned decision. Dry run creates no reservation or balance mutation. The current server emits `reservation.denied` for denied evaluations, but retain all responses in your application and join them to actual usage data. 2. **Set per-run budgets on your riskiest workflow** — Pick the agent where a failure has real consequences. Add a [budget ceiling](/blog/ai-agent-budget-control-enforce-hard-spend-limits). Review which runs would have been blocked. If the answer is "the ones that looped" — you've found the gap. -3. **Add action scoping to one sensitive operation** — Any agent that writes data, sends communications, or modifies infrastructure should require explicit [action authority](/blog/ai-agent-action-control-hard-limits-side-effects). The enforcement gates the action; the eval validates the output. Both layers, working together. +3. **Add action scoping to one sensitive operation** — Any agent that writes data, sends communications, or modifies infrastructure should require explicit application [action authority](/blog/ai-agent-action-control-hard-limits-side-effects). The authorization layer gates the tool and arguments; Cycles can separately bound caller-assigned cumulative exposure; an eval can assess output quality. 4. **[Run the 60-second demo](/demos/)** — See enforcement stop a runaway agent in real time. Then compare that to your eval pipeline catching the same failure — hours later, on a sampled subset, if at all. diff --git a/blog/why-i-am-building-cycles.md b/blog/why-i-am-building-cycles.md index 7fc12c05..d084888a 100644 --- a/blog/why-i-am-building-cycles.md +++ b/blog/why-i-am-building-cycles.md @@ -47,9 +47,9 @@ When I started sketching what became Cycles, I kept coming back to three princip **1. Enforcement must be atomic.** In enterprise middleware, a half-applied policy is worse than no policy. If you reserve capacity for a transaction, that [reservation](/glossary#reservation) must be atomic—either the full amount is locked across applicable ledgers or none of it is. The Cycles reserve-commit lifecycle closes the shared-ledger [time-of-check-to-time-of-use](https://dev.to/amavashev/your-ai-agent-budget-check-has-a-race-condition-33ei) window between checking and reserving. It does not eliminate races elsewhere in the application or external side effect. -**2. Authority should attenuate, not propagate.** In middleware, a message broker does not have to grant downstream systems the same permissions as the originating system. An orchestrator can apply that principle to agent delegation by provisioning a smaller child scope budget and exposing a narrower tool set at each hop. Cycles enforces the explicitly provisioned sub-budget; the orchestrator owns tool permissions and depth limits. +**2. Authority should attenuate, not propagate.** In middleware, a message broker does not have to grant downstream systems the same permissions as the originating system. An orchestrator can apply that principle to agent delegation by provisioning a smaller child-scope ledger and exposing a narrower tool set at each hop. Cycles enforces submitted calls against the explicitly provisioned ledger; the orchestrator owns tool permissions and depth limits. -**3. Control must be structural, not semantic.** You can't rely on an LLM to respect a system prompt that says "don't spend more than $10." That's a semantic control — a suggestion to a probabilistic system. Structural controls operate outside the LLM, at the infrastructure layer, and enforce boundaries deterministically. One is a hope. The other is an engineering guarantee. +**3. Control must be structural, not semantic.** You can't rely on an LLM to respect a system prompt that says "don't spend more than $10." That's a semantic control — a suggestion to a probabilistic system. Structural controls operate outside the LLM, at the infrastructure layer. When every protected path is required to obtain a reservation and honor the result, the submitted budget check is deterministic; callers that bypass that boundary remain outside the guarantee. These aren't novel ideas. They're battle-tested patterns from decades of distributed systems engineering. What's novel is applying them to autonomous AI agents — where the "messages" are tool calls, the "brokers" are agent orchestrators, and the "transactions" are LLM inference chains that can spawn arbitrary sub-tasks. diff --git a/blog/why-multi-agent-systems-fail-87-percent-cost-of-every-coordination-breakdown.md b/blog/why-multi-agent-systems-fail-87-percent-cost-of-every-coordination-breakdown.md index e8cb3a81..968649b1 100644 --- a/blog/why-multi-agent-systems-fail-87-percent-cost-of-every-coordination-breakdown.md +++ b/blog/why-multi-agent-systems-fail-87-percent-cost-of-every-coordination-breakdown.md @@ -1,9 +1,9 @@ --- -title: "Multi-Agent Systems Fail 87% — What It Costs" +title: "MAST: Multi-Agent Failure Rates Reached 86.7%" date: 2026-03-29 author: Cycles Team tags: [multi-agent, failures, costs, coordination, production, MAST, runtime-authority, engineering] -description: "UC Berkeley's MAST taxonomy found 14 failure modes across 1,600+ multi-agent traces with 41–87% failure rates. See how scoped budgets contain cascading spend." +description: "MAST analyzed 1,600+ traces across seven multi-agent frameworks and identified 14 failure modes. Model how scoped budgets can contain failure costs safely." blog: true sidebar: false head: @@ -12,7 +12,7 @@ head: content: multi-agent system failures, MAST taxonomy, AI agent coordination cost, cascading agent failures, scoped budgets, runtime authority --- -# Multi-Agent Systems Fail Up to 87% of the Time — Here's What Each Failure Actually Costs +# MAST: Selected Multi-Agent Failure Rates Reached 86.7% > **Part of: [Multi-Tenant AI Operations Reference](/guides/multi-tenant-operations)** — the full pillar covering scope hierarchy, per-tenant enforcement, multi-agent coordination, tenant lifecycle, and identity. @@ -22,11 +22,11 @@ The published MAST and SEMAP literature explains how multi-agent systems fail, b -That's the gap between failure *rate* research and failure *cost* research. UC Berkeley's [MAST taxonomy](https://arxiv.org/abs/2503.13657) — the first systematic study of multi-agent LLM failures — analyzed 1,600+ execution traces across seven frameworks (taxonomy developed from an initial 150-trace subset, then expanded to the full MAST-Data corpus in v3 of the paper) and found failure rates ranging from 41% to 86.7%. The taxonomy identifies 14 distinct failure modes in three categories. It tells you *how* multi-agent systems break. It doesn't model what each break costs. +That's the gap between failure *rate* research and failure *cost* research. UC Berkeley's [MAST taxonomy](https://arxiv.org/abs/2503.13657) analyzed 1,600+ execution traces across seven selected frameworks (the taxonomy was developed from an initial 150-trace subset) and reported configuration-level failure rates ranging from 41% to 86.7%. Those rates describe the evaluated framework, model, and task combinations—not all multi-agent systems. The taxonomy identifies 14 distinct failure modes in three categories, but it does not model what each break costs. This post fills that gap. The MAST paper explains how multi-agent systems fail; we model what those failures cost in production. We map each MAST failure category to an illustrative cost signature — the mechanism by which a coordination breakdown becomes a line item on your invoice — and show where [runtime authority](/glossary#runtime-authority) prevents the cost from compounding. The dollar figures in this post are scenario models based on published token pricing, not measured production data. -## The Numbers: 14 Failure Modes, Three Categories, 41–87% Failure Rates +## The Numbers: 14 Modes Across Seven Frameworks The [MAST study](https://sky.cs.berkeley.edu/project/mast/) (Cemri et al., NeurIPS 2025 Spotlight) analyzed traces from MetaGPT, ChatDev, HyperAgent, OpenManus, AppWorld, Magentic-One, and AG2. Across 1,600+ traces, the failures clustered into three categories: @@ -36,15 +36,15 @@ The [MAST study](https://sky.cs.berkeley.edu/project/mast/) (Cemri et al., NeurI | **Inter-agent misalignment** | 32.3% | Wrong assumptions propagated, conversation resets, lost handoff context | | **Task verification failures** | 23.5% | Agent marks task complete when it isn't, skips validation, accepts wrong output | -The failure rates varied by framework — from 41% (best) to 86.7% (worst). Across MAST-Data, failures clustered into all three categories, and framework-level profiles varied by architecture and benchmark. Model choice mattered but didn't eliminate failures: GPT-4o showed substantially fewer specification and misalignment failures than Claude 3.7 Sonnet within MetaGPT, and Qwen2.5-Coder proved substantially more robust than CodeLlama among the open models tested. But even with better-performing model setups, failure rates remained high. The paper's key insight: **better system design improved outcomes more than better models** — up to 15.6% improvement from architectural changes alone. +The evaluated failure rates varied from 41% to 86.7% across framework/model/task configurations. Across MAST-Data, failures clustered into all three categories, and profiles varied by architecture and benchmark. Model choice mattered but did not eliminate failures in the tested settings: GPT-4o showed fewer specification and misalignment failures than Claude 3.7 Sonnet within MetaGPT, while Qwen2.5-Coder was more robust than CodeLlama among the open models tested. The paper also reports improvement headroom from system-design changes; these are benchmark findings, not universal production rates. -A [follow-up study (SEMAP)](https://arxiv.org/html/2510.12120) using protocol-driven agent engineering in software-engineering multi-agent settings demonstrated up to 69.6% reduction in failures on function-level development tasks by enforcing structured communication protocols between agents. The problem isn't the models. It's what happens between them. +A [follow-up study (SEMAP)](https://arxiv.org/html/2510.12120) using protocol-driven agent engineering in software-engineering multi-agent settings reported up to a 69.6% reduction in failures on function-level development tasks. That result supports structured coordination in the evaluated setting; it does not make model quality irrelevant. ## Modeling What Each Failure Category Costs None of the failure taxonomy research measures cost. They measure success/failure as a binary. But in production, failure isn't binary — it's a spectrum from "slightly wrong output" to an unbounded recursive loop. The cost depends on what the system does *after* the failure occurs. -The following cost models are illustrative scenarios based on published per-token pricing (GPT-4o, Claude Sonnet 4). Your numbers will vary by model, context length, and workflow design — but the structural relationships hold: misalignment multiplies cost through the chain, verification failures compound through rework, and design issues generate redundant compute. +The following cost models are illustrative scenarios based on published per-token pricing (GPT-4o and Claude Sonnet 4.6). Your numbers will vary by model, context length, and workflow design. The multipliers below are scenario assumptions, not values measured by MAST. ### Category 1: Inter-Agent Misalignment (32.3% of failures) @@ -115,7 +115,7 @@ When you combine all three failure categories, the cost impact compounds. The fo | Cost per run | $3.50 | $18.40 | $10.95 | | **Daily cost (1,000 runs)** | **$3,500** | **$18,400** | **$10,950** | -At a 50% failure rate — which is *optimistic* relative to MAST's findings of 41–87% — daily spend in this model is 3.1× higher than expected. Over a month, that's $328,500 instead of $105,000. The $223,500 difference represents the cost of coordination failures that your monitoring never attributes to coordination. +At the scenario's assumed 50% failure rate, daily spend is 3.1× higher than the all-healthy baseline. Over a month, that is $328,500 instead of $105,000. The $223,500 difference is an output of this cost model, not a result reported by MAST. The structural problem is that token prices keep falling — but multi-agent architectures multiply consumption faster than prices drop. Every agent added, every retry triggered, every coordination message exchanged adds tokens that don't appear on a "useful work" ledger. @@ -153,7 +153,7 @@ Writer → scope: workflow:research/agent:writer budget: $3.00 If the Researcher misaligns and burns through its $5.00 on a wrong-premise investigation, it gets a `409 BUDGET_EXCEEDED` — not a quiet degradation that poisons the Analyst's work too. The [reserve-commit pattern](/protocol/how-reserve-commit-works-in-cycles) ensures every call is accounted for before execution. -In the model above, the error propagation multiplier drops from 7×+ to 1–2× because the blast radius is contained to the failing agent's scope. +In this illustrative model, limiting the failing agent's ledger reduces the assumed downstream multiplier from 7×+ to 1–2×. That is a scenario input, not a product guarantee; the boundary only covers calls that make the reservation mandatory. ### Task verification failures → Per-run budget caps @@ -167,18 +167,18 @@ Iteration 1-4: $3.50 each → $14.00 committed Iteration 5: Reserve $3.50 → 409 BUDGET_EXCEEDED (budget: $1.00 remaining) ``` -The run fails, but at $15 — not at an unbounded amount. The system can [gracefully degrade](/blog/ai-agent-failures-budget-controls-prevent): fall back to a simpler strategy, escalate to a human, or return a partial result with a cost-exceeded flag. +The fifth estimated call is rejected while $1 remains, so the example stops at $14 of committed usage. Actual settlement can differ from estimates according to the commit overage policy. The host can [gracefully degrade](/blog/ai-agent-failures-budget-controls-prevent): fall back to a simpler strategy, escalate to a human, or return a partial result. ### System design issues → Scope-isolated coordination budgets When agents have overlapping responsibilities and two attempt the same subtask, both consume budget. With scope isolation, you can enforce that each subtask has a single budget allocation: ``` -Subtask "data-extraction" → scope: workflow:research/task:data-extraction +Subtask "data-extraction" → scope: workflow:research-data-extraction budget: $4.00 (shared across all agents claiming this task) ``` -The first agent to reserve against this scope gets the budget. If a second agent attempts the same subtask, the reservation reflects the remaining budget — preventing redundant work from doubling costs. This is the [multi-tenant isolation pattern](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) applied at the task level. +Every agent assigned to that subtask must submit the same workflow value. Atomic reservations then share the ledger's remaining capacity and can cap the combined estimates; they do not deduplicate the task or guarantee that only one agent executes. Use orchestration or an idempotency key tied to the actual side effect for single-execution semantics. ## Scenario Model: Guarded vs. Unguarded at Scale @@ -193,13 +193,13 @@ Returning to the 1,000 runs/day scenario model, here's the illustrative impact o The failure rate doesn't change — that requires better system design, structured protocols, and the kind of architectural work the MAST and SEMAP research recommends. What changes is the cost of each failure. Runtime authority turns unbounded failures into bounded ones. -And the per-agent, per-run budget data creates the attribution layer that observability alone can't provide. When you can see that `agent:analyst` is consuming 4× its expected budget on 30% of runs, you know exactly where to invest in better coordination — which is what the MAST research says matters more than model upgrades. +Per-agent ledgers plus workflow values keyed by run add structural attribution. To calculate claims such as "`agent:analyst` consumed 4× its expected amount on 30% of runs," snapshot balances or export application settlement data; the current balance API is current state rather than a historical analytics warehouse. ## What To Do Now If you're building or operating multi-agent systems: -1. **Measure your actual failure rate.** Run the [MAST annotator](https://github.com/multi-agent-systems-failure-taxonomy/MAST) (`pip install agentdash`) against your traces. The number will be higher than you think. +1. **Measure your actual failure rate.** Run the [MAST annotator](https://github.com/multi-agent-systems-failure-taxonomy/MAST) against your traces and compare failure profiles across representative workloads. 2. **Calculate your failure cost multiplier.** Compare per-run cost for successful vs. failed runs. If the ratio is above 3×, coordination failures are a significant budget line item. @@ -209,18 +209,18 @@ If you're building or operating multi-agent systems: 5. **Use the cost data to prioritize design fixes.** Runtime authority doesn't replace good system design — it gives you the data to invest in the right design improvements. When you see that inter-agent misalignment is driving a disproportionate share of your spend, you know that structured handoff protocols (like SEMAP's approach) are worth building. -The MAST research proves that multi-agent failures are systematic, not random. Runtime authority ensures those systematic failures have a predictable, bounded cost — and generates the per-agent attribution data you need to fix the root causes. +The MAST research provides a systematic taxonomy for recurring multi-agent failure patterns. Correctly integrated runtime budgets can bound submitted spend for those workflows and provide scope-level budget records to join with application traces when investigating root causes. ## Sources Research and data referenced in this post: -- [MAST: Why Do Multi-Agent LLM Systems Fail?](https://arxiv.org/abs/2503.13657) — Cemri et al., UC Berkeley. NeurIPS 2025 Datasets & Benchmarks Track Spotlight. 1,600+ annotated traces (MAST-Data, v3), 14 failure modes, 41–87% failure rates across 7 frameworks. Primary source for failure category percentages (Figure 1: system design 44.2%, inter-agent misalignment 32.3%, task verification 23.5%) +- [MAST: Why Do Multi-Agent LLM Systems Fail?](https://arxiv.org/abs/2503.13657) — Cemri et al., UC Berkeley. More than 1,600 annotated traces across seven selected frameworks and 14 failure modes. Configuration-level failure rates are reported for the evaluated models and tasks. - [MAST Project Page — UC Berkeley Sky Computing Lab](https://sky.cs.berkeley.edu/project/mast/) — Dataset, taxonomy, and annotator tools - [SEMAP: Protocol-Driven Multi-Agent Engineering](https://arxiv.org/html/2510.12120) — Up to 69.6% failure reduction on function-level development tasks through structured communication protocols in software-engineering multi-agent settings - [LangChain State of AI Agents](https://www.langchain.com/state-of-agent-engineering) — 57.3% have agents in production, quality is #1 barrier (32%) -All dollar figures in cost tables and scenario models are illustrative. They are not measured production data. Pricing assumptions: GPT-4o at $2.50 input / $10.00 output per 1M tokens ([OpenAI API docs](https://developers.openai.com/api/docs/models/gpt-4o)); Claude Sonnet 4 at $3.00 input / $15.00 output per 1M tokens ([Anthropic API pricing](https://docs.anthropic.com/en/docs/about-claude/models)). Verify current rates before using these models for your own budgeting. +All dollar figures in cost tables and scenario models are illustrative. They are not measured production data. Pricing assumptions: GPT-4o at $2.50 input / $10.00 output per 1M tokens ([OpenAI API docs](https://developers.openai.com/api/docs/models/gpt-4o)); Claude Sonnet 4.6 at $3.00 input / $15.00 output per 1M tokens ([Anthropic API pricing](https://platform.claude.com/docs/en/about-claude/pricing)). Rates verified July 24, 2026; recheck before budgeting. ## Further Reading diff --git a/blog/zero-trust-for-ai-agents-why-every-tool-call-needs-a-policy-decision.md b/blog/zero-trust-for-ai-agents-why-every-tool-call-needs-a-policy-decision.md index 8a52ddc9..b0ead356 100644 --- a/blog/zero-trust-for-ai-agents-why-every-tool-call-needs-a-policy-decision.md +++ b/blog/zero-trust-for-ai-agents-why-every-tool-call-needs-a-policy-decision.md @@ -89,11 +89,11 @@ This is the difference between a guardrail and an enforcement layer. A guardrail ### 2. Budget as a First-Class Policy Dimension -Cost authorization is security authorization. An agent that burns $47K over a weekend because it [misinterpreted an API error and ran 2.3 million calls](https://rocketedge.com/2026/03/15/your-ai-agent-bill-is-30x-higher-than-it-needs-to-be-the-6-tier-fix/) is a security incident, not just a billing problem. Zero trust for agents must include spend limits as enforceable policy — per-agent, per-[tenant](/glossary#tenant), per-run. +Cost exposure belongs in the security model. A retry or tool loop can consume substantial resources while each individual call is technically authorized. Zero-trust agent designs should include enforceable spend boundaries at the application scopes they actually use. ### 3. Scoped, Hierarchical Permissions -Flat allow/deny lists don't scale. Production systems need hierarchical scopes: a tenant has a budget, each workspace within that tenant has a sub-budget, each workflow has a sub-sub-budget, and each agent within the workflow draws from its allocated share. When one agent exhausts its scope, others continue operating. When a sub-agent is spawned, it inherits constraints from its parent — it doesn't start with a blank check. +Flat allow/deny lists do not express cumulative exposure. Cycles can use explicit ledgers at tenant, workspace, workflow, agent, and toolset scopes. A protected call consumes every matching provisioned ledger atomically. Missing child ledgers are skipped, so the orchestrator must provision intended ceilings and separately restrict a sub-agent's tools, data, credentials, and delegation depth. ### 4. Concurrency-Safe Authorization @@ -172,7 +172,7 @@ The research for this post draws from discussions and reports published between - [Hacker News: How Are You Enforcing Permissions for AI Agent Tool Calls?](https://news.ycombinator.com/item?id=46740645) — January 24, 2026 - [Hacker News: Show HN: A Runtime Authorization Layer for AI Agents](https://news.ycombinator.com/item?id=47235484) — March 2026 - [DEV Community: The Three Things Wrong with AI Agents in 2026](https://dev.to/deiu/the-three-things-wrong-with-ai-agents-in-2026-492m) — 2026 -- [RocketEdge: AI Agent Cost Control — Avoiding Budget Overruns](https://rocketedge.com/2026/03/15/your-ai-agent-bill-is-30x-higher-than-it-needs-to-be-the-6-tier-fix/) — March 15, 2026 +- [RocketEdge: AI Agent Cost Control — Avoiding Budget Overruns](https://rocketedge.com/2026/03/15/ai-agent-cost-control/) — March 15, 2026 ## Next Steps diff --git a/calculators/ai-agent-blast-radius-risk.md b/calculators/ai-agent-blast-radius-risk.md index 23a5dd9d..7e11cd1c 100644 --- a/calculators/ai-agent-blast-radius-risk.md +++ b/calculators/ai-agent-blast-radius-risk.md @@ -54,9 +54,9 @@ Each row models one class of action your agent can take. The calculator quantifi **Monthly blast radius** = `per_incident × calls_per_day × (error_rate / 100) × 30` -**Runaway blast** = `per_incident × runaway_ceiling` — one action looping N times (the [runaway / tool-loop](/incidents/runaway-agents-tool-loops-and-budget-overruns-the-incidents-cycles-is-designed-to-prevent) failure mode) before it is stopped. The **runaway ceiling** is an action *count*, and runtime action authority bounds it two ways: a per-run **action-count** cap sets it directly, while a per-run budget or a per-action [`RISK_POINTS`](/how-to/assigning-risk-points-to-agent-tools) quota bounds it *indirectly* — the number of fires permitted before the budget is exhausted (roughly `budget ÷ risk_points_per_fire`, and fewer if other actions draw on the same budget). Uncapped, the ceiling is detection-limited (often hundreds of fires); with a cap it drops to that effective limit. Lower the ceiling to your effective cap to see the bounded blast. +**Runaway blast** = `per_incident × runaway_ceiling` — one action looping N times (the [runaway / tool-loop](/incidents/runaway-agents-tool-loops-and-budget-overruns-the-incidents-cycles-is-designed-to-prevent) failure mode) before it is stopped. The **runaway ceiling** is an action *count*. A host-enforced per-run count cap sets it directly, while a mandatory reservation of caller-assigned [`RISK_POINTS`](/how-to/assigning-risk-points-to-agent-tools) can bound cumulative attempts indirectly—roughly `budget ÷ risk_points_per_attempt`, and fewer if other attempts draw on the same budget. Enter the effective ceiling your actual authorization and budget controls enforce. -**With Cycles** = `monthly_blast × (1 - containment_pct / 100)` — where containment is the share of incidents that runtime [action authority](/concepts/action-authority-controlling-what-agents-do) would prevent before they fire. +**With configured containment** = `monthly_blast × (1 - containment_pct / 100)` — where containment is your assumed share of incidents prevented by mandatory application authorization plus any enforced count or exposure budget. The slider is a scenario input, not a measured Cycles effectiveness rate. The table shows **three** numbers per action: **Blast / incident** (a single wrong fire — the discrete, worst-case exposure), **Runaway blast** (that incident × the runaway ceiling — a looping agent before it is stopped), and **Blast / mo** (the expected loss at your error rate). This is deliberate: catastrophic classes fire rarely, so the monthly figure alone under-rates them — the per-incident and runaway radii are what a risk-prediction framing misses. @@ -93,7 +93,7 @@ The two calculators answer two halves of the same question: | Persuasion column | "Cheapest model — save 43×" | "Δ — monthly risk reduction from containment" | | Maps to Cycles dimension | Cost runtime control | Action runtime authority | -A real production AI workload has both. Cost is bounded by a [budget](/guides/llm-cost-runtime-control). Damage is bounded by [what you do not let the agent do in the first place](/guides/risk-and-blast-radius). Cycles enforces both at the same runtime gate. +A real production AI workload has both. A mandatory [budget boundary](/guides/llm-cost-runtime-control) can bound submitted spend, subject to estimation, settlement, overage policy, and coverage. Damage is bounded by [what the host does not let the agent do in the first place](/guides/risk-and-blast-radius). The host can compose authorization with a Cycles `RISK_POINTS` reservation at the same dispatch boundary; Cycles does not supply the permission decision. ## Related diff --git a/calculators/claude-vs-gpt-cost-comparison.md b/calculators/claude-vs-gpt-cost-comparison.md index cec2e698..aca85771 100644 --- a/calculators/claude-vs-gpt-cost-comparison.md +++ b/calculators/claude-vs-gpt-cost-comparison.md @@ -50,17 +50,17 @@ For accurate enterprise planning, treat the calculator as a directional estimate A common pattern: a team uses a calculator like this to project monthly cost at $4,000, sets up an alert for "$5,000 exceeded," and then loses $40,000 in a weekend to an agent that loops while the on-call team sleeps. -Calculators answer "what *should* we spend?" Cycles answers a broader question — "what *should be allowed to happen at all?" — by enforcing both budgets and action authority at runtime, before each call or tool invocation leaves your application. Cost is one dimension of that. The other dimensions matter just as much in production: +Calculators answer "what *might this workload cost?" Cycles can enforce caller-configured budgets at a mandatory application boundary before protected work begins. Application authorization separately decides which calls and tool arguments are allowed. Cost is one dimension of the resulting control design: -- **Blast radius.** A single agent action — a deploy, an email blast, a database mutation — can cost more in damage than the agent's entire month of LLM bills. [Action authority](/concepts/action-authority-controlling-what-agents-do) caps *what* agents do, not just *how much* they spend. -- **Risk-weighted authorization.** Not every tool call is equal. Reading a file is not the same as sending a refund or executing code. [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) lets you allocate authority by danger, not by token count. +- **Blast radius.** A single agent action — a deploy, an email blast, a database mutation — can cost more in damage than the agent's entire month of LLM bills. The host must authorize the tool and arguments; Cycles can meter caller-assigned action exposure alongside that decision. +- **Risk-weighted budgeting.** Not every tool call is equal. [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) lets the caller meter a team-defined exposure score separately from token cost; it is not an authorization decision. - **Multi-tenant boundaries.** A noisy tenant cannot drain shared headroom from quiet ones. See [Multi-tenant SaaS](/how-to/multi-tenant-saas-with-cycles). If your projected $4,000/month is the actual constraint, the right response is not an alert at $5,000 but a [pre-execution gate](/protocol/how-decide-works-in-cycles-preflight-budget-checks-without-reservation) that will not let calls proceed beyond the cap — *and* an action-authority layer that prevents the catastrophic single mistake that no cost calculator can predict. ## Related -- [Why Cycles](/why-cycles) — cost, action authority, multi-tenant isolation, and governance, together +- [Why Cycles](/why-cycles) — budget authority, application authorization, tenant scoping, and governance together - [Cost Estimation Cheat Sheet](/how-to/cost-estimation-cheat-sheet) — how to size budgets accurately - [Action authority: controlling what agents do](/concepts/action-authority-controlling-what-agents-do) — the blast-radius dimension - [Debugging sudden LLM cost spikes](/troubleshoot/llm-cost-spike-debugging) — what to do when the calculator was wrong diff --git a/calculators/index.md b/calculators/index.md index 4920cb49..c943e2d7 100644 --- a/calculators/index.md +++ b/calculators/index.md @@ -25,9 +25,9 @@ Monthly blast radius across agent action classes, modeled by reversibility (×1 ## Try a pre-configured scenario -Each link below opens the calculator with realistic numbers from a specific incident or workload pattern, ready to tune to your own: +Each link below opens the calculator with an illustrative workload pattern, ready to tune to your own: -- **[The "$800 estimated, $4,200 actual" support bot](/calculators/claude-vs-gpt-cost-standalone#s=eyJ3b3JrbG9hZE5hbWUiOiJDdXN0b21lciBzdXBwb3J0IGJvdCIsIndvcmtsb2FkRGVzY3JpcHRpb24iOiIxMSBMTE0gY2FsbHMgcGVyIGNvbnZlcnNhdGlvbi4gQ29udGV4dCB3aW5kb3dzIGdyb3cgd2l0aCBlYWNoIHR1cm4uIEVzdGltYXRlZCAkODAwL21vLCBhY3R1YWwgJDQsMjAwLiIsImlucHV0VG9rZW5zIjo1MDAwLCJvdXRwdXRUb2tlbnMiOjEyMDAsImNhbGxzUGVyRGF5IjozMzAwfQ)** — 11 calls per conversation, growing context windows +- **[An "$800 estimated, $4,200 production-shaped" support-bot scenario](/calculators/claude-vs-gpt-cost-standalone#s=eyJ3b3JrbG9hZE5hbWUiOiJDdXN0b21lciBzdXBwb3J0IGJvdCIsIndvcmtsb2FkRGVzY3JpcHRpb24iOiIxMSBMTE0gY2FsbHMgcGVyIGNvbnZlcnNhdGlvbi4gQ29udGV4dCB3aW5kb3dzIGdyb3cgd2l0aCBlYWNoIHR1cm4uIEVzdGltYXRlZCAkODAwL21vLCBhY3R1YWwgJDQsMjAwLiIsImlucHV0VG9rZW5zIjo1MDAwLCJvdXRwdXRUb2tlbnMiOjEyMDAsImNhbGxzUGVyRGF5IjozMzAwfQ)** — 11 calls per conversation, growing context windows - **[Quality-loop bug](/calculators/claude-vs-gpt-cost-standalone#s=eyJ3b3JrbG9hZE5hbWUiOiJTdXBwb3J0IGFnZW50IHdpdGggcXVhbGl0eS1sb29wIGJ1ZyIsIndvcmtsb2FkRGVzY3JpcHRpb24iOiJEcmFmdHMgYSByZXNwb25zZSwgZXZhbHVhdGVzIHF1YWxpdHksIHJlZmluZXMgdW50aWwgc2NvcmUgPjguIEJ1ZzogZXZhbHVhdG9yIG5ldmVyIHJldHVybnMgYWJvdmUgNi45LiB-MTAwIGNhbGxzIHBlciByZWZpbmVtZW50IGxvb3AuIiwiaW5wdXRUb2tlbnMiOjMwMDAsIm91dHB1dFRva2VucyI6ODAwLCJjYWxsc1BlckRheSI6NTAwfQ)** — agent refines until a score that never reaches threshold - **[Multi-tenant noisy neighbor](/calculators/claude-vs-gpt-cost-standalone#s=eyJ3b3JrbG9hZE5hbWUiOiJNdWx0aS10ZW5hbnQgU2FhUyDigJQgbm9pc3kgdGVuYW50Iiwid29ya2xvYWREZXNjcmlwdGlvbiI6Ik9uZSB0ZW5hbnQgcnVucyBhdCA1MHggdGhlIGF2ZXJhZ2UgbG9hZC4gU2hhcmVkIGJ1ZGdldDsgdGhlaXIgYnVybiBkcmFpbnMgZXZlcnlvbmUncyBoZWFkcm9vbS4iLCJpbnB1dFRva2VucyI6NDAwMCwib3V0cHV0VG9rZW5zIjoxMDAwLCJjYWxsc1BlckRheSI6NTAwMDB9)** — one tenant runs 50× the average load on a shared budget - **[Coding agent with database access](/calculators/ai-agent-blast-radius-standalone#s=eyJhZ2VudE5hbWUiOiJDb2RpbmcgYWdlbnQgKGRhdGFiYXNlIGFjY2VzcykiLCJhZ2VudERlc2NyaXB0aW9uIjoiQUkgY29kaW5nIGFnZW50IHdpdGggcHJvZHVjdGlvbiBkYXRhYmFzZSBjcmVkZW50aWFscyDigJQgcmVhZCBzY2hlbWEsIHJ1biBtaWdyYXRpb25zLCBwdXNoIGNvZGUuIiwiY29udGFpbm1lbnRQY3QiOjAsInJvd3MiOlt7Im5hbWUiOiJEUk9QIFRBQkxFIC8gREVMRVRFIG1pZ3JhdGlvbiIsInJldmVyc2liaWxpdHkiOiJpcnJldmVyc2libGUiLCJ2aXNpYmlsaXR5IjoiY3VzdG9tZXItZmFjaW5nIiwiY29zdFBlckFjdGlvbiI6MCwiYWZmZWN0ZWRVc2VycyI6MTAwMDAwLCJjb3N0UGVyVXNlciI6NTAsImNhbGxzUGVyRGF5Ijo1MCwiZXJyb3JSYXRlIjowLjA1fSx7Im5hbWUiOiJTY2hlbWEgbWlncmF0aW9uIHRvIHByb2R1Y3Rpb24iLCJyZXZlcnNpYmlsaXR5IjoiaGFyZC10by1yZXZlcnNlIiwidmlzaWJpbGl0eSI6ImN1c3RvbWVyLWZhY2luZyIsImNvc3RQZXJBY3Rpb24iOjAsImFmZmVjdGVkVXNlcnMiOjEwMDAwMCwiY29zdFBlclVzZXIiOjIwLCJjYWxsc1BlckRheSI6NSwiZXJyb3JSYXRlIjowLjF9LHsibmFtZSI6IkNvZGUgZGVwbG95IHRvIHB1YmxpYyBzaXRlIiwicmV2ZXJzaWJpbGl0eSI6ImhhcmQtdG8tcmV2ZXJzZSIsInZpc2liaWxpdHkiOiJwdWJsaWMiLCJjb3N0UGVyQWN0aW9uIjowLCJhZmZlY3RlZFVzZXJzIjoxMDAwMDAsImNvc3RQZXJVc2VyIjoxMCwiY2FsbHNQZXJEYXkiOjIwLCJlcnJvclJhdGUiOjAuM30seyJuYW1lIjoiUmVhZCBzY2hlbWEgLyBTRUxFQ1QiLCJyZXZlcnNpYmlsaXR5IjoicmV2ZXJzaWJsZSIsInZpc2liaWxpdHkiOiJpbnRlcm5hbCIsImNvc3RQZXJBY3Rpb24iOjAsImFmZmVjdGVkVXNlcnMiOjEsImNvc3RQZXJVc2VyIjowLCJjYWxsc1BlckRheSI6NTAwMCwiZXJyb3JSYXRlIjoxfV19)** — DROP TABLE, migration, deploy, read-only rows pre-loaded with realistic severity @@ -62,13 +62,13 @@ The embed view strips the docs chrome, shows only the calculator, and includes a These calculators help with capacity planning. They do not stop spend at runtime, and they say nothing about *what* an agent is permitted to do. -Estimating that a workload will cost $4,200/month is the start of the conversation. The rest is enforcing that it actually costs no more than $4,200/month — even when an agent loops, a tenant misuses your API, or a deploy regresses to a more expensive model — *and* that no single action (a refund, a deploy, a deletion) can cause damage that dwarfs your entire monthly LLM bill in one call. +An illustrative projection that a workload will cost $4,200/month is the start of the conversation. The rest is enforcing a chosen cap at runtime — even when an agent loops, a tenant misuses your API, or a deploy regresses to a more expensive model — and separately authorizing consequential actions such as refunds, deploys, or deletions. -If your projection includes the words "should not exceed" or "we expect," you are still relying on hope. See [Why Cycles](/why-cycles) for the full runtime authority model — cost, action authority, blast radius, multi-tenant isolation — and [Debugging sudden LLM cost spikes](/troubleshoot/llm-cost-spike-debugging) for the diagnostic playbook when reality exceeds the estimate. +If your projection includes the words "should not exceed" or "we expect," it is still a planning input rather than an enforced boundary. See [Why Cycles](/why-cycles) for the composed runtime-authority model—budget enforcement plus application authorization—and [Debugging sudden LLM cost spikes](/troubleshoot/llm-cost-spike-debugging) for the diagnostic playbook when reality exceeds the estimate. ## Posts that use these calculators -- [How Much Do AI Agents Actually Cost?](/blog/how-much-do-ai-agents-cost) — the canonical "estimated $800, actual $4,200" walkthrough +- [How Much Do AI Agents Actually Cost?](/blog/how-much-do-ai-agents-cost) — an illustrative "$800 estimate, $4,200 production-shaped scenario" walkthrough - [Cursor AI Agent Reportedly Deleted a Production Database in 9 Seconds](/blog/ai-agent-deleted-prod-database-9-seconds) — uses the blast-radius calculator inline - [Multi-Tenant AI Cost Control](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) — the noisy-neighbor scenario, configured - [The True Cost of Uncontrolled AI Agents](/blog/true-cost-of-uncontrolled-agents) — both calculators, mid-post diff --git a/community/badges.md b/community/badges.md index cf8992b5..a9e73617 100644 --- a/community/badges.md +++ b/community/badges.md @@ -75,4 +75,4 @@ The badge is available in two variants to suit light and dark backgrounds: ## Tell Us About Your Project -We'd love to feature your project on the [Integration Ecosystem](/how-to/ecosystem) page. Open a [GitHub issue](https://github.com/runcycles/cycles-protocol/issues) or start a [GitHub discussion](https://github.com/runcycles/cycles-protocol/discussions) to tell us what you've built and how you're using Cycles. Community projects that use the badge and integrate Cycles are eligible to be showcased on the ecosystem page. +We'd love to feature your project on the [Integration Ecosystem](/how-to/ecosystem) page. Open a [GitHub issue](https://github.com/runcycles/cycles-protocol/issues) or start an [organization discussion](https://github.com/orgs/runcycles/discussions) to tell us what you've built and how you're using Cycles. Community projects that use the badge and integrate Cycles are eligible to be showcased on the ecosystem page. diff --git a/concepts/coding-agents-need-runtime-budget-authority.md b/concepts/coding-agents-need-runtime-budget-authority.md index 9ed3b48c..abe48759 100644 --- a/concepts/coding-agents-need-runtime-budget-authority.md +++ b/concepts/coding-agents-need-runtime-budget-authority.md @@ -11,7 +11,7 @@ They can search a codebase, scaffold features, write tests, fix bugs, and compre It becomes more important. -> **Quantify a coding agent's blast radius:** [Blast Radius Risk Calculator →](/calculators/ai-agent-blast-radius-standalone) — DROP TABLE, schema migration, deploy, and read-only rows are pre-loaded with realistic severity factors. The "with Cycles containment" slider shows what runtime action authority is worth for an agent with database credentials. +> **Model a coding agent's blast radius:** [Blast Radius Risk Calculator →](/calculators/ai-agent-blast-radius-standalone) — DROP TABLE, schema migration, deploy, and read-only rows are pre-loaded with illustrative severity factors. Set the containment slider to an assumption supported by your actual authorization and budget controls. Coding agents and runtime authority solve different problems at different layers. A coding agent is designed to complete work. Runtime authority is designed to decide whether autonomous work is still allowed to continue, under what limits, and with what reconciliation afterward. @@ -68,7 +68,7 @@ Provider-level spending caps and rate limits are useful safety nets, but they so **Rate limits** bound how fast a system can act. They do not bound how much total exposure a system creates over time. -**Provider caps** are global kill switches. They apply to all usage across all tenants and workflows — they cannot express "this tenant may spend $50 on this run" or "this agent may use 100,000 tokens for this task." +**Provider controls** use vendor-defined organization, project, workspace, credit, or quota scopes. Those can be valuable hard or soft boundaries, but shared provider identities do not automatically express "this tenant may spend $50 on this run" or "this agent may use 100,000 tokens for this task." **In-app counters** are fragile under concurrency. Two agents checking the same counter simultaneously can both proceed, creating double-spend that is only visible after the fact. @@ -88,13 +88,13 @@ Runtime authority introduces a control loop around autonomous execution: 3. **Commit** — report actual usage after work completes (unused remainder is released automatically) 4. **Release** — explicitly release budget if work is canceled -This is the [reserve/commit model](/protocol/how-reserve-commit-works-in-cycles) that Cycles implements. +This is the [reserve-commit model](/protocol/how-reserve-commit-works-in-cycles) that Cycles implements. For coding agents, this means: - a run can check whether budget is available before starting - each model call or tool invocation can debit against a scoped budget -- retries consume from the same reservation, preventing unbounded cost +- retries can share one reservation only when its estimate covers the whole retry envelope; otherwise each retry needs another reservation against the same scoped ledger - if the budget is exhausted, the agent receives a clear signal to stop or degrade - operators see real-time budget consumption, not just post-hoc bills @@ -143,7 +143,7 @@ To learn more: - Read [Why Coding Agents Do Not Replace Cycles](/concepts/why-coding-agents-do-not-replace-cycles) for the business-layer companion to this piece - Read [Why Rate Limits Are Not Enough](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) for the broader case for runtime authority -- Understand the [reserve/commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) +- Understand the [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) - See [how events work](/protocol/how-events-work-in-cycles-direct-debit-without-reservation) for direct-debit accounting - Explore the full [API Reference](/protocol/api-reference-for-the-cycles-protocol) - Try the [MCP Server](/quickstart/getting-started-with-the-mcp-server) to give a coding agent direct budget tool access — zero code changes diff --git a/concepts/comparisons.md b/concepts/comparisons.md index c1b34080..598af44e 100644 --- a/concepts/comparisons.md +++ b/concepts/comparisons.md @@ -11,15 +11,15 @@ Teams evaluating Cycles usually already have some controls in place. This page h | Tool | Best for | Where Cycles fits | |---|---|---| -| LiteLLM | Unified provider routing, key-level budgets | Adds atomic action-layer authority + hierarchical scopes | +| LiteLLM | Unified provider routing, key-level budgets | Adds atomic scoped budget authority at an application boundary | | Helicone | Observability, caching, window cost limits | Bounds spend pre-execution instead of after the fact | | OpenRouter | Single-API model access, per-key caps | Adds per-tenant + per-run hierarchical budgets | -| LangSmith | Tracing what already happened | Decides whether execution should happen | -| Guardrails AI | Content validation (PII, toxicity) | Governs budget and actions, not output content | +| LangSmith | Tracing/evaluation; private-beta LLM Gateway spend policies | Adds reserve-commit budgets at an application boundary, including non-LLM work | +| Guardrails AI | Content validation (PII, toxicity) | Bounds spend and caller-assigned exposure, not output content | | Rate limiter | Velocity control (req/sec) | Bounds total consumption, not just velocity | -| Provider cap | Org-level spending ceiling | Pre-execution, per-tenant, per-run granularity | +| Provider controls | Vendor organization/project/workspace limits | Adds application scopes such as tenant and workflow; a workflow value can be keyed per run | | DIY wrapper | Quick prototype budget logic | Production concurrency, retries, multi-tenant safety | -| **Cycles** | **Atomic budget + action authority before execution** | **Covers every gap above** | +| **Cycles** | **Atomic scoped budget authority before protected execution** | **Complements routing, content, authorization, and observability controls** | Need all of it in one layer? [Talk to a founder](mailto:founder@runcycles.io) about your stack, or [run the local demo](/demos/) to see enforcement in action. @@ -30,12 +30,12 @@ Need all of it in one layer? [Talk to a founder](mailto:founder@runcycles.io) ab | LiteLLM | Yes (budget check) | Per-team/key | Yes | No | No | No | | Helicone | Window rate limit | Per-user/property | Yes | No | No | No | | OpenRouter | Yes (key cap) | Per-key | Yes | No | No | No | -| LangSmith | No | No | After the fact | No | No | No | +| LangSmith | Gateway: yes | Workspace/API key/user | Gateway: yes | No downstream tool authorization | Gateway model fallbacks | No | | Guardrails AI | No | No | No | No | No | No | | Rate limiter | Velocity only | Partial | No | No | No | No | -| Provider cap | No (delayed) | No | Partial | No | No | No | +| Provider controls | Vendor-dependent soft or hard boundary | Provider identity only | Yes for covered usage | No application tool policy | Application chooses fallback | No application reserve-commit | | DIY wrapper | Partial | Partial | Partial | No | No | No | -| **Cycles** | **Yes** | **Yes** | **Yes** | **Yes (RISK_POINTS)** | **Yes (three-way)** | **Yes** | +| **Cycles** | **Yes, when required by host** | **Yes** | **Yes** | **Caller-assigned RISK_POINTS budget; host authorizes** | **Configured caps returned; host applies** | **Yes** | ## By alternative @@ -43,23 +43,23 @@ Need all of it in one layer? [Talk to a founder](mailto:founder@runcycles.io) ab - **[Cycles vs Rate Limiting](/concepts/cycles-vs-rate-limiting)** — rate limiters control velocity, not total consumption. An agent can stay within its request-per-second limit and still burn through an entire budget. -- **[Cycles vs Provider Spending Caps](/concepts/cycles-vs-provider-spending-caps)** — provider caps are org-level, binary, and delayed. They cannot distinguish tenants, workflows, or runs. +- **[Cycles vs Provider Cost Controls](/concepts/cycles-vs-provider-spending-caps)** — provider budgets, credits, and quotas use vendor-defined scopes and semantics. Cycles adds caller-defined budgets for instrumented application scopes. - **[Cycles vs Custom Token Counters](/concepts/cycles-vs-custom-token-counters)** — in-app counters work until concurrency, retries, and hierarchical scopes make them unreliable. ### LLM proxies and gateways -- **[Cycles vs LiteLLM](/concepts/cycles-vs-litellm)** — LiteLLM routes, rate-limits, and tracks spend at the proxy layer. Cycles enforces atomic budget authority and action control at the agent layer. They complement each other — LiteLLM picks the model, Cycles decides if the action should happen. +- **[Cycles vs LiteLLM](/concepts/cycles-vs-litellm)** — LiteLLM routes, rate-limits, and tracks spend at the proxy layer. Cycles enforces atomic scoped budgets at the application boundary. They complement each other — LiteLLM picks the model, Cycles decides whether the submitted budget request fits, and the host authorizes the action. -- **[Cycles vs Helicone](/concepts/cycles-vs-helicone)** — Helicone provides observability, caching, and window-based cost limits. Cycles provides persistent cumulative budgets and action-level enforcement. Helicone reduces what you spend; Cycles limits what you're allowed to spend. +- **[Cycles vs Helicone](/concepts/cycles-vs-helicone)** — Helicone provides observability, caching, and window-based cost limits. Cycles provides cumulative budgets for caller-submitted operations; the host separately authorizes application actions. -- **[Cycles vs OpenRouter](/concepts/cycles-vs-openrouter)** — OpenRouter provides unified model access with per-key spending caps and guardrails. Cycles adds hierarchical runtime budgets, RISK_POINTS, and delegation attenuation. OpenRouter selects the model; Cycles governs the action. +- **[Cycles vs OpenRouter](/concepts/cycles-vs-openrouter)** — OpenRouter provides unified model access with per-key spending caps and guardrails. Cycles adds hierarchical runtime budgets and caller-assigned RISK_POINTS. OpenRouter selects the model; Cycles evaluates the budget request; the host governs the action and any delegation policy. ### Observability and content safety -- **[Cycles vs LangSmith](/concepts/cycles-vs-langsmith)** — LangSmith traces what happened after execution. Cycles decides whether execution should happen at all. They complement each other. +- **[Cycles vs LangSmith](/concepts/cycles-vs-langsmith)** — LangSmith traces application behavior, and its private-beta LLM Gateway can enforce provider spend policies. Cycles adds application-boundary reserve-commit budgets, including instrumented non-LLM work. -- **[Cycles vs Guardrails AI](/concepts/cycles-vs-guardrails-ai)** — Guardrails AI validates content (hallucination, toxicity, PII). Cycles governs budget and actions. They solve different problems and complement each other. +- **[Cycles vs Guardrails AI](/concepts/cycles-vs-guardrails-ai)** — Guardrails AI validates content (hallucination, toxicity, PII). Cycles governs budgets and meters caller-assigned exposure; application authorization governs tools and arguments. They solve different problems and complement each other. - **[Cycles vs LLM Proxies and Observability Tools](/blog/cycles-vs-llm-proxies-and-observability-tools)** — broader comparison of how Cycles complements the proxy and observability ecosystem. diff --git a/concepts/cycles-evidence-verifiable-audit-for-agent-decisions.md b/concepts/cycles-evidence-verifiable-audit-for-agent-decisions.md index 423ae7fd..733c4720 100644 --- a/concepts/cycles-evidence-verifiable-audit-for-agent-decisions.md +++ b/concepts/cycles-evidence-verifiable-audit-for-agent-decisions.md @@ -5,7 +5,7 @@ description: "CyclesEvidence turns each budget decision into a signed, content-a # CyclesEvidence: Verifiable Audit for Agent Decisions -Cycles enforces budget and action authority in real time. CyclesEvidence is the other half: it makes each decision **provable after the fact** — a signed, content-addressed record that an auditor, a counterparty, or a regulator can verify on their own, without trusting your word or querying your live ledger. +Cycles enforces configured budgets for instrumented work in real time; the host retains action authorization. CyclesEvidence can make supported budget decisions **verifiable after the fact** through signed, content-addressed envelopes without querying the live ledger. ## The problem: a 200 OK is not proof @@ -21,7 +21,7 @@ For all of them, "the server said ALLOW" is hearsay. CyclesEvidence replaces hea For each authorization lifecycle event, Cycles can emit a **CyclesEvidence envelope**: the request and response, wrapped in a JSON object that is -- **canonicalized** with [RFC 8785 JCS](https://www.rfc-editor.org/rfc/rfc8785) (a deterministic byte form), +- **canonicalized** with [RFC 8785 JCS](https://www.rfc-editor.org/info/rfc8785/) (a deterministic byte form), - **content-addressed** by `evidence_id` = the SHA-256 of those canonical bytes, computed with the `evidence_id` and `signature` fields both present and set to the empty string `""` — so the id *is* the integrity check, and - **Ed25519-signed** by the Cycles server's key — so the origin is provable. @@ -60,7 +60,7 @@ It is also **off until configured.** A deployment must set a shared signing iden ## In one line -CyclesEvidence makes a budget decision **portable, verifiable proof** — so an agent's spending and action authority can be audited and trusted by systems that don't depend on your live ledger. +CyclesEvidence makes a supported budget decision portable and verifiable. A complete action audit still needs correlated host authorization, tool arguments, execution results, and external outcomes. ## Related diff --git a/concepts/cycles-vs-custom-token-counters.md b/concepts/cycles-vs-custom-token-counters.md index f47c95bc..4e7a4c12 100644 --- a/concepts/cycles-vs-custom-token-counters.md +++ b/concepts/cycles-vs-custom-token-counters.md @@ -5,7 +5,7 @@ description: "In-app token counters break under concurrency, multi-service deplo # Cycles vs Custom Token Counters: Build vs Buy for Agent Budget Control -Every team that runs AI agents in production eventually builds a token counter. +Many teams that run AI agents in production begin with a token counter. It starts the same way every time. @@ -59,7 +59,7 @@ When two agent threads run concurrently: 4. Thread A's call uses 200 tokens. Thread B's call uses 200 tokens. 5. Actual total: 1,300 tokens. Budget exceeded by 30%. -This is not a theoretical concern. It is the most common bug in custom counter implementations. +This is not a theoretical concern; it is a common failure mode in counters that separate the check from the update. Solving it correctly requires atomic compare-and-swap operations, database-level locking, or serialized access. Most ad hoc counters do not implement any of these. Even when they do, the implementation is often subtly wrong — it works under light load and breaks under production concurrency. @@ -71,7 +71,7 @@ When the system scales to multiple instances, each instance has its own counter. Moving the counter to a shared store (Redis, PostgreSQL) solves the locality problem but introduces the concurrency problem. Now every read-check-increment must be atomic across a network boundary. Latency, retries, and connection failures add complexity. -Moving to a shared store also means every service that makes LLM calls needs to know about the counter, use the same key scheme, and handle failures consistently. That coordination cost grows with each new service. +Moving to a shared store also means every protected service that makes LLM calls needs to know about the counter, use the same key scheme, and handle failures consistently. That coordination cost grows with each new service. ### No reservation model: cannot hold budget for in-flight work @@ -163,7 +163,7 @@ Once budget enforcement must span more than one service, a local counter is no l ### Multi-tenant deployment -When different tenants share the same infrastructure and need independent budget limits, the counter must become tenant-aware. Multiplied by hierarchical scopes (tenant, workspace, run), the counter logic becomes a budget system whether you intended to build one or not. +When different tenants share the same infrastructure and need independent budget limits, the counter must become tenant-aware. Multiplied by hierarchical scopes such as tenant, workspace, and workflow, the counter logic becomes a budget system whether you intended to build one or not. ### Production concurrency @@ -187,19 +187,19 @@ Moving from custom counters to Cycles does not require a big-bang migration. ### Step 1: Deploy in shadow mode -Start Cycles in shadow mode alongside your existing counters. Cycles evaluates budget decisions but does not enforce them. Both systems run in parallel. +Send Cycles reservation requests with `dry_run: true` alongside your existing counter path. The server returns hypothetical decisions without creating reservations or mutating balances. Log those responses in the application so the two systems can be compared. -Compare the decisions. Does Cycles agree with your counter? Where do they diverge? Divergences usually reveal bugs in the custom counter — race conditions, missing scope checks, or inconsistent state. +Compare the decisions. Does Cycles agree with your counter? Where do they diverge? A divergence may reveal a counter bug, different scope mapping, stale state, or a difference in estimate policy, so investigate it rather than assuming which system is wrong. See [Shadow Mode Rollout](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) for a detailed guide. ### Step 2: Validate scope configuration -Configure Cycles with the same budget limits your counters enforce. Map your counter keys to Cycles scopes. Verify that the hierarchical scopes (tenant, workspace, workflow, run) match your application's budget structure. +Configure Cycles with the same budget limits your counters enforce. Map your counter keys to the standard Cycles scopes: tenant, workspace, app, workflow, agent, and toolset. If one counter is keyed per run, map that run ID to `subjects.workflow`; a run ID in `dimensions` is attribution only. ### Step 3: Enable enforcement on one service -Pick the lowest-risk service — the one with the simplest counter logic and the least concurrency. Switch it from the custom counter to Cycles. Monitor for a week. +Pick a lower-impact service with simple counter logic and limited concurrency. Switch its protected paths from the custom counter to live Cycles reservations, then monitor long enough to cover representative traffic and failure cases. ### Step 4: Roll out to remaining services @@ -209,7 +209,7 @@ Move each service from its custom counter to Cycles. With each migration, the cu Once all services use Cycles, the custom counter code can be deleted. No more duplicated logic, no more inconsistent enforcement, no more race conditions in hand-rolled concurrency handling. -The result is a system where runtime authority is centralized, concurrency-safe, and consistent across every service that makes LLM calls. +The result is a system where budget authority is centralized, concurrency-safe, and consistent across every protected service that makes LLM calls. ## The build vs buy calculation diff --git a/concepts/cycles-vs-guardrails-ai.md b/concepts/cycles-vs-guardrails-ai.md index 95987495..cadb4992 100644 --- a/concepts/cycles-vs-guardrails-ai.md +++ b/concepts/cycles-vs-guardrails-ai.md @@ -51,25 +51,25 @@ But none of these capabilities address the question: should this model call happ ## What Cycles does -Cycles is a runtime authority for autonomous agents. It enforces cost limits before work begins, using a reserve-then-commit lifecycle. +Cycles supplies runtime budget authority for autonomous agents through a reserve-then-commit lifecycle. Its core capabilities include: ### Pre-execution budget enforcement -Before an agent calls a model, Cycles checks whether sufficient budget remains. If the budget is exhausted, the call does not happen. The decision is made before any cost is incurred. +Before an instrumented model call, the host asks Cycles whether the submitted estimate fits the matching ledgers. At a mandatory boundary, a rejected reservation prevents that call from starting. Calls that bypass the integration are outside this guarantee. ### Reserve-then-commit lifecycle -Cycles does not just track spend after the fact. It reserves estimated cost before execution, then commits actual cost afterward. Unused budget is released automatically. This prevents concurrent requests from racing past a budget limit. +Cycles reserves estimated cost before execution, then commits actual cost afterward. Commit releases any unused portion of the hold. This prevents concurrent submitted estimates from oversubscribing the same matching ledgers; actual usage above an estimate follows the commit-overage policy. ### Concurrency-safe budget tracking -When multiple agent threads or workflows run in parallel, Cycles uses atomic reservations to prevent overspend. Two threads cannot both claim the last $5 of budget — the reservation is atomic. +When multiple agent threads or workflows run in parallel, Cycles uses atomic reservations. If $5 remains and two submitted reservations each request $4, at most one can succeed. ### Hierarchical scope enforcement -Budgets can be enforced at multiple levels simultaneously: tenant, workspace, workflow, run, and action. A single reservation can check all applicable scopes in one operation. +Budgets can be enforced at multiple standard subject levels simultaneously: tenant, workspace, app, workflow, agent, and toolset. A single reservation checks all applicable populated scopes in one atomic operation. To create a ledger per run, map the run ID to a standard field such as `workflow`; `run` and `action` are not native budget scopes. ### Three-way decisions @@ -106,7 +106,7 @@ These are independent concerns. Neither subsumes the other. | **What it prevents** | Toxic content, schema violations, prompt injection, PII leakage | Budget overruns, unbounded spend, cost race conditions | | **Concurrency model** | Per-request validation (stateless) | Atomic reservations across concurrent requests (stateful) | | **Budget awareness** | None — does not track cost or spend | Core function — reserves, commits, and tracks budget across scopes | -| **Protocol** | Python framework with validators and guards | Open protocol with reserve/commit/release lifecycle | +| **Protocol** | Python framework with validators and guards | Open protocol with reserve-commit-release lifecycle | | **Retry behavior** | Re-asks the model with corrected prompts | Idempotent reservations — retries do not double-spend | | **Scope** | Per-call input/output validation | Per-tenant, per-workflow, per-agent hierarchical budgets | | **Degradation** | Can correct or filter outputs | Can downgrade model choice, reduce scope, or deny execution | diff --git a/concepts/cycles-vs-helicone.md b/concepts/cycles-vs-helicone.md index be692d28..cc67c823 100644 --- a/concepts/cycles-vs-helicone.md +++ b/concepts/cycles-vs-helicone.md @@ -1,6 +1,6 @@ --- title: "Cycles vs Helicone: Enforcement vs Observability and Rate Limiting" -description: "Helicone optimizes cost with caching and observability. Cycles enforces budgets and action authority. See how they differ and how they work better together." +description: "Helicone adds gateway observability and rate limits. Cycles meters caller-submitted work across application scopes. See how the layers fit together." --- # Cycles vs Helicone: Enforcement vs Observability and Rate Limiting @@ -20,7 +20,7 @@ The question is whether visibility and rate limiting are enough — or whether y | **Rate limiting** | Request-count and cost-per-window (via headers) | Not a rate limiter — enforces per-action budget authority | | **Budget enforcement** | Cost-based rate limit blocks within a time window | Cumulative budget with atomic reserve-commit lifecycle | | **Alerts** | Threshold notifications (email, Slack) | Webhook events on budget state transitions | -| **Action control** | None — all actions pass if under rate limit | [RISK_POINTS](/glossary#risk-points) — per-tool risk scoring | +| **Action control** | No authorization for downstream application tools | Caller-assigned [RISK_POINTS](/glossary#risk-points) budget; host authorizes tools | | **Multi-tenant** | Per-user/per-property rate limit segmentation | Tenant-scoped API keys with hierarchical budgets | | **Caching** | Built-in LLM response caching | Not a caching layer | | **Smart routing** | Cheapest-provider selection | Not a routing layer | @@ -49,19 +49,19 @@ Cycles tracks cumulative budget state with a balance that decreases with each re Helicone rate limits are configured per-request via HTTP headers (`Helicone-RateLimit-Policy`) with low-latency enforcement and immediate 429 feedback. However, there's no persistent budget object that lives independently of the requests. If you change the header value, the limit changes. If you forget the header, there's no limit. -Cycles budgets are persistent objects created via the admin API. They exist independently of any request. Every reservation checks against the budget state — there's no way to "forget" to enforce. +Cycles budgets are persistent objects created via the admin API. They exist independently of any request. The protection still depends on the host sending each governed operation through the mandatory reservation boundary. -### 3. Alerts vs. enforcement +### 3. Gateway enforcement vs. budget-state events -Helicone cost alerts notify you when thresholds are crossed. They don't block the action. The alert fires, the Slack message arrives, and by then the budget may already be significantly exceeded. +Helicone alerts are notifications, while its custom request- and cost-based rate limits are enforceable gateway controls that return `429` before an over-limit provider request. These are separate features. -Cycles events also notify — but the enforcement has already happened. The agent's action was DENIED or constrained (ALLOW_WITH_CAPS) before execution. The webhook event is confirmation of what the enforcement layer already prevented. +Cycles emits events for selected registered lifecycle transitions. A live reservation without sufficient budget returns an error before protected execution; `ALLOW_WITH_CAPS` is a separately configured decision outcome, not an automatic response at a threshold. ### 4. No action-level control Helicone controls request volume and cost. It cannot distinguish between a $0.01 search API call and a $0.01 `send_email` tool call. Both cost the same in tokens — but the email has 10,000x the blast radius. -Cycles' [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) budget scores actions by consequence, not cost. An agent can search freely (0 points) while being limited to 2 customer emails per run (40 points each × 2 = 80 of a 100-point budget). +The host can assign [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) estimates by consequence and require a reservation before a tool attempt. For example, two 40-point email estimates consume 80 points from a 100-point budget. The host still authorizes the email and ensures every protected attempt is instrumented. ### 5. Segmented window limits, not hierarchical cumulative budgets @@ -77,9 +77,9 @@ Helicone and Cycles complement each other. Running both gives you capabilities n Request flow: Agent decides to act → Cycles: "Should this action happen?" (budget authority, RISK_POINTS) - → Helicone: Route to cheapest provider, check cache + → Helicone: Check rate-limit policy, route, or serve cache → Provider: Execute (or return cached response) - → Helicone: Log cost, trace, check rate limit window + → Helicone: Log cost and trace → Cycles: Commit actual cost, release unused reservation ``` @@ -90,7 +90,7 @@ Request flow: | LLM response caching (deduplicate identical calls) | Helicone | | Cheapest-provider routing | Helicone | | Pre-execution budget authority | Cycles | -| Action-level RISK_POINTS control | Cycles | +| Caller-assigned action-exposure budget | Cycles; host authorizes actions | | Cost attribution per trace/session | Helicone | | Cumulative budget enforcement per tenant | Cycles | | Rate limiting per time window | Helicone | @@ -124,7 +124,7 @@ Cycles is not an observability platform, a caching layer, or a router. It doesn' ## Sources -Feature claims verified against [docs.helicone.ai](https://docs.helicone.ai) as of April 2026. Cycles claims based on v0.1.25. These tools evolve quickly — check the linked docs for the latest. +Feature claims verified against [Helicone's custom rate-limit documentation](https://docs.helicone.ai/features/advanced-usage/custom-rate-limits) on July 24, 2026. Cycles claims are based on v0.1.25. These tools evolve quickly—check the linked docs for the latest. ## Related diff --git a/concepts/cycles-vs-langsmith.md b/concepts/cycles-vs-langsmith.md index 6c3aadac..9b8bf41e 100644 --- a/concepts/cycles-vs-langsmith.md +++ b/concepts/cycles-vs-langsmith.md @@ -1,13 +1,13 @@ --- -title: "Cycles vs LangSmith: Enforcement vs Observability" -description: "LangSmith traces what happened after execution. Cycles decides whether execution should happen at all. See how they complement each other in a production agent stack." +title: "Cycles vs LangSmith: Budget Layers Compared" +description: "Compare Cycles application-boundary budgets with LangSmith tracing and its private-beta LLM Gateway spend policies." --- -# Cycles vs LangSmith: Enforcement vs Observability +# Cycles vs LangSmith: Budget Layers Compared LangSmith is one of the most widely adopted observability platforms for LLM applications. If you're building with LangChain or LangGraph, you're probably already using it — or evaluating it. -Cycles and LangSmith operate at different points in the agent lifecycle. Understanding where each fits prevents both gaps and redundancy in your production stack. +LangSmith is no longer only an observability comparison. Its private-beta LLM Gateway can proxy supported provider calls and enforce spend policies. Cycles remains an application-boundary budget service with reserve-commit semantics. Understanding the enabled LangSmith surface and the traffic each boundary covers prevents both gaps and redundancy. > **Run the numbers for your workload:** [Cost Calculator →](/calculators/claude-vs-gpt-cost-standalone) · [Blast Radius Risk Calculator →](/calculators/ai-agent-blast-radius-standalone) — observability records what happened; the calculators show what *will* happen at your token volume and action profile. @@ -15,40 +15,39 @@ Cycles and LangSmith operate at different points in the agent lifecycle. Underst | | LangSmith | Cycles | |---|---|---| -| **When it acts** | After execution | Before execution | -| **What it answers** | "What happened?" | "Should this happen?" | -| **Core mechanism** | Tracing, logging, evaluation | Reserve → commit → release | -| **Cost tracking** | Reports actual cost per trace | Enforces cost limits per action | -| **Action control** | None — records all actions | Denies actions that exceed authority | -| **Scope** | Per-run, per-chain traces | Per-tenant, per-agent, per-action | -| **Concurrency** | N/A (read-only) | Atomic reservations (write path) | -| **Multi-tenant** | Tags and metadata | API-key-scoped tenant isolation | +| **When it acts** | Observability records execution; LLM Gateway evaluates policy before a proxied model call | Application reserves before protected execution and settles afterward | +| **What it answers** | "What happened?" and, in Gateway, "Does this provider request fit the spend policy?" | "Can this submitted amount be held against every matching ledger?" | +| **Core mechanism** | Tracing, evaluation, and private-beta provider proxy policies | Reserve → commit → release | +| **Cost control** | Hard Gateway caps by organization, workspace, API key, or user over hourly through monthly windows | Atomic budgets across populated tenant → workspace → app → workflow → agent → toolset scopes | +| **Application actions** | Traces tool runs; Gateway governs supported LLM-provider traffic | Can meter caller-assigned tool exposure; host still authorizes tools and arguments | +| **Settlement** | Gateway tracks spend against caps | Explicit estimate hold, best-known actual commit, and release | +| **Deployment** | LangSmith-managed Gateway is in private beta | Self-hosted service and open protocol | ## The fundamental difference -LangSmith tells you that yesterday's agent spent $47 across 312 runs, with an average latency of 2.3 seconds and a 4% error rate. That information is valuable — it drives optimization, debugging, and capacity planning. +LangSmith Observability tells you what an agent did across traces and runs. That information drives optimization, debugging, evaluation, and capacity planning. -LangSmith can alert on cost anomalies — but alerting is reactive. By the time the alert fires, the spend has already happened. Cycles operates on the write path, preventing the spend before it occurs. +When traffic is routed through the private-beta LLM Gateway, LangSmith can also evaluate hard spend policies on each incoming provider request. Its documented scopes are organization, workspace, API key, and user, with hourly, daily, weekly, or monthly windows. A blocked request receives `402`, and the violation is attached to a trace. -Cycles operates on the write path. Before an LLM call executes, the agent must reserve budget. If the budget is exhausted, the call is denied — the model is never invoked, no tokens are consumed, no cost is incurred. This is enforcement, not observation. +Cycles operates at the boundary the application chooses. Before a protected LLM or tool call executes, the host requests an atomic hold against matching ledgers. If the reservation fails and the host honors that result, the protected operation does not run. If it succeeds, the host commits best-known actual usage or releases an unused hold. -## Where LangSmith stops +## Where the boundaries differ -### No pre-execution gate +### Provider gateway versus application boundary -LangSmith traces are recorded during and after execution. There is no mechanism to block an LLM call before it happens based on budget state. By the time LangSmith records that an agent exceeded its budget, the money is already spent. +LangSmith's Gateway protects LLM calls routed through that proxy and currently documents OpenAI, Anthropic, Bedrock, Baseten, Fireworks, Gemini, and Vertex AI providers. Calls that bypass the Gateway, plus arbitrary application tools such as refunds, emails, database writes, and deployments, need another mandatory control point. -### No tenant-level enforcement +Cycles is provider-neutral and can sit around any instrumented operation. Coverage is not automatic: bypass paths remain outside its budget boundary, and action authorization remains application logic. -LangSmith supports tags and metadata for filtering traces by customer or environment. But these are labels — they don't enforce boundaries. Customer A's traces can't prevent Customer B's agent from running. There is no per-tenant budget enforcement. +### Spend policy versus reserve-commit -### No concurrency safety +LangSmith documents real-time spend tracking and a pre-execution block when a proxied request would cross a Gateway cap. Cycles exposes a different lifecycle: reserve an estimate atomically, hold it while work is in flight, then commit actual usage or release it. That distinction matters when many calls start concurrently or when an application needs to account for work that can fail after admission. -When multiple agent instances run simultaneously, LangSmith records each trace independently. It cannot coordinate across instances to prevent concurrent overspend. Two agents checking the same budget in parallel will both proceed — the classic time-of-check-to-time-of-use (TOCTOU) problem that Cycles solves with atomic reservations. +Do not infer one product's concurrency or overage semantics from the other. Validate Gateway behavior against the private-beta version you are using; configure Cycles estimation and commit-overage policy for the application boundary you own. -### No action authority +### Budget exposure is not tool permission -LangSmith records that an agent called `send_email` 200 times. Cycles prevents the 201st call if the action budget is exhausted. The distinction is between logging a side effect and governing whether the side effect should occur. +LangSmith can trace a `send_email` run. A host can separately require a Cycles `RISK_POINTS` reservation before each authorized attempt. Neither a Gateway spend policy nor a risk-point balance decides whether a recipient, tool, or argument is authorized. Keep identity, credentials, allowlists, and argument validation at the host or gateway that executes the action. ## Where Cycles stops @@ -67,22 +66,23 @@ These are observability concerns. Cycles is not an observability tool. The strongest production setup uses both: ``` -Request → Cycles (should this execute?) → LLM call → LangSmith (what happened?) - ↓ ↓ - DENY → graceful degradation trace → dashboard - ALLOW → proceed with budget cap cost → attribution - ALLOW_WITH_CAPS → proceed with limits latency → profiling +Application action + → host authorization + → Cycles reserve (application-scope budget) + → optional LangSmith LLM Gateway (provider spend policy, redaction, credentials) + → provider + → Cycles commit/release + LangSmith trace ``` ### Practical example A customer support agent built with LangChain: -1. **Cycles** checks budget before each instrumented LLM call and tool invocation. If the deepest matching budget has `max_tokens` configured, an accepted request returns `ALLOW_WITH_CAPS`; if a live reservation lacks budget, it returns an error such as `409 BUDGET_EXCEEDED`. The application applies caps or handles the denial. +1. **Cycles** checks budget before each instrumented LLM call and tool invocation. If a matching budget has `max_tokens` configured, an accepted request can return `ALLOW_WITH_CAPS`; if a live reservation lacks budget, it returns an error such as `409 BUDGET_EXCEEDED`. The application applies caps or handles the denial. -2. **LangSmith** traces the full execution — every chain step, tool call, and LLM response. The traces show token counts, latency, and error rates. The team uses this data to optimize prompts, evaluate response quality, and debug failures. +2. **LangSmith Observability** traces the instrumented chain execution. If the team also uses the private-beta Gateway, provider calls pass its spend and data policies before being forwarded. -Neither tool can do the other's job. LangSmith cannot block an LLM call. Cycles cannot visualize a chain execution. +The overlap is provider spend enforcement; the differences are boundary, scope vocabulary, lifecycle, deployment, and observability depth. Cycles does not visualize a chain execution, while LangSmith's provider Gateway does not automatically govern every application tool. ### Feeding Cycles data into LangSmith @@ -99,25 +99,29 @@ The commit metrics (`StandardMetrics` — tokens, latency, model version) attach - Evaluate response quality across datasets - Profile latency across chain steps - Track cost attribution across runs and users +- Enforce supported-provider spend policies through its private-beta LLM Gateway **Use Cycles when you need to:** -- Prevent an agent from exceeding its budget before it acts -- Enforce per-tenant, per-agent, or per-action limits -- Handle concurrent agents safely (atomic reservations) -- Control non-LLM actions (tool calls, API requests, emails) -- Degrade gracefully when budget is low (three-way decisions) +- Reserve estimated exposure atomically before protected application work +- Use the protocol's tenant, workspace, app, workflow, agent, and toolset scope hierarchy +- Settle best-known actual usage after execution +- Meter non-LLM work and caller-assigned action exposure +- Return configured caps for the host to apply **Use both when you need to:** - Run agents in production with both visibility and enforcement -- Attribute cost accurately (LangSmith) while enforcing limits (Cycles) +- Correlate rich execution traces with reserve-commit budget records - Debug why an agent was denied (LangSmith trace + Cycles decision) +- Layer provider-gateway policies under broader application budgets ## Key points -- **Different layers, different timing.** LangSmith observes after execution. Cycles enforces before execution. They don't overlap. -- **Observability doesn't prevent overspend.** Knowing an agent spent $47 doesn't stop the next one from spending $470. -- **Enforcement doesn't provide visibility.** Cycles tracks budget state, not execution traces. You need both for production operations. -- **Concurrency is the hidden gap.** LangSmith has no mechanism for coordinating budget across parallel agent instances. Cycles' atomic reservations solve this. +- **Compare enabled surfaces.** LangSmith Observability is retrospective; its private-beta LLM Gateway adds pre-execution provider spend policies. +- **Compare boundaries, not slogans.** Gateway policies cover routed provider traffic. Cycles covers instrumented application paths and exposes reserve-commit settlement. +- **Authorization remains separate.** Neither cost control grants permission to invoke an application tool. +- **Use correlated records.** LangSmith traces and Cycles reservation IDs can explain both execution and budget treatment. + +LangSmith behavior was rechecked on July 24, 2026 against the official [LLM Gateway overview](https://docs.langchain.com/langsmith/llm-gateway) and [spend-policy documentation](https://docs.langchain.com/langsmith/llm-gateway-spend-policies). The Gateway is documented as private beta, so verify availability and semantics for your account. ## Next steps diff --git a/concepts/cycles-vs-litellm.md b/concepts/cycles-vs-litellm.md index 17f9d168..e6c63b38 100644 --- a/concepts/cycles-vs-litellm.md +++ b/concepts/cycles-vs-litellm.md @@ -1,6 +1,6 @@ --- title: "Cycles vs LiteLLM: Budget Authority vs Proxy Budgets" -description: "LiteLLM routes and rate-limits. Cycles enforces budget authority and action control. See where each fits, where they overlap, and how they work together." +description: "LiteLLM routes model traffic and supports gateway budgets. Cycles meters caller-submitted work across application scopes. See how the layers fit together." --- # Cycles vs LiteLLM: Budget Authority vs Proxy Budgets @@ -16,13 +16,13 @@ The answer depends on what failure modes you're trying to prevent. | | LiteLLM | Cycles | |---|---|---| | **Primary role** | LLM proxy — routing, fallback, cost tracking | Runtime authority — pre-execution enforcement | -| **When it acts** | At the model-call layer | At the agent-action layer | -| **Budget model** | Per-key, per-team, per-user, per-customer, per-model spend limits | Per-tenant, per-workflow, per-run, per-action hierarchical scope derivation | +| **When it acts** | At the model-call layer | At any application boundary the caller instruments | +| **Budget model** | Per-key, per-team, per-user, per-customer, per-model spend limits | Standard tenant → workspace → app → workflow → agent → toolset ledgers; a workflow value can be unique per run | | **Enforcement** | Block when `max_budget` exceeded | Atomic reserve-commit — budget locked before action | -| **Rate limiting** | RPM, TPM per key/team/user | Not a rate limiter — enforces per-action authority | -| **Action control** | Model access lists (which models a key can call) | [RISK_POINTS](/glossary#risk-points) — per-tool risk scoring and limits | +| **Rate limiting** | RPM, TPM per key/team/user | Not a rate limiter — enforces configured cumulative budgets | +| **Action control** | Model access lists (which models a key can call) | Caller-assigned [RISK_POINTS](/glossary#risk-points) budget; host authorizes tools | | **Multi-tenant** | Team/user isolation via `team_id` | Tenant-scoped API keys with hierarchical budget derivation | -| **Concurrency safety** | Eventually consistent (~10 request drift at high traffic) | Atomic Lua-scripted reservations (zero TOCTOU drift) | +| **Concurrency model** | Gateway spend counters and budget checks | Atomic estimate reservation before protected work | ## Where LiteLLM's budgets work well @@ -38,31 +38,29 @@ For teams that need basic cost control at the LLM proxy layer, this covers the c ## Where the gaps appear -### 1. Concurrency safety +### 1. Different accounting boundaries -LiteLLM tracks spend via an in-memory cache synced to Redis every ~10ms. Under high concurrency, budget enforcement is eventually consistent — their docs note approximately 10 requests of drift at 100 RPS across 3 instances. +LiteLLM can block inference requests against configured key, team, user, customer, and project budgets. Those are gateway spend controls and should be used when they match the workload. -For a single-agent prototype, this is fine. For concurrent agents sharing a budget, 10 requests of drift means 10 unaccounted charges — the overrun depends on per-request cost ($0.50-$5 at the high end for expensive models). On a small budget, this can be significant. This is the [TOCTOU race condition](/blog/we-built-a-custom-agent-rate-limiter-heres-why-we-stopped) that atomic reservation systems are designed to prevent. - -Cycles uses atomic Lua-scripted reservations: the budget check and the decrement happen in a single Redis operation. Zero TOCTOU drift, regardless of concurrency. (Overages can still occur when actual cost exceeds the estimate — these are tracked as debt and surfaced immediately, not silently absorbed.) +Cycles uses a different accounting primitive: it atomically reserves a caller estimate before protected work begins, then commits the actual amount. This prevents concurrent requests from all relying on the same unreserved capacity. Actual usage can still exceed the estimate; the configured commit-overage policy determines whether the overage is rejected, charged from remaining capacity, or recorded as debt. ### 2. Action-level control LiteLLM controls which **models** a key can access. It cannot control what **actions** an agent takes with those models. -An agent that sends 200 emails costs $1.40 in tokens. LiteLLM's budget wouldn't fire — the cost is trivial. But the action is catastrophic. Cycles' [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) budget scores tools by blast radius (email = 40 points, deploy = 150 points, search = 0), limiting *what* the agent does, not just *how much it spends*. +An application can assign higher [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) estimates to higher-blast-radius tool attempts and require a Cycles reservation before invoking them. That bounds submitted cumulative exposure. Cycles does not decide whether the tool or its arguments are authorized; the host must enforce that separately. ### 3. Scope hierarchy and delegation LiteLLM supports budgets across multiple proxy-layer scopes: keys, teams, internal users, end users/customers, and model/provider/tag dimensions. This is meaningful coverage for proxy-level cost governance. -However, these are proxy-layer spend-tracking scopes, not runtime authority scopes. Cycles supports hierarchical scope derivation (tenant → workspace → app → workflow → agent → toolset) where each level can have its own persistent cumulative budget, making [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) a pattern you can build: carve a narrower sub-budget for each delegation hop, so a sub-agent draws from a smaller allocation than its parent. Action masks and delegation-depth limits are orchestration logic on top of those scopes — not protocol primitives. +However, these are proxy-layer spend-tracking scopes, not the same application hierarchy. Cycles derives tenant → workspace → app → workflow → agent → toolset scopes, where explicitly provisioned ledgers can impose overlapping ceilings. A child call can consume both a shared ancestor ledger and a narrower agent ledger. Cycles does not transfer a parent allocation to the child; action masks and delegation-depth limits remain orchestration logic. -The difference matters in multi-agent systems where a single user request fans out into dozens of sub-agent calls. LiteLLM sees each call as an independent model request at the proxy layer. Cycles sees the delegation chain and enforces aggregate limits across the full scope hierarchy. +The difference matters in multi-agent systems where a single user request fans out into dozens of sub-agent calls. LiteLLM sees the gateway identity and metadata on each inference request. Cycles evaluates the tenant and subject scopes the host submits; it does not discover a delegation chain automatically. ### 4. Reserve-commit lifecycle -LiteLLM tracks spend after the fact — the cost is recorded when the model response arrives. If the response is expensive (long output, many tokens), the budget is already spent before LiteLLM knows the cost. +LiteLLM checks configured gateway budgets before routing and records actual inference cost. Cycles adds an explicit caller-estimated hold before work, including for non-LLM operations. Cycles [reserves budget before the action](/blog/what-is-runtime-authority-for-ai-agents) based on an estimate, executes only if approved, and commits the actual cost after. The unused difference is released. The budget cannot be silently drained by concurrent requests. If actual cost exceeds the estimate, the overage is tracked as debt and surfaced via webhook events — not silently absorbed. @@ -91,7 +89,7 @@ Request flow: | Team-level cost visibility | LiteLLM | | Atomic per-action budget enforcement | Cycles | | Model access restrictions (which models) | LiteLLM | -| Tool access restrictions (which actions) | Cycles | +| Tool access restrictions (which actions) | Application/harness; Cycles can return configured allow/deny cap fields | | Delegation attenuation for sub-agents | Cycles (pattern via hierarchical scopes) | | Provider failover and retry | LiteLLM | @@ -106,10 +104,9 @@ Cycles is not a proxy, router, or model-access layer. It doesn't handle provider ## When LiteLLM alone is enough - Single-tenant, single-agent prototype -- No concurrent agents sharing budgets - No action-level risk (agent only reads, never writes/sends/deploys) -- Budget overruns of ~10 requests are acceptable -- No delegation chains or multi-agent systems +- Proxy identities and spend limits match the required budget boundaries +- All governed cost flows through the LiteLLM gateway ## When you need Cycles @@ -117,11 +114,11 @@ Cycles is not a proxy, router, or model-access layer. It doesn't handle provider - Agent tools with side effects (email, deploy, database mutation) - Multi-tenant SaaS with per-customer budget isolation - Multi-agent delegation chains requiring authority attenuation -- Zero-tolerance for budget overruns (financial, compliance) +- You need an explicit atomic estimate hold before concurrent work starts ## Sources -Feature claims verified against [docs.litellm.ai](https://docs.litellm.ai/docs/proxy/users) as of April 2026. Cycles claims based on v0.1.25. These tools evolve quickly — check the linked docs for the latest. +Feature claims verified against [LiteLLM's current gateway documentation](https://docs.litellm.ai/) on July 24, 2026. Cycles claims are based on v0.1.25. These tools evolve quickly—check the linked docs for the latest. ## Related diff --git a/concepts/cycles-vs-openrouter.md b/concepts/cycles-vs-openrouter.md index 609b97f6..c56b3ef0 100644 --- a/concepts/cycles-vs-openrouter.md +++ b/concepts/cycles-vs-openrouter.md @@ -1,13 +1,13 @@ --- title: "Cycles vs OpenRouter: Runtime Authority vs Routing with Guardrails" -description: "OpenRouter routes to the best model with per-key caps. Cycles enforces budget authority and action control. See where each fits and how they complement each other." +description: "Compare OpenRouter routing, workspace budgets, and guardrails with Cycles reserve-commit budgets across arbitrary instrumented agent operations." --- # Cycles vs OpenRouter: Runtime Authority vs Routing with Guardrails -OpenRouter is an LLM routing gateway that provides unified access to hundreds of models with automatic provider selection. As of 2025-2026, it has added a guardrails system with per-key spending caps, model restrictions, and data privacy policies. +OpenRouter is an LLM routing gateway that provides unified access to many models and providers. Its current controls include workspace budgets, guardrails assignable across workspace/member/key boundaries, model and provider restrictions, zero-data-retention policies, prompt-injection filters, and sensitive-data controls. -If you're already routing through OpenRouter, you have some cost control built in. The question is whether per-key caps are enough — or whether you need the deeper enforcement primitives that agent systems require. +If all protected spend flows through OpenRouter, those controls provide real preflight enforcement. The architectural question is whether gateway-level inference controls cover the same boundary as your agent workload. > **Run the numbers for your workload:** [Cost Calculator →](/calculators/claude-vs-gpt-cost-standalone) — OpenRouter routes; the calculator shows what *cheaper-model routing* alone saves vs hard per-tenant budget enforcement. @@ -16,44 +16,42 @@ If you're already routing through OpenRouter, you have some cost control built i | | OpenRouter | Cycles | |---|---|---| | **Primary role** | LLM router — model selection, provider aggregation | Runtime authority — pre-execution enforcement | -| **Budget model** | Per-key spending cap with daily/weekly/monthly reset | Per-tenant, per-workflow, per-run, per-action scope hierarchy | -| **Enforcement** | Reject requests when key limit reached | Atomic reserve-commit — budget locked before action | -| **Rate limiting** | Global per-account (not configurable per-key) | Not a rate limiter — enforces per-action authority | -| **Model control** | Model allowlist and provider allowlist per guardrail | Model-agnostic — controls the action, not the model | -| **Action control** | None — controls which models, not what actions | [RISK_POINTS](/glossary#risk-points) — per-tool risk scoring and limits | -| **Multi-tenant** | Per-user and per-key enforcement | Tenant-scoped API keys with hierarchical budgets | -| **Budget hierarchy** | Multiple guardrails checked independently; lowest limit wins | Hierarchical scopes — tenant, workspace, workflow, agent, toolset | +| **Budget model** | Workspace spend limits for daily, weekly, monthly, and lifetime intervals; member/key guardrails | Tenant and subject scopes such as workspace, app, workflow, agent, and toolset | +| **Enforcement** | Preflight gateway check; already-dispatched requests can cause slight overage | Atomic estimate reservation before protected work, followed by actual-cost commit | +| **Coverage** | Requests routed through the OpenRouter inference gateway | Any application operation explicitly instrumented by the host | +| **Model control** | Model/provider allowlists and routing policies | Returns configured cap fields; the host maps and enforces them | +| **Action control** | No native authorization for downstream application tools | Caller-assigned [RISK_POINTS](/glossary#risk-points) budget; application authorization remains separate | +| **Multi-tenant** | Organization, workspace, member, and key controls | Tenant-scoped keys plus subject-scoped budgets | +| **Budget hierarchy** | Workspace interval budgets and inherited/assigned guardrails | Deepest matching configured subject scope | | **Alerts** | Usage dashboard + key credit/usage introspection | Webhook events on budget state transitions (programmatic, PagerDuty/Slack) | -| **Concurrency safety** | Not documented | Atomic Lua-scripted reservations (zero TOCTOU drift) | +| **Concurrency behavior** | Preflight enforcement; in-flight inference may slightly exceed a limit | Reservation atomically consumes available budget before work starts | ## Where OpenRouter's guardrails work well OpenRouter's guardrails system provides: -- **Per-key spending caps** that reset on configurable intervals (daily, weekly, monthly) -- **Model restrictions** — allowlist which models and providers a key can access -- **Data privacy controls** — restrict data handling per guardrail +- **Workspace budgets** for daily, weekly, monthly, and lifetime spend +- **Guardrail assignment and inheritance** across workspaces, members, and keys +- **Model and provider restrictions** +- **Data controls** including zero-data-retention and sensitive-information policies +- **Prompt filters** including regex-based prompt-injection controls - **Hard enforcement** — requests are rejected when the limit is reached -- **Hierarchy** — multiple guardrails stack; the lowest limit wins -- **Programmatic key management** — create, update, disable keys via API -For teams routing all LLM calls through OpenRouter, this provides meaningful cost guardrails at the gateway level. +For teams routing all inference through OpenRouter, this provides meaningful cost and data-policy enforcement at the gateway. ## Where the gaps appear -### 1. Organization/key caps vs. hierarchical runtime budgets +### 1. Inference workspaces vs. application execution scopes -OpenRouter provides budget controls at the organization, member, and API key level — with guardrails that stack (strictest wins). This is meaningful governance for teams managing API access centrally. +OpenRouter workspace budgets are shared across requests and can enforce daily, weekly, monthly, and lifetime limits. Member and key guardrails add finer access controls. This is meaningful governance for centrally managed inference. -However, these are gateway-layer caps, not runtime authority scopes. In a multi-agent system, you might have 10 agents sharing a $100 workspace budget. With OpenRouter, you'd need to pre-allocate per key and hope usage is evenly distributed. If agent A uses $2 and agent B needs $15, B is blocked even though the overall allocation has capacity remaining. - -Cycles' hierarchical scopes handle this differently: a workspace budget is shared across all agents in the workspace, with per-agent sub-budgets optionally carved out. The scope hierarchy (tenant → workspace → workflow → agent) derives enforcement atomically at every reservation. +The boundary is still OpenRouter inference. An agent workflow may also pay for search, browsers, sandboxes, SaaS APIs, or database operations, and may need separate limits per workflow or run even when calls share one gateway workspace. Cycles can apply budgets to those application-defined scopes and operations, provided the host instruments them. ### 2. No action-level control -OpenRouter controls which **models** a key can access. It cannot control what **tools** an agent invokes or what **side effects** those tools produce. +OpenRouter controls inference requests, models, providers, prompts, and data policies. It does not authorize downstream application tools or their side effects. -An agent routed through OpenRouter that sends 200 customer emails costs pennies in tokens. OpenRouter's spending cap wouldn't trigger. Cycles' [RISK_POINTS](/how-to/assigning-risk-points-to-agent-tools) budget would — because each `send_email` costs 40 risk points regardless of token cost. +For example, a host can assign a `RISK_POINTS` estimate to each email attempt and require a Cycles reservation before invoking the email provider. Cycles bounds the submitted cumulative exposure; the host still decides whether the email is authorized and enforces the tool call. ### 3. No graduated enforcement or programmatic alerts @@ -61,23 +59,21 @@ OpenRouter offers dashboard-level usage alerts and per-key activity logs. But en Cycles provides [three-way decisions](/glossary#three-way-decision): ALLOW, ALLOW_WITH_CAPS (proceed with constraints like model downgrade or tool restrictions), and DENY. Plus webhook events on budget state transitions (`budget.exhausted`, `budget.over_limit_entered`) that integrate with PagerDuty, Slack, and automated remediation pipelines. -### 4. No reserve-commit lifecycle - -OpenRouter tracks spend based on completed requests. The cost is known after the response arrives, not before. If a long response exceeds the remaining budget, the spend has already happened. +### 4. Preflight spend checks vs. reserve-commit -Cycles [reserves budget before the action](/blog/what-is-runtime-authority-for-ai-agents) based on an estimate, executes only if approved, and commits the actual cost after. The budget cannot be silently drained by concurrent requests. If actual cost exceeds the estimate, the overage is tracked as debt and surfaced immediately. +OpenRouter checks workspace spend before routing, but requests already in flight complete. Its documentation notes that actual workspace spend can therefore slightly exceed a limit before the next request is blocked. -### 5. Rate limits are global, not configurable +Cycles [reserves an estimate before the action](/blog/what-is-runtime-authority-for-ai-agents) and commits the actual amount afterward. Concurrent reservations atomically consume available capacity. Commit overages follow the configured overage policy and may be rejected, charged from remaining capacity, or recorded as debt. -OpenRouter rate limits are global per-account — creating additional API keys doesn't increase rate capacity. This means you can't allocate different rate limits to different agents or use cases. +### 5. Gateway-only coverage -Cycles doesn't rate-limit at all — it enforces budget authority. But this means you control the throughput profile through budget allocation rather than being constrained by a global rate limit you can't configure. +OpenRouter can only evaluate traffic that reaches its gateway. Cycles is provider-independent and unit-independent, so the same budget protocol can cover model calls from multiple gateways alongside explicitly instrumented non-LLM operations. Cycles is not a rate limiter and does not discover uninstrumented work. ### 6. No delegation attenuation When agent A spawns sub-agent B via an LLM call, OpenRouter sees both as independent requests from the same key. There's no way to enforce that B has a smaller budget than A, or that B can only access a subset of A's tools. -Cycles supports [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) as a pattern: hierarchical scopes let you carve a narrower sub-budget for each delegation hop, so a sub-agent draws from a smaller allocation than its parent. Action masks and delegation-depth limits are orchestration logic you build on top of those scopes — not protocol primitives. +Cycles supports [authority attenuation](/blog/agent-delegation-chains-authority-attenuation-not-trust-propagation) as an application pattern: provision a narrower child agent ledger and submit both the shared ancestor and child scopes on every protected call. Cycles does not transfer balances at handoff; action masks and delegation-depth limits remain orchestration logic. ## Better together: OpenRouter + Cycles @@ -87,9 +83,9 @@ OpenRouter and Cycles operate at different layers. Running both gives you capabi Request flow: Agent decides to act → Cycles: "Should this action happen?" (budget authority, RISK_POINTS) - → OpenRouter: "Which model handles this?" (routing, provider selection) + → OpenRouter: check workspace/guardrail policy, then route the model call → Provider: Execute the call - → OpenRouter: Track cost, check key cap + → OpenRouter: Track inference usage → Cycles: Commit actual cost, release unused reservation ``` @@ -100,19 +96,19 @@ Request flow: | Unified access to hundreds of models | OpenRouter | | Automatic provider selection and pricing | OpenRouter | | Pre-execution budget authority | Cycles | -| Action-level RISK_POINTS control | Cycles | -| Per-key spending caps with reset | OpenRouter | +| Caller-assigned action-exposure budgets | Cycles; host authorizes the action | +| Workspace interval budgets and key/member guardrails | OpenRouter | | Hierarchical tenant/workflow/agent budgets | Cycles | | Model and provider allowlists | OpenRouter | -| Tool allowlists and denylists | Cycles | +| Tool allowlists and denylists | Cycles returns configured cap fields; the host enforces them | | Credit management | OpenRouter | | Delegation attenuation for sub-agents | Cycles (pattern via hierarchical scopes) | **Concrete integration scenario:** OpenRouter provides access to many models through a single API. Cycles decides whether an instrumented action can reserve against the configured budget. If the deepest matching budget supplies `ALLOW_WITH_CAPS`, your application can map a returned cap to a cheaper OpenRouter model. OpenRouter handles routing; Cycles handles the budget reservation. The current Cycles server neither infers a risk profile nor adds caps automatically as the balance falls. -**Another scenario:** OpenRouter guardrails restrict a key to only GPT-4o-mini and Claude Haiku (cheaper models). Cycles' RISK_POINTS budget independently restricts the same agent to 2 emails and 0 deploys per run. Model access (OpenRouter) and action access (Cycles) are enforced independently — both constraints must pass. +**Another scenario:** OpenRouter guardrails restrict a key to lower-cost models. The application authorizes email but not deploy, assigns each email a caller-defined `RISK_POINTS` amount, and requires a Cycles reservation before sending. OpenRouter enforces model access, the application enforces tool access, and Cycles bounds the submitted email exposure. -OpenRouter selects the model and provider. Cycles decides whether the action should happen at all. They're complementary, not competing. +OpenRouter selects the model and provider. Cycles decides whether the configured budget can cover the submitted action estimate. The host makes and enforces the broader authorization decision. The layers are complementary, not competing. ## What Cycles does not do @@ -121,8 +117,8 @@ Cycles is not a router or model aggregator. It doesn't provide access to hundred ## When OpenRouter alone is enough - All your agents do is make LLM calls (no side-effecting tools) -- Per-key spending caps with daily/monthly resets are sufficient -- You don't have concurrent agents sharing budgets +- Workspace budgets and assigned guardrails match the required organizational boundaries +- Slight overage from already-dispatched inference requests is acceptable - You don't need graduated enforcement (just hard allow/deny) - Single-team deployment without multi-tenant isolation needs @@ -137,7 +133,7 @@ Cycles is not a router or model aggregator. It doesn't provide access to hundred ## Sources -Feature claims verified against [openrouter.ai/docs](https://openrouter.ai/docs/guides/features/guardrails) as of April 2026. Cycles claims based on v0.1.25. These tools evolve quickly — check the linked docs for the latest. +Feature claims verified against OpenRouter's [guardrails](https://openrouter.ai/docs/guides/features/guardrails) and [workspace budgets](https://openrouter.ai/docs/guides/features/workspaces/workspace-budgets) documentation on July 24, 2026. Cycles claims are based on v0.1.25. These tools evolve quickly—check the linked docs for the latest. ## Related diff --git a/concepts/cycles-vs-provider-spending-caps.md b/concepts/cycles-vs-provider-spending-caps.md index dee47c95..2aa988b4 100644 --- a/concepts/cycles-vs-provider-spending-caps.md +++ b/concepts/cycles-vs-provider-spending-caps.md @@ -1,105 +1,90 @@ --- -title: "Cycles vs Provider Spending Caps: Why Platform Limits Are Not Enough" -description: "OpenAI, Anthropic, and Google all offer spending limits — but they are monthly, org-wide, and delayed. See why teams need finer-grained runtime authority." +title: "Cycles vs Provider Cost Controls: Where Runtime Budgets Fit" +description: "Compare provider budgets, prepaid credits, and quotas with application-scoped runtime budgets for runs, tenants, workflows, and instrumented operations." --- # Cycles vs Provider Spending Caps: Why Platform Limits Are Not Enough -Every major LLM provider offers some form of spending control. +Every major LLM provider offers controls that affect cost or capacity. -OpenAI has usage limits. Anthropic has spending limits. Google Cloud has budget alerts and quotas. AWS Bedrock has service quotas. +Depending on the vendor and account type, those controls can include soft budget alerts, prepaid credits, model rate limits, project or workspace attribution, throughput quotas, and billing automation. -These exist for good reason. They prevent surprise bills at the organizational level. They are a safety net. +These controls are useful. They can improve visibility, constrain throughput, or stop provider access when a credit or account limit is reached. -But a safety net is not a governance system. +They do not automatically create a per-run or per-tool budget model inside your application. -Provider spending caps operate at the wrong granularity, the wrong timing, and the wrong scope for teams running autonomous AI agents in production. +The relevant question is whether their scope, timing, and failure behavior match the boundary your agent application needs to enforce. -> **Run the numbers for your workload:** [Cost Calculator →](/calculators/claude-vs-gpt-cost-standalone) — provider caps fire at the org level; the calculator shows per-tenant exposure when one tenant burns the shared headroom. +> **Run the numbers for your workload:** [Cost Calculator →](/calculators/claude-vs-gpt-cost-standalone) — use the calculator to model workload cost, then decide which controls belong at the provider, gateway, and application boundaries. ## What provider caps offer Provider spending caps vary by vendor, but the general pattern is consistent. -### OpenAI usage limits +### OpenAI spend alerts, hard spend limits, and billing controls -OpenAI allows organizations to set monthly spend limits on their API usage. When the limit is reached, API calls are rejected. Organizations can also set per-project or per-API-key budgets. Usage data is available through a dashboard with some reporting delay. +OpenAI supports monthly spend alerts and optional hard spend limits at organization and project scope. Alerts notify while traffic continues. When tracked spend reaches an applicable hard limit, affected requests return `429 insufficient_quota`; OpenAI notes that enforcement is not instantaneous, so recorded spend can slightly exceed the configured amount. Projects also support model-specific rate limits and model access controls. Prepaid billing is separate and can stop API access when credits are exhausted, although its cutoff can also be delayed. -### Anthropic spending limits +### Anthropic credits, usage tiers, and workspaces -Anthropic provides workspace-level spending limits. Organizations can set monthly caps that hard-block API access once reached. Usage is tracked at the workspace level and visible through the console. +Anthropic bills API usage through prepaid usage credits and stops API access when those credits run out. Its Console reports cost and usage by workspace, model, and API key, while organization usage tiers impose spend and rate limits. Auto-reload settings can change whether the prepaid balance behaves like a fixed ceiling. ### Google Cloud budget alerts -Google Cloud offers budget alerts for Vertex AI and other services. These are primarily notification-based — they send alerts at defined thresholds (50%, 90%, 100%) but do not automatically block usage. Actual enforcement requires additional configuration through quota policies. +Google Cloud budgets are notification-oriented and do not automatically cap Vertex AI spend. Quotas constrain capacity rather than dollars; for newer generative models, Dynamic Shared Quota has no customer-configured predefined usage limit. Provisioned Throughput provides a separate fixed-capacity purchasing model. ### AWS Bedrock service quotas -AWS provides service quotas that limit tokens per minute and requests per minute for Bedrock models. These are throughput limits, not spend limits. Cost governance requires separate AWS Budgets configuration, which is alert-based with optional automated actions. +AWS provides Bedrock service quotas that constrain request or token throughput. AWS Budgets is a separate billing service with alerts and configurable actions; those controls do not inherently represent an individual agent run or application tenant. ### The common thread -All of these share a similar shape: +The exact behavior differs by product and plan, but provider-native controls generally share several boundaries: -- Organization-wide or workspace-wide scope -- Monthly or daily granularity -- Delayed usage reporting -- Binary enforcement (all traffic blocked, or nothing) -- Single-provider visibility +- They govern traffic or billing inside one provider. +- Their identities are provider projects, workspaces, accounts, keys, or cloud projects—not necessarily your application's tenant and run hierarchy. +- Budget reporting and hard-stop semantics vary; a field labeled “budget” may be an alert threshold rather than a cap. +- Rate and throughput quotas bound request volume or capacity, not arbitrary application-side side effects. -For basic protection against runaway API bills, they work. That is their purpose. +They remain valuable controls. The gap appears when the application needs a cumulative budget for a business-defined scope or for work that is not itself a provider request. The problem starts when teams need more than basic protection. The single-provider point in particular has its own structural argument — see [Agents Are Cross-Cutting. Your Controls Aren't.](/blog/agents-are-cross-cutting-your-controls-arent) for why a control that lives inside one provider can't reach across an agent that spans many. ## Why provider caps are not sufficient -### Monthly or daily granularity — not per-run, not per-workflow +### Provider periods are not application runs -Provider caps operate on calendar time. You set a monthly limit or a daily limit. +Many provider cost controls use calendar windows, credit balances, usage tiers, or throughput intervals. But autonomous agents operate in runs. A single agent run might take 30 seconds and make 15 LLM calls. Another run might take 4 hours and make 300 calls. The cost difference between these runs can be orders of magnitude. -A monthly cap cannot express: "This run may spend at most $5." It can only express: "This organization may spend at most $10,000 this month." +A provider-level monthly threshold does not, by itself, express: "This run may consume at most $5 of the application budget." Your application or gateway needs a run identity and an enforcement rule for that boundary. -That means a single runaway run can consume a significant portion of the monthly budget before anyone notices. The cap will eventually trigger, but not before damage is done. +Without that finer-grained boundary, a single runaway run can consume a significant share of a broader provider allowance even when the provider control behaves exactly as documented. -### Org-wide scope — not per-tenant, not per-user, not per-workspace +### Provider identities may not match application tenants -Provider caps apply to the organization or API key. They cannot distinguish between tenants sharing the same infrastructure. +Provider controls may apply at an organization, project, workspace, cloud-project, model, or key scope. Those scopes can help isolate workloads, but they do not automatically map shared credentials to your application's customer, workflow, run, or tool identities. -If you run a multi-tenant platform where each customer gets their own AI agent, a provider cap cannot enforce per-customer budgets. One customer's runaway agent can exhaust the cap for all customers. +If a multi-tenant platform sends every customer's agent traffic through one shared provider identity, the provider cannot infer the application's per-customer budgets. A team can create separate provider projects, workspaces, or keys where supported, but that mapping is an application design choice rather than an automatic tenant model. -This is the most common gap teams discover. They have 50 tenants sharing one OpenAI API key. The monthly cap is set at $50,000. One tenant's agent loops overnight and consumes $8,000. The provider cap does not know or care which tenant caused it. It only knows the organization-level total. +For example, consider an illustrative platform with 50 tenants sharing one provider project and a $50,000 soft monthly threshold. If one tenant's agent consumes $8,000, provider reporting can attribute the spend to the shared project but cannot infer the platform's tenant boundary unless the platform supplies a distinct provider identity or enforces that boundary elsewhere. ### Delayed enforcement -Provider usage data is not real-time. +Budget dashboards, billing exports, and alerts are not the same as an in-process admission decision. Providers document different reporting and cutoff behavior. OpenAI now distinguishes soft spend alerts from optional hard organization/project spend limits, and documents that hard-limit enforcement is not instantaneous. -OpenAI usage updates can lag by minutes. Anthropic and Google have similar delays. AWS Bedrock usage data flows through CloudWatch, which adds its own latency. - -That means a cap set at $1,000 might not trigger until actual spend reaches $1,050 or $1,100, depending on the velocity of requests and the reporting delay. - -For autonomous agents making rapid successive calls, this delay can be significant. An agent can make dozens of expensive calls in the minutes between usage updates. +If an application polls those reporting surfaces and reacts later, work can continue between the underlying usage and the application's response. Request-time rate limits or exhausted-credit checks are different controls and should not be described as post-hoc. ### No pre-execution check -Provider caps are reactive. - -The model call happens. The tokens are consumed. The cost is recorded. Then the cap is checked. +Providers can reject a request at their own boundary because of rate, credit, or account limits. What they generally do not expose is an application-defined reserve-commit lifecycle that asks, "Does this tenant's current run have enough of this budget unit for the estimated operation?" and holds that amount while the work is in flight. -There is no mechanism to ask: "Does this organization have enough budget for this specific call?" before the call executes. - -That means the system always incurs at least one over-budget call before enforcement kicks in. Under high concurrency, it can incur many. - -Cycles inverts this. Budget is reserved before execution. If the budget is insufficient, the call never happens. Zero cost is incurred for denied requests. +For calls instrumented through Cycles, the application reserves its submitted estimate before execution. If the reservation is denied and the caller honors that denial, that protected call is not sent. Traffic that bypasses the integration is outside this guarantee. ### No graceful degradation -When a provider cap triggers, all API calls fail. - -There is no middle ground. The system goes from fully operational to completely blocked. Every agent, every workflow, every tenant — all stopped at once. - -This is the equivalent of a circuit breaker with no dimmer switch. +When a hard provider limit rejects a request, the provider does not know which application-specific fallback is safe. The application can still route to another model, use a cache, or reduce work, but it must implement that policy. Production systems need nuance: @@ -109,9 +94,7 @@ Production systems need nuance: - Serve cached responses instead of live inference - Degrade gracefully for low-priority workflows while keeping high-priority ones running -Provider caps cannot express any of this. They have one response: block everything. - -Cycles supports three-way decisions (ALLOW, ALLOW_WITH_CAPS, DENY) that enable graceful degradation at the per-action level. A workflow can continue with reduced capability instead of failing completely. +Cycles can return `ALLOW`, configured `ALLOW_WITH_CAPS`, or `DENY` for submitted operations. The caller must translate caps into behavior—such as a cheaper route or smaller context—and must authorize the action separately. ### Multi-provider blind spots @@ -128,37 +111,37 @@ Each provider tracks its own usage independently. None of them know about spend A team that has budgeted $500 per day across all providers has no single place to enforce that limit. OpenAI knows about OpenAI spend. Anthropic knows about Anthropic spend. Neither knows the total. -Cycles aggregates budget across providers. A single reservation can account for the expected cost of any model call, regardless of which provider serves it. The budget boundary is defined by the application, not by the vendor. +Cycles can account for submitted estimates across providers when every relevant path is instrumented into the same budget. The budget boundary is defined by the application, not inferred from provider billing. ## Comparison -| | Provider Cap | Cycles | +| | Provider controls | Cycles | |---|---|---| -| **Granularity** | Monthly or daily, per-organization | Per-tenant, per-workspace, per-workflow, per-agent | -| **Scope** | Organization or API key | Hierarchical — tenant → workspace → app → workflow → agent → toolset | -| **Enforcement timing** | Post-usage with reporting delay | Pre-execution — budget reserved before the call | -| **Multi-provider** | Single provider only | Aggregates across all providers in one budget | -| **Degradation** | Binary — all traffic blocked or all allowed | Three-way — ALLOW, ALLOW_WITH_CAPS, DENY | -| **Protocol** | Vendor-specific dashboard and API | Open protocol with reserve/commit/release lifecycle | -| **Concurrency handling** | Delayed counter — race conditions under load | Atomic reservations — no overspend under concurrency | -| **Per-tenant enforcement** | Not supported | Built-in hierarchical scopes | -| **Retry awareness** | None — each retry is a new charge | Idempotent reservations — retries do not double-spend | +| **Granularity** | Vendor-dependent: project/workspace/account windows, credits, or quotas | Submitted operation against configured tenant and subject scopes | +| **Scope** | Provider organization, project, workspace, cloud project, model, or key | Tenant plus caller-supplied subject hierarchy | +| **Enforcement timing** | Vendor-dependent: soft alert, request-time quota, credit check, or billing action | Pre-execution reservation for instrumented work | +| **Multi-provider** | One provider's traffic and billing | Shared budget only when callers submit all relevant provider paths | +| **Degradation** | Provider rejection or provider-specific policy; application chooses fallback | `ALLOW`, configured `ALLOW_WITH_CAPS`, or `DENY`; caller applies caps | +| **Protocol** | Vendor-specific dashboard and API | Open protocol with reserve-commit-release lifecycle | +| **Concurrency handling** | Vendor-specific | Atomic reservation mutation across the matching Cycles budget scopes | +| **Per-tenant enforcement** | Possible when provider identities and policies map to application tenants | Tenant-scoped keys and caller-supplied subject scopes | +| **Retry awareness** | Provider billing and idempotency semantics vary | Reusing the same Cycles idempotency key and request body deduplicates the budget mutation | ## The delay problem in detail -The reporting delay deserves special attention because it is the subtlest failure mode. +Reporting delay matters when an application treats a dashboard, export, or alert as its enforcement loop. -Consider an agent making calls at a steady rate of one per second. Each call costs approximately $0.10. The provider cap is set at $100. +Consider an application that reacts only to a hypothetical usage report delayed by 60 seconds. Its agent makes calls at one per second, each assumed to cost $0.10, and its application threshold is $100. -At second 1,000, the agent has spent $100. But the provider's usage dashboard reflects spend as of second 940 — a 60-second reporting delay. The cap has not triggered. +At second 1,000, the agent has spent $100. But the polled report reflects spend as of second 940, so the application has not reacted. The agent makes 60 more calls before the cap catches up. That is $6 of overspend — a 6% overrun. Now increase the call rate. Five calls per second, each costing $0.50. At the same 60-second delay, that is 300 calls and $150 of overspend on a $100 cap — a 150% overrun. -This is not a bug. It is an inherent limitation of post-hoc enforcement with delayed reporting. +This is an illustrative property of that polling design, not a measured overrun or a claim about every provider limit. -Cycles avoids this entirely. Budget is reserved before execution. The reservation is atomic and immediate. There is no delay between the budget check and the budget decrement. +An instrumented Cycles path instead reserves the submitted estimate before execution. The reservation mutation is atomic across the matching Cycles scopes; the caller still needs accurate estimates, consistent integration, and settlement of actual usage. ## When to use both @@ -166,37 +149,35 @@ Provider caps and Cycles are not mutually exclusive. They serve as different lay ### Keep provider caps as a safety net -Provider caps are your last line of defense. If everything else fails — if Cycles is misconfigured, if a bug bypasses the budget check, if a new service is deployed without integration — the provider cap catches it. +Provider account, credit, quota, and billing controls remain an independent line of defense. Which of them is a hard stop depends on the provider and configuration. -Set your provider caps at a level that represents your absolute maximum acceptable spend. This is the "something has gone badly wrong" threshold. +Configure those controls according to their documented semantics. Do not treat a soft budget alert as an absolute maximum. ### Use Cycles for operational control -Cycles is your operational layer. It enforces the budgets that matter to your business: +Cycles can supply the operational budget decision for instrumented paths: - Per-tenant limits that align with pricing tiers - Per-workflow limits that prevent individual runs from spiraling - Per-run limits that bound the cost of any single agent execution -- Degradation policies that keep the system running under budget pressure +- Configured caps that the application can map to degradation behavior -This is the layer that runs day-to-day. It handles the normal case, the edge cases, and the concurrent cases. +This layer supplies the budget decision; the application remains responsible for complete instrumentation, authorization, and fallback behavior. ### Defense in depth The combination creates defense in depth: -1. **Cycles** handles per-tenant, per-workflow, per-agent budget enforcement with pre-execution checks. This is the primary control layer. -2. **Provider caps** handle organizational safety nets. They catch anything that slips through the primary layer. - -If Cycles is working correctly, provider caps should never trigger. They exist for the case where Cycles is not working correctly. +1. **Cycles** handles configured budgets for instrumented operations with pre-execution checks. +2. **Provider controls** independently constrain or report the provider traffic they cover. -That is good engineering. Multiple independent layers of control, each catching different failure modes. +Multiple independent controls can catch different failure modes, but their exact guarantees come from their documented configuration—not from their position in this diagram. ## Migration path Teams that currently rely on provider caps alone can adopt Cycles incrementally. -**Step 1: Shadow mode.** Deploy Cycles in shadow mode. It evaluates budget decisions but does not enforce them. Log the decisions. Compare what Cycles would have done against what actually happened. +**Step 1: Shadow mode.** Deploy Cycles in shadow mode. It evaluates budget decisions but does not enforce or persist the dry-run result. Have the application log the result and compare it with what actually happened. **Step 2: Validate.** Review the shadow mode data. Are the budget allocations correct? Are the scope hierarchies right? Would enforcement have blocked legitimate work? Adjust the configuration. @@ -204,11 +185,21 @@ Teams that currently rely on provider caps alone can adopt Cycles incrementally. **Step 4: Expand enforcement.** Gradually move more workflows from shadow mode to enforcement as confidence builds. -**Step 5: Adjust provider caps.** Once Cycles is handling operational budget control, raise your provider caps to be true safety nets — generous enough to never trigger under normal operation, strict enough to catch genuine failures. +**Step 5: Recheck provider controls.** Confirm that alerts, credits, quotas, and billing actions still match the organization's independent safety requirements. + +The practical result is layered control: keep the provider-native protections that fit your account, and add application-scoped budgets where provider identities and windows do not express the boundary you need. + +## Sources -Provider caps become your fire alarm. Cycles becomes your thermostat. +Provider behavior was rechecked on July 24, 2026: -One prevents catastrophe. The other maintains comfortable operating conditions. +- [OpenAI spend limits](https://developers.openai.com/api/docs/guides/spend-limits) +- [OpenAI projects and limits](https://help.openai.com/en/articles/9186755-managing-projects-in-the-api-platform) +- [OpenAI prepaid billing](https://help.openai.com/en/articles/8264644-what-is-prepaid-billing) +- [Anthropic API billing and usage credits](https://support.anthropic.com/en/articles/8977456-how-do-i-pay-for-my-api-usage) +- [Anthropic cost and usage reporting](https://support.anthropic.com/en/articles/9534590-cost-and-usage-reporting-in-console) +- [Google Cloud generative AI throughput quota](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/resources/throughput-quota) +- [AWS Bedrock service quotas](https://docs.aws.amazon.com/bedrock/latest/userguide/quotas.html) ## Next steps @@ -217,4 +208,4 @@ One prevents catastrophe. The other maintains comfortable operating conditions. - Integrate with Python using the [Python Client](/quickstart/getting-started-with-the-python-client) - Integrate with TypeScript using the [TypeScript Client](/quickstart/getting-started-with-the-typescript-client) - Try the [End-to-End Tutorial](/quickstart/end-to-end-tutorial) — zero to a working budget-guarded LLM call in ten minutes -- [Multi-Tenant AI Cost Control](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) — why provider caps fail in multi-tenant systems and what to use instead +- [Multi-Tenant AI Cost Control](/blog/multi-tenant-ai-cost-control-per-tenant-budgets-quotas-isolation) — how provider and application scopes differ in shared systems diff --git a/concepts/cycles-vs-rate-limiting.md b/concepts/cycles-vs-rate-limiting.md index c63f0adf..6c411de3 100644 --- a/concepts/cycles-vs-rate-limiting.md +++ b/concepts/cycles-vs-rate-limiting.md @@ -133,7 +133,7 @@ This is a classic time-of-check-to-time-of-use (TOCTOU) race condition. Rate lim Cycles handles this with atomic reservations. When the first thread reserves $5, that budget is immediately unavailable to the second thread. The second thread's reservation attempt sees the reduced balance and can be denied or degraded. -No race condition. No overspend. +No oversubscription of the submitted estimates across matching Cycles ledgers. Actual provider spend still depends on estimate quality, complete instrumentation, and settlement policy. ## When to use both together @@ -186,11 +186,11 @@ Rate limiting and runtime authority are orthogonal controls. Rate limiting governs the speed of requests. It prevents bursts and abuse. It is stateless in the sense that it does not track what those requests cost in aggregate. -Runtime authority governs the total exposure of execution. It prevents overspend and cost runaway. It is stateful — it tracks reservations, commits, and remaining balances across scopes. +Runtime budget authority governs submitted cumulative exposure. It is stateful — it tracks reservations, commits, and remaining balances across configured scopes. An AI agent that respects rate limits can still create unbounded cost. -An AI agent governed by Cycles cannot exceed its budget envelope — overages under `ALLOW_WITH_OVERDRAFT` surface as bounded, tracked debt — regardless of how fast or slow it operates. +For paths that must reserve before execution, Cycles prevents concurrent submitted estimates from oversubscribing the matching ledgers. Actual usage can exceed an estimate and is handled by the configured commit-overage policy; bypass traffic remains outside the boundary. Rate limiting answers **how fast?** diff --git a/concepts/exposure-why-rate-limits-leave-agents-unbounded.md b/concepts/exposure-why-rate-limits-leave-agents-unbounded.md index 8a427861..8aaee11f 100644 --- a/concepts/exposure-why-rate-limits-leave-agents-unbounded.md +++ b/concepts/exposure-why-rate-limits-leave-agents-unbounded.md @@ -7,7 +7,7 @@ description: "Exposure is the gap between what an agent does and when enforcemen Exposure is the total cost, risk, or damage an autonomous system can create before something stops it. -It is not the same as spend. A support agent that sends 200 customer emails costs $1.40 in model tokens — but creates $50,000 in lost pipeline. The spend was trivial. The exposure was catastrophic. +It is not the same as spend. In an illustrative scenario, 200 mistaken customer emails cost about $1.40 in model tokens while their unquantified customer and business impact can be much larger. Low spend does not imply low exposure. > **Quantify exposure for your agent:** [Blast Radius Risk Calculator →](/calculators/ai-agent-blast-radius-standalone) — model action classes by reversibility and visibility; the catastrophic *irreversible + public* class is what rate limits leave unbounded. @@ -18,7 +18,7 @@ Every autonomous system has two numbers: 1. **Spend** — what it costs to run (tokens, compute, API fees) 2. **Exposure** — what it can do before it is stopped (emails sent, records modified, deploys triggered, dollars committed) -Most cost controls target spend. Rate limits cap requests per second. Provider spending caps pause billing at a monthly threshold. Observability dashboards show what happened after the fact. +Most cost controls target one dimension. Rate limits cap throughput. Provider controls may alert on budgets, consume prepaid credits, or enforce account and capacity limits. Observability dashboards report what happened. None automatically represents every application run, tenant, or side effect. None of these bound exposure, because none of them enforce limits **before** the next action executes. @@ -38,7 +38,7 @@ Observability is essential — but it observes exposure. It does not bound it. S Cycles bounds exposure by requiring agents to **reserve** budget before execution and **commit** the actual cost afterward. -The reservation is the enforcement point. If the budget is exhausted, the reservation is denied, and the action never executes. The maximum possible damage is capped at the reserved amount — not at infinity. +The reservation is the enforcement point. If sufficient budget is unavailable, a live reservation fails and correctly integrated protected work does not execute. This bounds the submitted cumulative exposure at that boundary; it does not cap all possible business harm, validate the estimate, or cover work that bypasses instrumentation. This applies to both financial exposure (USD, tokens) and operational exposure (risk points). A toolset-scoped budget denominated in RISK_POINTS can cap the number of consequential actions an agent takes, regardless of their dollar cost. See [Action Authority](/concepts/action-authority-controlling-what-agents-do). diff --git a/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority.md b/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority.md index f7d22a86..b31b6113 100644 --- a/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority.md +++ b/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority.md @@ -283,7 +283,7 @@ Enforcement does not have to mean “deny everything aggressively.” A mature control model often includes multiple outcomes: - allow normally -- allow in shadow mode +- return a hypothetical dry-run decision for the application to log - degrade to a smaller model - disable costly tools - switch to read-only behavior @@ -317,7 +317,7 @@ Teams often need to observe policy against real workloads before turning on hard Shadow mode is often the bridge between observability and enforcement. -It lets teams ask: +With `dry_run: true`, Cycles returns a hypothetical decision without creating a reservation or modifying balances. The application must retain that response and correlate it with actual usage. Those records let teams ask: - what would have been denied? - which runs would have exceeded budget? @@ -339,13 +339,13 @@ That is a much safer operational path. Once a system has a real runtime authority, several things change. -### Incidents become easier to bound +### Instrumented incidents become easier to bound -Instead of relying only on dashboards and operator reaction, the system can stop or degrade work earlier. +Instead of relying only on dashboards and operator reaction, a mandatory application boundary can stop or degrade protected work when a budget request fails. ### Policy becomes explicit -Instead of scattered heuristics across code, the platform gains a clearer model of what is allowed at tenant, workflow, and run levels. +Instead of scattered budget heuristics across code, the platform gains a clearer model of submitted exposure at tenant and workflow levels. An application can use a run ID as `subjects.workflow` when it needs one ledger per execution; a run ID in `dimensions` is attribution only. ### Teams can reason about autonomy operationally @@ -394,9 +394,9 @@ At that point, the missing piece is clear. The platform does not need another chart. It needs a way to say: -- this run may only consume this much +- protected actions in this run share a workflow ledger with this allocation - this tenant has this much remaining -- this workflow should degrade after crossing this threshold +- this application should degrade the workflow when a reservation is denied or returned caps require it - this next action may not proceed unless budget is reserved first That is the shift from observability to enforcement. diff --git a/concepts/how-cycles-compares-to-rate-limiters-observability-provider-caps-in-app-counters-and-job-schedulers.md b/concepts/how-cycles-compares-to-rate-limiters-observability-provider-caps-in-app-counters-and-job-schedulers.md index 6bf7d9d8..8be0acba 100644 --- a/concepts/how-cycles-compares-to-rate-limiters-observability-provider-caps-in-app-counters-and-job-schedulers.md +++ b/concepts/how-cycles-compares-to-rate-limiters-observability-provider-caps-in-app-counters-and-job-schedulers.md @@ -10,8 +10,8 @@ description: "See how Cycles differs from rate limiters, observability tools, pr | Approach | What it controls | Pre-execution? | Per-tenant? | Cost-aware? | Degradation? | |---|---|:---:|:---:|:---:|:---:| | **Rate limiter** | Request velocity | Velocity only | Partial | No | No | -| **Observability** | Post-hoc visibility | No | No | After the fact | No | -| **Provider cap** | Org-level spend | No (delayed) | No | Partial | No | +| **Observability** | Traces, metrics, and product-specific controls | Product/config dependent | Product/config dependent | Usually | Product/config dependent | +| **Provider controls** | Vendor spend, credits, or capacity | Soft or hard, vendor dependent | Provider identity only | Yes for covered usage | Application chooses fallback | | **In-app counter** | Custom metric | Partial | Partial | Partial | No | | **Job scheduler** | Execution timing | No | No | No | No | | **Cycles** | Bounded budget, risk exposure | Yes | Yes | Yes | Yes (ALLOW / ALLOW_WITH_CAPS / DENY) | @@ -155,40 +155,40 @@ But do not confuse explaining the past with governing the present. Most LLM providers offer some form of spending cap or usage limit. -These are typically: +Depending on the vendor and plan, these include: -- monthly dollar limits on an API key or organization -- hard caps that block all requests once the limit is hit +- monthly spend alerts or hard limits on an organization or project +- prepaid-credit cutoffs - daily or monthly spend alerts -- per-model token limits +- per-model request or token-rate limits ### Where provider caps fall short -Provider caps are coarse, global, and external to your application. +Provider controls are scoped to vendor-defined identities and are external to your application's own subject hierarchy. They operate at the wrong level of granularity for autonomous systems. -**No per-tenant enforcement.** -A provider cap applies to the entire organization or API key. It cannot distinguish between tenants, workflows, or runs. One runaway tenant can exhaust the cap for everyone. +**No automatic application-tenant enforcement.** +A provider project, workspace, account, or key can isolate traffic assigned to it. When multiple application tenants share that identity, however, the provider cannot infer their separate ledgers. One tenant can consume the shared allowance. -**No per-run or per-workflow limits.** -A provider cap cannot say "this workflow may only spend $5." It only knows about total organizational consumption. +**No automatic per-run or per-workflow limit.** +A team could dedicate a provider project or key to a workload where supported, but a shared provider boundary does not learn the application's workflow or run identity. -**Binary enforcement.** -When the cap is hit, all requests fail. There is no degradation, no per-action decision, no nuanced response. The system goes from fully operational to fully blocked. +**Provider-specific enforcement.** +Some controls only alert; others reject affected requests when a credit, quota, or hard spend limit binds. The provider does not know which application-specific degradation path is safe, so the application must choose the fallback. -**No reservation semantics.** -Provider caps do not support reserve-before-execute. They decrement a counter after usage. That means concurrent requests can race past the limit before it takes effect. +**No application reserve-commit semantics.** +Provider controls can reject at their request boundary, but they do not expose a reserve-commit lifecycle for an application-defined tenant/run estimate. For example, OpenAI documents that hard spend-limit enforcement is not instantaneous and tracked spend can slightly exceed the configured amount. -**Delayed accounting.** -Provider usage data is often updated with a delay of minutes to hours. That means the cap may not reflect real-time exposure accurately, especially under high concurrency. +**Timing varies.** +Alerts, dashboards, hard spend limits, credit checks, and rate quotas have different timing. Treat each according to its documented behavior rather than assuming every control is either immediate or delayed. **No lifecycle awareness.** -Provider caps do not know that a request is a retry, that the run has already used most of its budget, or that the workflow should degrade instead of continuing at full cost. +Provider controls do not infer that a request belongs to an application retry, how much a particular run has used, or which workflow-specific degradation is safe. ### How Cycles differs -Cycles provides fine-grained, application-aware budget enforcement. +Cycles provides budget state for caller-supplied application scopes. It operates at the level your system actually needs: @@ -201,19 +201,19 @@ It operates at the level your system actually needs: Budget is reserved before execution rather than inferred after usage occurs. -| | Provider budget cap | Cycles | +| | Provider controls | Cycles | |---|---|---| -| Scope | Organization or API key | Tenant, workspace, app, workflow, agent, toolset | -| Enforcement | Binary (all-or-nothing) | Three-way (ALLOW / ALLOW_WITH_CAPS / DENY) | -| Timing | Post-usage counter (often delayed) | Pre-execution reservation | -| Multi-tenant aware | No | Yes | -| Degradation support | No | Yes | -| Retry-safe | No | Yes (idempotent reservations) | +| Scope | Vendor organization, project, workspace, account, key, credit, or quota | Caller-supplied tenant, workspace, app, workflow, agent, toolset | +| Enforcement | Soft or hard, vendor/configuration dependent | Live reservation accepted with optional configured caps, or rejected | +| Timing | Alert, request-time rejection, or non-instantaneous spend cutoff | Pre-execution hold for instrumented work | +| Multi-tenant aware | Only through explicit provider-identity mapping | Yes, when the caller submits tenant scopes | +| Degradation support | Provider-specific; application chooses fallback | Configured caps returned; application applies fallback | +| Retry-safe | Provider-specific | Same idempotency key and body deduplicate a budget mutation | | Under your control | No (vendor-managed) | Yes (self-hosted, operator-defined) | -**Provider caps are a safety net of last resort.** +**Provider controls are an independent safety layer.** -They can prevent catastrophic overspend at the organization level. +Depending on their semantics, they can alert, constrain capacity, exhaust credits, or stop provider traffic at an organization or project boundary. But they are not a substitute for application-level budget governance. @@ -241,7 +241,7 @@ If two requests check the counter simultaneously, both may see "under budget" an Counters typically increment after execution. That means the system commits to work before knowing whether the budget can absorb it. If the model call costs more than expected, the counter reflects reality too late. **No hierarchical scopes.** -A counter per tenant is useful. But autonomous systems often need limits at multiple levels: tenant, workspace, workflow, run, and action. Building and maintaining hierarchical counters with correct rollup logic is significantly more complex than a single counter. +A counter per tenant is useful. But autonomous systems often need limits at multiple levels such as tenant, workspace, app, workflow, agent, and toolset. An application can key a workflow ledger per run, while action authorization remains a host concern. Building and maintaining hierarchical counters with correct rollup logic is significantly more complex than a single counter. **Fragile under retries.** If a request fails and retries, does the counter increment once or twice? If the retry uses a different code path, does it check the same counter? Most ad hoc counters do not handle retries cleanly. @@ -337,28 +337,26 @@ But pair it with a runtime authority so retries and fan-out do not become unboun The table below maps specific capabilities against each approach. -✅ = supported  ◐ = partial or manual effort  ✗ = not supported - | Capability | Rate limiter | Observability | Provider cap | In-app counter | Job scheduler | Cycles | -|---|:---:|:---:|:---:|:---:|:---:|:---:| -| Pre-execution budget check | ✗ | ✗ | ✗ | ◐ | ✗ | ✅ | -| Reserve → commit lifecycle | ✗ | ✗ | ✗ | ✗ | ✗ | ✅ | -| Per-tenant limits | ◐ | ✗ | ✗ | ◐ | ✗ | ✅ | -| Per-workflow / per-agent limits | ✗ | ✗ | ✗ | ◐ | ✗ | ✅ | -| Hierarchical scopes | ✗ | ✗ | ✗ | ✗ | ✗ | ✅ | -| Cost-aware decisions | ✗ | ◐ | ◐ | ◐ | ✗ | ✅ | -| Retry / idempotency safety | ✗ | ✗ | ✗ | ✗ | ◐ | ✅ | -| Graceful degradation (ALLOW_WITH_CAPS) | ✗ | ✗ | ✗ | ✗ | ✗ | ✅ | -| Concurrency-safe accounting | ✗ | ✗ | ◐ | ✗ | ◐ | ✅ | -| Real-time enforcement | ✅ | ✗ | ◐ | ◐ | ✗ | ✅ | -| Post-hoc analysis and traces | ✗ | ✅ | ◐ | ✗ | ◐ | ◐ | -| Traffic shaping / abuse prevention | ✅ | ✗ | ✗ | ✗ | ◐ | ✗ | -| Execution scheduling and retries | ✗ | ✗ | ✗ | ✗ | ✅ | ✗ | +|---|---|---|---|---|---|---| +| Pre-execution budget check | No | No for tracing-only tools | Yes, at provider/gateway scope | Partial | No | Yes | +| Caller-estimated reserve-commit lifecycle | No | No | No | Partial, if custom-built | No | Yes | +| Per-tenant limits | Partial | Attribution, not enforcement | Partial, product-specific | Partial | No | Yes | +| Per-workflow / per-agent limits | Partial, with custom keys | Attribution, not enforcement | Partial, product-specific | Partial | Partial | Yes | +| Hierarchical scopes | No | Partial for grouping | Partial, product-specific | Partial | Partial | Yes | +| Cost-aware decisions | No | No for tracing-only tools | Yes | Partial | No | Yes | +| Retry / idempotency safety | Partial | No | Partial | Partial | Yes for job execution | Yes for budget lifecycle | +| Configured `ALLOW_WITH_CAPS` outcome | No | No | Product-specific alternatives | Partial | No | Yes; caller applies caps | +| Concurrency-safe accounting | No | No | Product-specific | Partial | Partial | Yes, for reservations | +| Real-time enforcement | Yes, for traffic | No for tracing-only tools | Yes, at provider/gateway scope | Partial | No | Yes, at instrumented boundary | +| Post-hoc analysis and traces | No | Yes | Partial | Partial | Partial | Partial; lifecycle records only | +| Traffic shaping / abuse prevention | Yes | No | Partial | No | Partial | No | +| Execution scheduling and retries | No | No | No | No | Yes | No | A few things worth noting: - No single tool covers every row. That is expected. -- The ◐ ratings are honest. Some of these capabilities can be approximated with enough custom work. But "possible with effort" is different from "supported by design." +- "Partial" covers product-specific support or substantial custom work. Check the exact gateway, provider, or scheduler rather than treating a category as one uniform product. - Cycles does not try to replace traffic shaping, observability, or scheduling. Those are separate concerns with mature tooling. Cycles focuses on the budget governance column because that is the gap most teams hit as autonomous systems scale. Each of these tools earns its place in a production stack. The question is whether the stack has a gap where runtime authority should be. @@ -373,9 +371,9 @@ A well-governed autonomous system typically includes: - a **rate limiter** for traffic shaping and abuse prevention - an **observability platform** for visibility, traces, and alerts -- **provider caps** as an organizational safety net +- **provider controls** as an independent vendor-side boundary - a **job scheduler** for execution timing and retry policies -- **Cycles** as the runtime authority that decides whether bounded work may proceed +- **Cycles** as the budget authority for caller-submitted application scopes These layers are complementary, not competitive. @@ -393,7 +391,7 @@ Consider adding Cycles to your stack when: - **Cost is unpredictable** — fan-out, tool loops, or retries make per-run cost hard to bound - **Multiple tenants share infrastructure** — one tenant's runaway agent should not affect others - **You need graceful degradation** — switching to cheaper models or reducing scope when budget is low, rather than hard-failing -- **Compliance requires cost limits** — audit trails showing that every action was authorized against a budget +- **Governance requires budget evidence** — lifecycle records showing which instrumented operations reserved, committed, released, or were denied against configured budgets - **You've outgrown ad hoc counters** — custom counters work until concurrency, retries, and hierarchy make them unreliable If none of these apply yet, start with [shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) to see what enforcement would look like on your current traffic. diff --git a/concepts/runtime-authority-vs-runtime-authorization.md b/concepts/runtime-authority-vs-runtime-authorization.md index 746f36e1..8387776a 100644 --- a/concepts/runtime-authority-vs-runtime-authorization.md +++ b/concepts/runtime-authority-vs-runtime-authorization.md @@ -8,19 +8,19 @@ description: "Authorization decides which agent may call a tool; authority decid Two governance terms have started circulating in the AI agent ecosystem, and they sound like the same thing. They aren't. > **Runtime *authorization*** asks whether an identity is *allowed* to use a tool. -> **Runtime *authority*** asks whether an agent still has *bounded permission* — budget, risk, action exposure — to take this specific next step *right now*. +> **Runtime budget authority** asks whether the caller's submitted amount fits the matching ledgers for this next step. -**Authorization grants access; authority meters and bounds the action.** +**Authorization grants access; budget authority meters caller-submitted exposure.** A production agent stack needs both. They sit at different layers, fire at different moments, and bound different things. AWS AgentCore Policy, Akeyless Agentic Runtime Authority, and internal agent-IAM patterns focus on identity, intent, access, and real-time policy enforcement — they decide whether an agent identity, intent, and request context are *permitted* to use a given tool or system. Cycles focuses on bounded exposure: whether a caller-assigned amount can still be reserved against the relevant scoped budget. The caller can express exposure as money, tokens, credits, or `RISK_POINTS`; the current Cycles server does not infer risk or classify tools itself. -The term "runtime authority" is used by multiple vendors with overlapping but different scopes. This page spells out the substance distinction: Cycles answers the question identity authorization can't — *should this specific next step still happen, given the budget, risk, and actions already consumed?* +The term "runtime authority" is used by multiple vendors with overlapping but different scopes. In Cycles, the concrete decision is narrower: *can this amount be reserved against the configured ledgers now?* The host combines that answer with identity and tool authorization. ## The two questions, side by side | | Runtime Authorization | Runtime Authority (Cycles) | |---|---|---| -| **What it answers** | "Is this identity allowed to call this tool?" | "Does this agent still have bounded permission to take this next step?" | +| **What it answers** | "Is this identity allowed to call this tool?" | "Can this submitted amount be reserved against the matching budgets?" | | **When it fires** | At identity-resolution time, per tool invocation | At every reservation, before each costly action | | **What it bounds** | Static policy — which identities can touch which tools | Dynamic budget — total spend or caller-assigned exposure such as credits and risk points | | **What it does NOT cover** | Cumulative consumption, hierarchical scopes, atomic concurrency | Identity-to-tool mapping, credential management, secret rotation | @@ -37,13 +37,13 @@ A real agent action goes through both layers in sequence: 1. Agent decides to call tool X 2. AUTHORIZATION: "Is this agent identity allowed to invoke X?" ↓ Yes (or DENY → caller informed) -3. AUTHORITY: "Can the caller-assigned exposure be reserved for this action?" +3. BUDGET AUTHORITY: "Can the caller-assigned exposure be reserved for this action?" ↓ ALLOW or ALLOW_WITH_CAPS (or a budget error → graceful degradation) 4. Execute tool with the constraints from authority's caps 5. Authority commits actual cost, releases unused budget ``` -Skip layer 2 and any agent that obtained credentials can do anything. Skip layer 3 and an authorized agent can drain a budget, run a tool a thousand times, or take a high-blast-radius action that exceeds its allocated risk. +Skip layer 2 and an agent with credentials may reach tools it should not use. Skip layer 3 and an authorized agent has no Cycles ledger bounding cumulative submitted spend or exposure. ## Where adjacent tools fit @@ -54,7 +54,7 @@ We don't ship per-vendor comparison pages against the identity-based agent gover | AWS Bedrock AgentCore Policy | Yes | Not publicly documented | Not publicly documented | Not publicly documented | AWS-managed | | Akeyless Agentic Runtime Authority | Yes — intent-aware access / real-time policy | Not publicly documented | Not publicly documented | Not publicly documented | Cloud / vendor-managed | | Generic agent IAM patterns | Yes | Usually no | Usually no | No | Varies | -| **Cycles** | API permissions only; downstream tool IAM external | **Yes (RISK_POINTS)** | **Yes** | **Yes** | **Yes** | +| **Cycles** | API permissions only; downstream tool IAM external | **Caller-assigned RISK_POINTS budget** | **Yes** | **Yes** | **Yes** | The first column is the authorization / intent-policy layer. AgentCore and Akeyless are well-suited for it — they handle identity, intent-aware access, policy attachment, and credential governance. The middle columns are the bounded-exposure layer — that is where Cycles operates. The final column is a deployment / privacy distinction, not a runtime-authority capability per se. @@ -67,7 +67,7 @@ Concrete example — a SaaS deploying customer-support agents: - **Authorization layer** (AgentCore / Akeyless / IAM): defines that *the support agent's identity* is allowed to call the `send_email` tool, and *the engineering agent's identity* is allowed to call the `deploy_service` tool. Cross-access denied at the policy layer. - **Authority layer** (Cycles + host integration): defines that the *support tenant* has $500/month in tokens and a 200-point daily risk budget. The host classifies each email as 40 [RISK_POINTS](/concepts/action-authority-controlling-what-agents-do) and reserves that amount before dispatch. Even though the support agent is *authorized* to send emails, the 6th live reservation fails once that risk budget is exhausted; an LLM call likewise does not proceed when its token reservation fails. -Without authorization, an attacker who exfiltrates an API key can use any tool. Without authority, an authorized agent can run a tool a thousand times. +Without authorization, a credentialed caller may reach tools it should not use. Without a cumulative budget or count control, an authorized agent can repeat a tool until some other limit stops it. ## When you only need authorization @@ -80,10 +80,10 @@ If you're here, AgentCore Policy or a similar identity-based system is sufficien ## When you need authority - Multi-tenant SaaS where one customer's runaway must not affect other tenants. -- Agents with hierarchical scopes — tenant → workspace → workflow → run — that each need their own budget. +- Agents with hierarchical standard scopes—tenant → workspace → app → workflow → agent → toolset—that need multiple budget ledgers. A run can be represented by a unique workflow value when it needs its own ledger. - Tools with side effects (email, deploy, mutation) where you want to bound risk *separately* from cost. - Multi-agent delegation chains where authority should attenuate at each hop, not propagate. -- Production cost predictability — you need to *prove* a $4,200 overnight runaway can't happen. +- Production cost predictability — you need evidence that configured budgets bound covered execution paths under concurrency and retries. If any of these apply, identity authorization alone leaves the budget and risk dimensions unbounded. That's where Cycles fits. @@ -98,6 +98,6 @@ External vendor capabilities verified against linked sources as of July 2026. Th - [Cycles Protocol](/protocol/) — the open specification behind the runtime-authority claim. Explicit conformance criteria and the reference implementation are public. - [What Is Runtime Authority for AI Agents](/blog/what-is-runtime-authority-for-ai-agents) — the canonical definition we use throughout Cycles documentation. -- [Action Authority — Controlling What Agents Do](/concepts/action-authority-controlling-what-agents-do) — RISK_POINTS, action allowlists, and the action-layer enforcement model. +- [Action Authority — Controlling What Agents Do](/concepts/action-authority-controlling-what-agents-do) — composing host authorization with caller-assigned RISK_POINTS and host-applied caps. - [Comparisons — How Cycles Differs from Alternatives](/concepts/comparisons) — proxy/observability/rate-limit comparison hub for the LiteLLM/Helicone/LangSmith axis. - [Why Rate Limits Are Not Enough](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) — the deeper argument for why velocity controls and identity policy alone fail for autonomous systems. diff --git a/concepts/webhooks-and-events.md b/concepts/webhooks-and-events.md index 13a70e62..e20b5416 100644 --- a/concepts/webhooks-and-events.md +++ b/concepts/webhooks-and-events.md @@ -5,19 +5,19 @@ description: How Cycles emits events and delivers them to external systems via w # Webhooks and Events -Cycles emits **events** for every observable state change — budget exhaustion, reservation denials, tenant lifecycle changes, API key operations, and system health. **Webhooks** deliver these events to external endpoints via HTTP POST with HMAC-SHA256 signatures. +Cycles services emit **events** from implemented budget, reservation, tenant, API-key, webhook, and system hooks. The schema also registers planned event types that are not emitted yet; the [Event Payloads Reference](/protocol/event-payloads-reference) tracks that status. **Webhooks** deliver emitted events to external endpoints via HTTP POST with HMAC-SHA256 signatures. ## Core Concepts ### Events -An event is an immutable record of a state change. Every event has: +An event is an immutable record of an emitted decision, lifecycle outcome, or state change. Every event has: - **event_id** — unique identifier; the key consumers dedupe on - **event_type** — dotted format like `budget.exhausted` or `reservation.denied` - **category** — one of: budget, reservation, tenant, api_key, policy, webhook, system - **timestamp** — when the event occurred - **tenant_id** — which tenant is affected -- **source** — which service emitted it (an open string; e.g. `cycles-server`, `cycles-admin`, `expiry-sweeper`, `anomaly-detector`) +- **source** — which service emitted it (an open string; current reference values include `cycles-server`, `cycles-admin`, and `cycles-events`) - **data** — optional event-specific payload (varies by type) Events are stored in Redis with a 90-day TTL (configurable). @@ -34,7 +34,7 @@ A subscription defines which events to deliver and where: ### Delivery Semantics - **At-least-once** — events may be delivered more than once. Deduplicate using `event_id`. -- **Best-effort ordering** — first-attempt deliveries are dispatched from a single FIFO queue, but retried deliveries may arrive out of order. Consumers should dedupe and sequence on `event_id` and timestamp. +- **No delivery-order guarantee** — concurrent delivery workers and retries can reorder deliveries. Use `event_id` only for deduplication; reconstruct chronology from event timestamps, the stored event log, and current domain state. - **Non-blocking** — webhook delivery never blocks the API operation that produced the event. - **Retry with backoff** — failed deliveries retry with exponential backoff (default: 5 retries). - **Auto-disable** — subscriptions are disabled after consecutive failures (default: 10). @@ -47,7 +47,7 @@ The events service is **optional**. If not deployed, events accumulate in Redis ## 51 Registered Event Types -| Category | Count | Examples | +| Category | Count | Registered examples (some remain planned) | |---|---|---| | budget | 17 | `budget.exhausted`, `budget.threshold_crossed`, `budget.over_limit_entered`, `budget.funded`, `budget.closed_via_tenant_cascade` | | reservation | 6 | `reservation.denied`, `reservation.commit_overage`, `reservation.released_via_tenant_cascade` | diff --git a/concepts/why-coding-agents-do-not-replace-cycles.md b/concepts/why-coding-agents-do-not-replace-cycles.md index f0b831a0..bfbbcd23 100644 --- a/concepts/why-coding-agents-do-not-replace-cycles.md +++ b/concepts/why-coding-agents-do-not-replace-cycles.md @@ -163,6 +163,6 @@ To learn more: - Read [Coding Agents Need Runtime Authority](/concepts/coding-agents-need-runtime-budget-authority) for the runtime-layer companion to this piece - Understand [Why Rate Limits Are Not Enough](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) for how velocity controls differ from runtime authority - See [From Observability to Enforcement](/concepts/from-observability-to-enforcement-how-teams-evolve-from-dashboards-to-budget-authority) for how teams evolve from dashboards to budget governance -- Explore the [reserve/commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) that powers runtime enforcement +- Explore the [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles) that powers runtime enforcement - Get started with the [Python Client](/quickstart/getting-started-with-the-python-client) or [TypeScript Client](/quickstart/getting-started-with-the-typescript-client) - [AI Agent Budget Control: Enforce Hard Spend Limits](/blog/ai-agent-budget-control-enforce-hard-spend-limits) — the technical mechanism behind runtime budget enforcement diff --git a/concepts/why-rate-limits-are-not-enough-for-autonomous-systems.md b/concepts/why-rate-limits-are-not-enough-for-autonomous-systems.md index a0acd07c..0f18d9ff 100644 --- a/concepts/why-rate-limits-are-not-enough-for-autonomous-systems.md +++ b/concepts/why-rate-limits-are-not-enough-for-autonomous-systems.md @@ -171,7 +171,7 @@ If retries are idempotent and tied to the same reservation lifecycle, the system Many systems can estimate cost before execution but only know the true cost afterward. -Reserve/commit handles both. +Reserve-commit handles both. ### Hierarchical control diff --git a/configuration/server-configuration-reference-for-cycles.md b/configuration/server-configuration-reference-for-cycles.md index a3578581..ad1f698a 100644 --- a/configuration/server-configuration-reference-for-cycles.md +++ b/configuration/server-configuration-reference-for-cycles.md @@ -111,7 +111,7 @@ Runtime audit rows never use the admin-plane `__admin__` / `__unauth__` sentinel |---|---|---|---| | `cycles.metrics.tenant-tag.enabled` | `true` | `CYCLES_METRICS_TENANT_TAG_ENABLED` | When `true`, Prometheus counters include a `tenant` label. Set to `false` in deployments with many thousands of tenants to bound series cardinality. | -The runtime server publishes seven domain counters under `cycles_*_total` (introduced in v0.1.25.10); the events service publishes `cycles_webhook_*` counters plus a latency timer. The `tenant-tag.enabled` toggle is mirrored on both services, but note the defaults differ: `true` on the runtime, `false` on the events service. For the full metric enumeration, tag definitions, scrape targets, and alert recipes, see [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference). +The runtime server publishes 11 domain counters plus one maintenance timer; the events service publishes 17 delivery, evidence, dispatcher, and security counters plus one delivery-latency timer. The `tenant-tag.enabled` toggle is mirrored on both services, but the defaults differ: `true` on the runtime and `false` on the events service. For the complete current inventory, tag definitions, scrape targets, and alert recipes, see [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference). ## JSON serialization @@ -513,7 +513,7 @@ A delivery that exceeds `dispatch.max-delivery-age-ms` (default 24h) is failed i ### Events service metrics -Introduced in `cycles-server-events` v0.1.25.6. Seven counters plus one latency timer under the `cycles_webhook_*` namespace, with `tenant` and `event_type` labels gated by `cycles.metrics.tenant-tag.enabled`. For the full enumeration — metric names, tags, cardinality guidance, scrape config, and alert recipes — see [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference#events-service-cycles-server-events). +Introduced in `cycles-server-events` v0.1.25.6 and expanded in later releases. The current service exposes 17 delivery, evidence, dispatcher, and security counters plus one delivery-latency timer. Metrics that carry `tenant` respect `cycles.metrics.tenant-tag.enabled`; metrics without that tag are unaffected. For the complete enumeration, see [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference#events-service-cycles-server-events). ### Encryption key (shared across all services) diff --git a/glossary.md b/glossary.md index 520eea5e..88fa6a51 100644 --- a/glossary.md +++ b/glossary.md @@ -19,11 +19,11 @@ The role Cycles plays in an autonomous system: authorizing or denying execution ### Action Authority -The subset of [runtime authority](#runtime-authority) that governs what actions an agent is permitted to take — independent of cost. While budget authority controls spend, action authority controls side effects: emails sent, deployments triggered, records modified. Cycles enforces action authority through toolset-scoped budgets denominated in [RISK_POINTS](/protocol/understanding-units-in-cycles-usd-microcents-tokens-credits-and-risk-points). See the [Action Authority Demo](/demos/) and [AI Agent Action Control](/blog/ai-agent-action-control-hard-limits-side-effects). +The subset of [runtime authority](#runtime-authority) that governs what actions an agent is permitted to take—independent of cost. Application or gateway authorization controls side effects such as emails, deployments, and record changes. Cycles can contribute a caller-assigned [RISK_POINTS](/protocol/understanding-units-in-cycles-usd-microcents-tokens-credits-and-risk-points) budget and return configured caps, but it does not infer tool permissions or authorize arguments; the host must enforce both the permission decision and the budget boundary. See the [Action Authority Demo](/demos/) and [AI Agent Action Control](/blog/ai-agent-action-control-hard-limits-side-effects). ### Exposure -The cumulative cost, risk, or side effects an autonomous system can create before it is stopped. An agent with a $5 budget but no pre-execution enforcement has unbounded exposure — every action executes before any limit is checked. Cycles bounds exposure through the [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles): budget is reserved before work begins, capping the maximum possible damage. See [Exposure Estimation](/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles). +The cumulative cost or caller-modeled risk an autonomous system can create before a control intervenes. Cycles can bound submitted estimates on mandatory instrumented paths through the [reserve-commit lifecycle](/protocol/how-reserve-commit-works-in-cycles). It does not cap uninstrumented work or prove the real-world damage of an action, and settled spend can differ according to the commit overage policy. See [Exposure Estimation](/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles). ### Reservation @@ -147,7 +147,7 @@ The current state of a budget, including fields such as `allocated`, `spent`, `r ### Budget Envelope -A fixed upper bound on how much an entity (tenant, workflow, run) is allowed to consume. Budget envelopes are enforced hierarchically — a run's envelope cannot exceed its parent workflow's remaining budget, which in turn cannot exceed the tenant's allocation. +A configured ledger limit at one standard scope such as tenant, workspace, app, workflow, agent, or toolset. A run can use a unique workflow value as an application-level mapping. One reservation checks every matching configured ledger atomically, but allocations are independent: Cycles does not transfer or subdivide funds between parent and child ledgers. ### Graceful Degradation @@ -223,7 +223,7 @@ An HTTP POST callback triggered by a state change event. Cycles delivers webhook ### Event (Webhook) -An immutable record of a state change (e.g., `budget.exhausted`, `reservation.denied`). The v0.1.25 Admin API `EventType` enum registers 51 event types across 7 categories (budget: 17, reservation: 6, tenant: 6, api_key: 7, policy: 3, webhook: 7, system: 5). The `webhook` category covers six lifecycle events (`webhook.created` / `.updated` / `.paused` / `.resumed` / `.disabled` / `.deleted`) added in spec v0.1.25.33; the four `_via_tenant_cascade` event names emitted by the tenant-close cascade are part of the registered enum since governance revision v0.1.25.35. Events are stored in Redis with configurable TTL (default 90 days) and dispatched to matching webhook subscriptions. +An immutable record of an emitted decision, lifecycle outcome, or state change (for example, `reservation.denied` or `budget.exhausted`). The v0.1.25 Admin API `EventType` enum registers 51 event types across 7 categories (budget: 17, reservation: 6, tenant: 6, api_key: 7, policy: 3, webhook: 7, system: 5); registration does not mean every type is currently emitted. The `webhook` category covers six lifecycle events (`webhook.created` / `.updated` / `.paused` / `.resumed` / `.disabled` / `.deleted`) added in spec v0.1.25.33; the four `_via_tenant_cascade` event names emitted by the tenant-close cascade are part of the registered enum since governance revision v0.1.25.35. Events are stored in Redis with configurable TTL (default 90 days) and matching subscriptions are delivered when the Events Service is deployed. ### Signing Secret @@ -271,7 +271,7 @@ The W3C Trace Context HTTP header (`00---`). Cycles ac ### correlation_id -A server-set identifier that groups a family of related events in the event stream, in one of two shapes: for protocol event-stream clusters, a deterministic hash over `(tenant_id, scope, action_kind_or_risk_class, window, window_key)` — so threshold-alert → trip → reset chains and `observed_denied` ↔ `reservation.denied` pairs join without an operator supplying anything; for governance/admin operations, an explicit server-composed operation ID (e.g. `webhook_create:`, `webhook_bulk_action::`, tenant-cascade IDs). Scoped to the event stream only. Distinct from `trace_id` (logical-operation grain, W3C-compatible) and `request_id` (one HTTP request) — both of which are also server-managed but answer different questions. +A server-managed event field. The runtime YAML requires deterministic cluster IDs derived from `(tenant_id, scope, action_kind_or_risk_class, window, window_key)`, but the current reference runtime leaves `correlation_id` absent on its implemented emit paths. The current admin server does populate explicit operation IDs for selected lifecycle and fan-out operations (for example, `webhook_create:`, `webhook_bulk_action::`, and tenant-cascade IDs). It is distinct from `trace_id` (the W3C-compatible logical-operation join that callers can propagate) and `request_id` (one HTTP request). ## Admin Plane diff --git a/how-to/assigning-risk-points-to-agent-tools.md b/how-to/assigning-risk-points-to-agent-tools.md index bdcf6754..dc8996c9 100644 --- a/how-to/assigning-risk-points-to-agent-tools.md +++ b/how-to/assigning-risk-points-to-agent-tools.md @@ -5,7 +5,7 @@ description: "Step-by-step risk scoring guide: classify your AI agent's tools in # How to Assign RISK_POINTS to AI Agent Tools -This guide walks you through risk scoring for your AI agent's tools with [RISK_POINTS](/glossary#risk-points) — the unit Cycles uses for [action authority](/concepts/action-authority-controlling-what-agents-do) enforcement. By the end, you'll have a scored tool list and a per-run budget you can configure directly. +This guide walks you through an illustrative method for assigning [RISK_POINTS](/glossary#risk-points) to tool attempts. Cycles treats those caller-assigned points as a budget unit; the host still authenticates the principal, authorizes the tool and arguments, and makes the reservation boundary mandatory. For the full risk assessment framework and regulatory context, see [AI Agent Risk Assessment](/blog/ai-agent-risk-assessment-score-classify-enforce-tool-risk). This page is the implementation-focused quick reference. @@ -59,7 +59,7 @@ Does this tool modify any state? The third question — **"Does it affect someone who did not request it?"** — is the key differentiator between Tier 2 (external but contained) and Tier 3 (customer-facing impact). An API call to a third-party service you control is Tier 2. An email landing in a customer's inbox is Tier 3 — even though both are external. -Each tier has a **base RISK_POINTS score**: +This guide proposes the following **starting scores**. They are not built into the server and are not a compliance standard: | Tier | Type | Base Points | Rationale | |:---:|---|:---:|---| @@ -132,12 +132,12 @@ For `send_customer_email`: ## Step 4: Set your per-run budget -Sum the expected tool calls for a **typical** run, then add buffer: +Sum the expected tool calls for representative runs, then choose a buffer from the variation you are prepared to allow: 1. Count how many times each tool is called in a normal agent run 2. Multiply each by its RISK_POINTS score 3. Sum the results -4. Add 20-30% buffer for variance +4. Choose a buffer from observed percentiles and the maximum tolerated exposure **Example: Customer support agent** @@ -159,18 +159,19 @@ Set per-run budget to **250 RISK_POINTS**: - Single refund + email: 150 + 40 = 190 points (fits) - Two refunds: 300 points (does **not** fit — requires escalation) -This budget lets the agent handle any normal support interaction while preventing it from issuing multiple refunds or sending dozens of emails in a single run. +At a mandatory boundary, this illustrative budget would reject an attempt whose submitted points no longer fit. It does not itself determine whether a refund or email is authorized. + +To enforce this per run, create the ledger at a unique workflow scope such as `workflow:run-12345` and send that run ID as `subjects.workflow` on every protected tool attempt. `run` is not a native scope, and `dimensions.run_id` is attribution only. ## Step 5: Validate with shadow mode -Before enforcing, run with [`dry_run: true`](/protocol/dry-run-shadow-mode-evaluation-in-cycles) for 1-2 weeks: +Before enforcing, run with [`dry_run: true`](/protocol/dry-run-shadow-mode-evaluation-in-cycles) long enough to cover representative traffic: 1. Create the RISK_POINTS budget via admin API 2. Set reservations to dry-run mode -3. Monitor the shadow-mode denial rate: - - **> 5%** — scores or budget too tight, agents can't do normal work - - **1-3%** — catching real anomalies without blocking legitimate work - - **0%** — budget is too loose, won't catch incidents +3. Have the application log every dry-run result and actual outcome, then review the hypothetical denial rate by workflow and action class. The current server emits `reservation.denied` for denied evaluations, but that is not a complete shadow dataset. + - A high rate can indicate scores or budgets are too tight, incomplete scoping, or genuinely abnormal traffic. + - A low rate does not prove that the policy is effective; exercise known denial cases separately. 4. Adjust individual tool scores or the run budget based on the data 5. When denial patterns are no longer surprising, enable enforcement @@ -251,7 +252,7 @@ A data analysis agent with these tools: Typical run: 10 queries (0) + 3 searches (0) + 2 charts (10) + 1 email (40) + 1 dashboard update (1) + 1 export (8) = **59 points** -Set run budget: **100 RISK_POINTS** — allows a normal run with room for a second email or extra charts, but prevents sending 3+ reports in a loop. +Set run budget: **100 RISK_POINTS** — at a mandatory boundary, the third 40-point report attempt would not fit after two such attempts, assuming no other point consumption. ## When to recalibrate risk scores @@ -260,7 +261,7 @@ Review your scores when: - **A new tool is added.** Classify and score it before deployment. - **An incident occurs.** If a tool caused damage, re-evaluate its tier and multiplier. - **Usage patterns shift.** If shadow mode shows agents consistently near the budget ceiling on normal runs, the budget is too tight — raise it or optimize the workflow. -- **Commit overage events climb.** Rising [`reservation.commit_overage`](/protocol/event-payloads-reference) events indicate your cost estimates are drifting from reality. Tool scores may need adjusting too. +- **The consequence model changes.** Revisit scores when a tool gains new destinations, broader permissions, more sensitive data, or a larger audience. ## Next steps diff --git a/how-to/budget-templates.md b/how-to/budget-templates.md index a8a42cd1..83f542ff 100644 --- a/how-to/budget-templates.md +++ b/how-to/budget-templates.md @@ -216,7 +216,7 @@ echo "=== Customer $CUSTOMER_ID onboarded (plan: $PLAN) ===" - 1 API key - 1 USD budget (cost control) - 1 RISK_POINTS budget (action control — per-run) -- Per-run RISK_POINTS cap prevents tool abuse within a single agent execution +- Per-run `RISK_POINTS` cap bounds caller-assigned exposure when every protected tool attempt uses a mandatory reservation boundary ```bash #!/bin/bash @@ -301,7 +301,7 @@ echo "RISK_POINTS budget: $RISK_BUDGET_PER_RUN points per run (create per run)" | USD (cost) | `tenant:my-company` | Monthly via cron | Total API spend across all agents | | RISK_POINTS (action) | `tenant:my-company/workflow:run-{uuid}` | Per run (new scope each time) | What tools the agent can use within one execution | -The USD budget prevents cost overruns. The RISK_POINTS budget prevents action abuse. An agent can search freely (0 points) but can only send 6 emails per run (6 × 40 = 240 of 250 points). +The USD budget bounds submitted spend. The `RISK_POINTS` budget separately bounds caller-assigned exposure. In this example, a mandatory handler could reserve 40 points per authorized email attempt, so six attempts consume 240 of 250 points and a seventh does not fit. Tool and argument authorization remains a host responsibility. --- diff --git a/how-to/choosing-the-right-overage-policy.md b/how-to/choosing-the-right-overage-policy.md index 9aa47522..6da3c3a4 100644 --- a/how-to/choosing-the-right-overage-policy.md +++ b/how-to/choosing-the-right-overage-policy.md @@ -20,7 +20,7 @@ Ask these questions in order: → **REJECT** with a 10–20% buffer — hard enforcement is safe when estimates are tight 3. **Everything else** — variable-cost LLM calls, tool invocations, streaming responses, multi-step agents - → **ALLOW_IF_AVAILABLE** (the default) — always commits, never creates debt, caps at budget boundary + → **ALLOW_IF_AVAILABLE** (the default) — does not reject a valid commit merely because of overage, never creates debt, and caps the charged delta at available budget ## By use case @@ -28,7 +28,7 @@ Ask these questions in order: **Recommended:** ALLOW_IF_AVAILABLE -Token counts vary by prompt, context window, and model behavior. Estimation is inherently imprecise. ALLOW_IF_AVAILABLE ensures every call is recorded and caps the charge when budget runs low. +Token counts vary by prompt, context window, and model behavior. Estimation is inherently imprecise. For an instrumented call whose commit passes ownership, state, expiry, and unit validation, ALLOW_IF_AVAILABLE records the submitted actual but caps the charged overage when budget runs low. ```python @cycles(estimate=50000, action_kind="llm.completion", action_name="openai:gpt-4o") diff --git a/how-to/client-performance-tuning.md b/how-to/client-performance-tuning.md index f1a29c73..56df01f8 100644 --- a/how-to/client-performance-tuning.md +++ b/how-to/client-performance-tuning.md @@ -103,7 +103,7 @@ config = CyclesConfig( ``` ::: warning -Never set the read timeout below the server's expected p99 latency. At default load, the server's p99 for reserve+commit is ~20ms. Under heavy load it can reach 50ms+. A read timeout of 100ms will cause spurious failures. +Never set the read timeout below the server's expected p99 latency. At default load, the server's p99 for a reserve-commit cycle is ~20ms. Under heavy load it can reach 50ms+. A read timeout of 100ms will cause spurious failures. ::: ## Connection pooling @@ -268,7 +268,7 @@ For server-side performance: - **Redis connection pool** — default 128 connections. See [Production Operations Guide](/how-to/production-operations-guide). - **Expiry sweep interval** — default 5000ms. See [Server Configuration Reference](/configuration/server-configuration-reference-for-cycles). -- **Benchmarks** — Reserve 5.3ms p50, 2,632 reserve+commit lifecycles/sec at 32 threads. See [Performance Benchmarks](/blog/cycles-server-performance-benchmarks). +- **Benchmarks** — Reserve 5.3ms p50, 2,632 reserve-commit lifecycles/sec at 32 threads. See [Performance Benchmarks](/blog/cycles-server-performance-benchmarks). ## Next steps diff --git a/how-to/cost-estimation-cheat-sheet.md b/how-to/cost-estimation-cheat-sheet.md index e00c8a6a..e042db83 100644 --- a/how-to/cost-estimation-cheat-sheet.md +++ b/how-to/cost-estimation-cheat-sheet.md @@ -37,6 +37,9 @@ microcents = price_per_million_tokens × token_count × 100 | Model | Input (per 1M tokens) | Output (per 1M tokens) | Input (microcents/token) | Output (microcents/token) | |---|---|---|---|---| +| gpt-5.6-sol | $5.00 | $30.00 | 500 | 3,000 | +| gpt-5.6-terra | $2.50 | $15.00 | 250 | 1,500 | +| gpt-5.6-luna | $1.00 | $6.00 | 100 | 600 | | gpt-5 | $1.25 | $10.00 | 125 | 1,000 | | gpt-5-mini | $0.25 | $2.00 | 25 | 200 | | gpt-5-nano | $0.05 | $0.40 | 5 | 40 | @@ -53,30 +56,30 @@ microcents = price_per_million_tokens × token_count × 100 | Model | Input (per 1M tokens) | Output (per 1M tokens) | Input (microcents/token) | Output (microcents/token) | |---|---|---|---|---| -| Claude Opus 4 | $15.00 | $75.00 | 1,500 | 7,500 | -| Claude Sonnet 4 | $3.00 | $15.00 | 300 | 1,500 | -| Claude Haiku 3.5 | $0.80 | $4.00 | 80 | 400 | +| Claude Opus 4.8 | $5.00 | $25.00 | 500 | 2,500 | +| Claude Sonnet 4.6 | $3.00 | $15.00 | 300 | 1,500 | +| Claude Haiku 4.5 | $1.00 | $5.00 | 100 | 500 | ### Google | Model | Input (per 1M tokens) | Output (per 1M tokens) | Input (microcents/token) | Output (microcents/token) | |---|---|---|---|---| | Gemini 2.5 Pro | $1.25 | $10.00 | 125 | 1,000 | -| Gemini 2.5 Flash | $0.15 | $0.60 | 15 | 60 | -| Gemini 2.0 Flash | $0.10 | $0.40 | 10 | 40 | +| Gemini 2.5 Flash | $0.30 | $2.50 | 30 | 250 | +| Gemini 2.5 Flash-Lite | $0.10 | $0.40 | 10 | 40 | -### Meta (Llama) +### Groq on-demand | Model | Input (per 1M tokens) | Output (per 1M tokens) | Input (microcents/token) | Output (microcents/token) | |---|---|---|---|---| -| Llama 4 Maverick | $0.20 | $0.60 | 20 | 60 | -| Llama 4 Scout | $0.15 | $0.40 | 15 | 40 | -| Llama 3.3 70B | $0.40 | $0.40 | 40 | 40 | +| openai/gpt-oss-20b | $0.075 | $0.30 | 7.5 | 30 | +| openai/gpt-oss-120b | $0.15 | $0.60 | 15 | 60 | +| qwen/qwen3.6-27b | $0.60 | $3.00 | 60 | 300 | -> Llama model pricing varies by hosting provider. The values above are representative of major inference providers (Together AI, Groq, Fireworks). Self-hosted models have no per-token cost — use `TOKENS` or `RISK_POINTS` units instead. See the [Ollama integration guide](/how-to/integrating-cycles-with-ollama) for self-hosted patterns. +> Open-model pricing and availability vary by host. The table above is specifically Groq's on-demand pricing, not a universal rate for those model families. Round the final reservation amount up when a per-token conversion is fractional. Self-hosted models have no provider token invoice, but still consume compute; use a unit that matches what you want to bound. See the [Groq integration guide](/how-to/integrating-cycles-with-groq) and [Ollama integration guide](/how-to/integrating-cycles-with-ollama). ::: info Note -Prices change. Check your provider's pricing page for current rates. The formulas and approach remain the same regardless of specific prices. +Provider rates above were checked on July 24, 2026. The OpenAI table includes the current GPT-5.6 family plus selected older models that still appear in examples. GPT-5.6 cache reads are discounted, cache writes cost 1.25 times the uncached input rate, and requests above 272,000 input tokens use higher rates for the full request. Prices, caching rules, long-context tiers, and regional premiums change; check each provider's pricing page before deploying. The formulas remain the same. ::: ## Quick estimation formula @@ -93,16 +96,16 @@ Then add a safety buffer: reservation_amount = estimate × 1.2 # 20% buffer ``` -### Example: GPT-4o call with 2,000 input tokens, 1,000 max output tokens +### Example: GPT-5.6 Luna call with 2,000 input tokens, 1,000 max output tokens ``` -input_cost = 2,000 × 250 = 500,000 microcents -output_cost = 1,000 × 1,000 = 1,000,000 microcents -total = 1,500,000 microcents ($0.015) -with buffer = 1,800,000 microcents +input_cost = 2,000 × 100 = 200,000 microcents +output_cost = 1,000 × 600 = 600,000 microcents +total = 800,000 microcents ($0.008) +with buffer = 960,000 microcents ``` -### Example: Claude Sonnet 4 call with 4,000 input tokens, 2,000 max output tokens +### Example: Claude Sonnet 4.6 call with 4,000 input tokens, 2,000 max output tokens ``` input_cost = 4,000 × 300 = 1,200,000 microcents @@ -115,32 +118,39 @@ with buffer = 5,040,000 microcents ::: code-group ```python [Python] +import math + # Simple cost estimator def estimate_cost(input_tokens: int, max_output_tokens: int, model: str) -> int: """Return estimated cost in USD_MICROCENTS with 20% buffer.""" rates = { + "gpt-5.6-sol": (500, 3000), + "gpt-5.6-terra": (250, 1500), + "gpt-5.6-luna": (100, 600), "gpt-4o": (250, 1000), "gpt-4o-mini": (15, 60), "gpt-4.1": (200, 800), "gpt-4.1-mini": (40, 160), "gpt-4.1-nano": (10, 40), "claude-sonnet": (300, 1500), - "claude-haiku": (80, 400), + "claude-haiku": (100, 500), "gemini-2.5-pro": (125, 1000), - "gemini-2.5-flash":(15, 60), - "llama-4-maverick":(20, 60), + "gemini-2.5-flash":(30, 250), + "groq:gpt-oss-20b":(7.5, 30), + "groq:gpt-oss-120b":(15, 60), + "groq:qwen3.6-27b": (60, 300), } - input_rate, output_rate = rates.get(model, (250, 1000)) + input_rate, output_rate = rates.get(model, (100, 600)) estimate = (input_tokens * input_rate) + (max_output_tokens * output_rate) - return int(estimate * 1.2) + return math.ceil(estimate * 1.2) # Usage with the @cycles decorator @cycles( estimate=lambda prompt, max_tokens=1000: estimate_cost( - len(prompt) // 4, max_tokens, "gpt-4o" + len(prompt) // 4, max_tokens, "gpt-5.6-luna" ), action_kind="llm.completion", - action_name="openai:gpt-4o", + action_name="openai:gpt-5.6-luna", ) def ask(prompt: str, max_tokens: int = 1000) -> str: ... @@ -148,27 +158,32 @@ def ask(prompt: str, max_tokens: int = 1000) -> str: ```typescript [TypeScript] function estimateCost(inputTokens: number, maxOutputTokens: number, model: string): number { const rates: Record = { + "gpt-5.6-sol": [500, 3000], + "gpt-5.6-terra": [250, 1500], + "gpt-5.6-luna": [100, 600], "gpt-4o": [250, 1000], "gpt-4o-mini": [15, 60], "gpt-4.1": [200, 800], "gpt-4.1-mini": [40, 160], "gpt-4.1-nano": [10, 40], "claude-sonnet": [300, 1500], - "claude-haiku": [80, 400], + "claude-haiku": [100, 500], "gemini-2.5-pro": [125, 1000], - "gemini-2.5-flash":[15, 60], - "llama-4-maverick":[20, 60], + "gemini-2.5-flash":[30, 250], + "groq:gpt-oss-20b":[7.5, 30], + "groq:gpt-oss-120b":[15, 60], + "groq:qwen3.6-27b": [60, 300], }; - const [inputRate, outputRate] = rates[model] ?? [250, 1000]; + const [inputRate, outputRate] = rates[model] ?? [100, 600]; const estimate = inputTokens * inputRate + maxOutputTokens * outputRate; return Math.ceil(estimate * 1.2); } const ask = withCycles( { - estimate: (prompt: string) => estimateCost(Math.ceil(prompt.length / 4), 1000, "gpt-4o"), + estimate: (prompt: string) => estimateCost(Math.ceil(prompt.length / 4), 1000, "gpt-5.6-luna"), actionKind: "llm.completion", - actionName: "openai:gpt-4o", + actionName: "openai:gpt-5.6-luna", }, async (prompt: string) => { ... }, ); @@ -181,9 +196,9 @@ Quick reference for typical operations (including 20% buffer): | Operation | Model | Typical Estimate (microcents) | Approx USD | |---|---|---|---| -| Short chat reply (500 in / 200 out) | gpt-4o | 390,000 | $0.004 | -| Long chat reply (2,000 in / 1,000 out) | gpt-4o | 1,800,000 | $0.018 | -| Document summary (8,000 in / 2,000 out) | gpt-4o | 4,800,000 | $0.048 | +| Short chat reply (500 in / 200 out) | gpt-5.6-luna | 204,000 | $0.002 | +| Long chat reply (2,000 in / 1,000 out) | gpt-5.6-luna | 960,000 | $0.010 | +| Document summary (8,000 in / 2,000 out) | gpt-5.6-luna | 2,400,000 | $0.024 | | Short chat reply (500 in / 200 out) | gpt-4o-mini | 23,400 | $0.0002 | | Long chat reply (2,000 in / 1,000 out) | claude-sonnet | 2,520,000 | $0.025 | | Code generation (4,000 in / 4,000 out) | claude-sonnet | 8,640,000 | $0.086 | @@ -202,7 +217,7 @@ Use these rules of thumb: If you prefer to budget in tokens rather than dollars: ```python -@cycles(estimate=2000, unit="TOKENS", action_kind="llm.completion", action_name="gpt-4o") +@cycles(estimate=2000, unit="TOKENS", action_kind="llm.completion", action_name="gpt-5.6-luna") def ask(prompt: str) -> str: ... ``` diff --git a/how-to/ecosystem.md b/how-to/ecosystem.md index fab95ce8..eb4afffc 100644 --- a/how-to/ecosystem.md +++ b/how-to/ecosystem.md @@ -11,7 +11,7 @@ Cycles integrates with the tools, frameworks, and AI providers you already use. ### OpenAI -Integrate Cycles runtime authority with ChatGPT, GPT-4, GPT-4o, and other OpenAI models. Control per-request and per-session spending when your agents call OpenAI APIs. +Integrate Cycles with GPT-5.6 and other models available through the OpenAI API. Reserve budget per protected request and use standard subject fields for enforceable workflow or agent scopes. - [OpenAI integration guide (Python)](/how-to/integrating-cycles-with-openai) - [OpenAI integration guide (TypeScript)](/how-to/integrating-cycles-with-openai-typescript) @@ -48,7 +48,7 @@ Budget control for local model runners — track GPU time and compute costs for ### Groq -Budget governance for Groq's LPU-accelerated inference. Uses the OpenAI-compatible API with Groq-specific pricing. Includes a model-downgrade degradation pattern — fall back from GPT-4o to Groq when budget runs low. +Budget governance for Groq's LPU-accelerated inference. Uses the OpenAI-compatible API with current Groq model IDs and pricing, plus an application-owned fallback pattern after a primary route's budget reservation is rejected. - [Groq integration guide](/how-to/integrating-cycles-with-groq) - [groq.com](https://groq.com) @@ -64,14 +64,14 @@ Build budget-aware LangChain agents in Python. The `langchain-runcycles` package - [langchain-runcycles on PyPI](https://pypi.org/project/langchain-runcycles/) - [LangChain integration guide](/how-to/integrating-cycles-with-langchain) - [Source on GitHub](https://github.com/runcycles/langchain-runcycles) -- [python.langchain.com](https://python.langchain.com) +- [Python LangChain documentation](https://docs.langchain.com/oss/python/langchain/overview) ### LangChain.js The same LangChain integration, purpose-built for JavaScript and TypeScript environments. - [LangChain.js integration guide](/how-to/integrating-cycles-with-langchain-js) -- [js.langchain.com](https://js.langchain.com) +- [JavaScript LangChain documentation](https://docs.langchain.com/oss/javascript/langchain/overview) ### LangGraph @@ -85,7 +85,7 @@ Budget control for LangGraph stateful agent workflows. Use LangChain's callback Add Cycles runtime authority to applications built with the Vercel AI SDK for seamless spending control in Next.js and other Vercel-deployed projects. - [Vercel AI SDK integration guide](/how-to/integrating-cycles-with-vercel-ai-sdk) -- [sdk.vercel.ai](https://sdk.vercel.ai) +- [ai-sdk.dev](https://ai-sdk.dev/) ### Spring AI @@ -119,7 +119,7 @@ Budget control for CrewAI multi-agent workflows. Scope budgets per agent and per Guard Pydantic AI agent runs and tool calls with the `@cycles` decorator. Works with structured output and tool scoping. - [Pydantic AI integration guide](/how-to/integrating-cycles-with-pydantic-ai) -- [ai.pydantic.dev](https://ai.pydantic.dev) +- [Pydantic AI documentation](https://pydantic.dev/docs/ai/overview/) ### AnyAgent diff --git a/how-to/evaluate-cycles-for-agent-saas.md b/how-to/evaluate-cycles-for-agent-saas.md index a65c92d8..d9803067 100644 --- a/how-to/evaluate-cycles-for-agent-saas.md +++ b/how-to/evaluate-cycles-for-agent-saas.md @@ -65,14 +65,14 @@ Once the local test passes, try Cycles against the actions in your product that For each, decide: - What action kind does it map to? (`llm.completion`, `web.search`, `message.email.send`, `code.exec.shell`, etc.) -- What should be enforced — spend budget, token budget, risk budget, action-count quota, or some combination? -- What scope does the cap belong on — tenant, workflow, run, agent, tool? +- What should be enforced today — spend, token, or caller-assigned risk budget? Treat action-count quotas as a v0.1.26 preview until the reference server implements them. +- Which standard scope owns the ledger — tenant, workspace, app, workflow, agent, or toolset? Map a run to a unique workflow value when needed. That mapping is the design exercise. See [Assigning Risk Points to Agent Tools](/how-to/assigning-risk-points-to-agent-tools) for the framework, and [Choosing the Right Integration Pattern](/how-to/choosing-the-right-integration-pattern) for where to put the gate (SDK in-process, MCP, gateway, framework plugin). ## The architecture in one sentence -Cycles becomes the **runtime authority layer** between agent intent and external execution: every consequential action passes through `reserve → execute → commit` (or `release` on failure), scoped by tenant / workflow / run / tool. +Cycles becomes a **runtime budget-authority layer** between agent intent and external execution when every selected consequential path passes through `reserve → execute → commit` (or `release` on failure), using the standard subject scopes. Application authorization still decides whether the tool and arguments are permitted. That is the core idea. Multi-tenant isolation, per-tier budgets, [action-governance previews](/protocol/action-governance-preview-in-cycles), OTLP metrics, MCP integration, and dashboard workflows all build on that one reserve-before-execute boundary. diff --git a/how-to/force-releasing-stuck-reservations-as-an-operator.md b/how-to/force-releasing-stuck-reservations-as-an-operator.md index 7950bfce..e34fb27a 100644 --- a/how-to/force-releasing-stuck-reservations-as-an-operator.md +++ b/how-to/force-releasing-stuck-reservations-as-an-operator.md @@ -107,13 +107,13 @@ Fields to expect on the audit entry: | `tenant_id` | Tenant the reservation belonged to | | `metadata.reason` | Free-form text from the request body, sanitized for CR/LF if provided | | `request_id` | The HTTP request id for the release call | -| `trace_id` | The cross-plane trace id for joining to events and webhook deliveries | +| `trace_id` | W3C-compatible trace ID for joining the audit entry to response and application logs | The audit entry is retained under the authenticated-audit retention policy (default 400 days in v0.1.25.20+). This comfortably covers SOC2 audit windows. -### Joining release → events → webhook delivery with `trace_id` +### Joining the release audit entry with `trace_id` -Every response from the runtime server — including the force-release response — carries an `X-Cycles-Trace-Id` header and a `trace_id` field in error bodies (v0.1.25.14+). Use it to see the full consequences of a force-release across planes: +Every response from the runtime server — including the force-release response — carries an `X-Cycles-Trace-Id` header, and error bodies carry a `trace_id` field (v0.1.25.14+). Use the response header to retrieve the matching audit entry: ```bash TID=<32-hex from X-Cycles-Trace-Id response header> @@ -121,18 +121,9 @@ TID=<32-hex from X-Cycles-Trace-Id response header> # The audit entry for this specific release curl -s "http://localhost:7979/v1/admin/audit/logs?trace_id=$TID" \ -H "X-Admin-API-Key: $ADMIN_KEY" - -# Events emitted as a side effect (reservation.released, budget.*) -curl -s "http://localhost:7979/v1/admin/events?trace_id=$TID" \ - -H "X-Admin-API-Key: $ADMIN_KEY" - -# Webhook deliveries that went out — the deliveries endpoint has no trace_id -# query parameter, but each delivery row carries trace_id; filter client-side -curl -s "http://localhost:7979/v1/admin/webhooks//deliveries" \ - -H "X-Admin-API-Key: $ADMIN_KEY" | jq --arg tid "$TID" '.deliveries[] | select(.trace_id == $tid)' ``` -This is useful for confirming that a subscriber alerting channel (oncall rotation, incident tracker) received the `reservation.released` notification before you close the incident. Requires `cycles-server-admin` v0.1.25.31+. See [Correlation and Tracing](/protocol/correlation-and-tracing-in-cycles). +The current reference runtime does not emit `reservation.released`, so an ordinary or force-release operation does not produce a release webhook to confirm. Join the audit row to your incident and application logs using the same trace ID. The admin `trace_id` audit filter requires `cycles-server-admin` v0.1.25.31+. See [Correlation and Tracing](/protocol/correlation-and-tracing-in-cycles). ## Recovery checklist diff --git a/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles.md b/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles.md index a9dfd3c8..fb0da016 100644 --- a/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles.md +++ b/how-to/how-to-estimate-exposure-before-execution-practical-reservation-strategies-for-cycles.md @@ -409,7 +409,7 @@ A team refining reservation quality should track: - unused remainder by action class - actions that regularly exceed estimate - workflows with high variance -- pressure at tenant, workflow, and run scopes +- pressure at tenant and workflow scopes, including workflow ledgers keyed per run - degradation frequency when premium reservations fail These signals tell you where the estimate strategy is helping and where it needs refinement. diff --git a/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles.md b/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles.md index 5ba659b3..76b9e2fb 100644 --- a/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles.md +++ b/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles.md @@ -1,6 +1,6 @@ --- title: "How to Model Tenant, Workflow, and Run Budgets in Cycles" -description: "Design budget policies using tenant, workflow, and run scopes to bound autonomous execution across multiple levels in Cycles." +description: "Design tenant and workflow budget policies, including workflow ledgers keyed per run, to bound instrumented autonomous execution across multiple levels." --- # How to Model Tenant, Workflow, and Run Budgets in Cycles @@ -24,13 +24,13 @@ Instead, it combines multiple scopes such as: - agent - toolset -This article focuses on three of the most important ones: +This article focuses on three useful budgeting patterns: - **tenant budgets** - **workflow budgets** - **run budgets** -These three scopes are often enough to build a strong first production model. +Tenant and workflow are native scopes. “Run budget” is an application pattern: use a unique run ID as the `workflow` subject value when each execution needs its own ledger. All protected calls in the run must use that same value. ## Why multiple scopes matter @@ -55,7 +55,7 @@ Think of the scopes like this: - **Tenant budget** protects the customer or account boundary - **Workflow budget** protects the logical process boundary -- **Run budget** protects the single execution boundary +- **Run budget pattern** uses a workflow ledger to protect a single execution boundary Each one solves a different problem. @@ -84,7 +84,7 @@ It is your product and feature-level control. This answers: ::: info -How much exposure is this individual execution allowed to consume before it stops? +How much submitted exposure can this individual execution consume before the host must stop or degrade protected work? ::: It is your execution safety control. @@ -102,7 +102,7 @@ They are especially important for: - internal business units - account-level usage governance -A tenant budget prevents one customer or account from consuming unbounded resources. +A tenant budget bounds the instrumented exposure submitted against that tenant ledger. It does not cover paths that bypass the integration. ### What tenant budgets are good for @@ -192,16 +192,16 @@ It is where teams begin translating product intent into execution boundaries. ## Run budgets -Run budgets are the most local and execution-specific scope. +Run budgets are an execution-specific mapping onto a standard scope. -In the Cycles protocol, "run" is not a built-in subject field like tenant or workflow. Instead, run-level budgets are modeled by passing a unique run identifier through one of the standard subject fields — for example, setting `workflow="run-12345"` derives the scope `workflow:run-12345`, or the `agent` field can carry the run identifier. This gives each execution its own scope in the budget hierarchy. +In the Cycles protocol, `run` is not a built-in subject field. Model a run-level budget by setting a unique value such as `workflow="run-12345"`, which derives the scope `workflow:run-12345`. Keep the business workflow name and other attribution in application telemetry or metadata when you use the workflow field for the execution ID. Do not use the `dimensions` field for run budgets. Scopes are derived only from the six standard subject fields (tenant, workspace, app, workflow, agent, toolset). The `dimensions` map never derives scopes, and servers MAY ignore `dimensions` entirely for budgeting decisions — a run identifier placed there would not be enforced. A run budget answers: ::: info -How much exposure can this single execution consume before it must stop? +How much submitted exposure can this single execution consume before the host must stop or degrade protected work? ::: This scope is especially important for: @@ -214,7 +214,7 @@ This scope is especially important for: ### Why run budgets matter -Run budgets are your best defense against runaway execution. +When every costly step crosses a mandatory boundary, a per-run workflow ledger is a strong defense against runaway execution. Even if the tenant has plenty of remaining budget, one individual run may still need a hard ceiling. @@ -231,12 +231,12 @@ That protects against: A workflow run might be allowed: - up to 500 units total -- downgrade behavior after 400 units -- hard stop at exhaustion +- application-selected downgrade behavior when a reservation is denied or configured caps require it +- host-side stop at exhaustion This gives each run a bounded envelope. -Step limits (for example, "no more than 10 model/tool steps") are not something budgets enforce. They come from **Caps** — soft-landing constraints such as `max_steps_remaining` that the server can return from `/decide` or alongside an `ALLOW_WITH_CAPS` reservation decision. Combine a run budget (spend ceiling) with Caps (step and tool constraints) for full run-level control. +Step limits (for example, "no more than 10 model/tool steps") are not inferred from budget consumption. An operator can configure standard caps such as `max_steps_remaining` or `max_tool_calls_remaining`, and the server can return them from `/decide` or with `ALLOW_WITH_CAPS`; the application must apply them. Combine a per-run workflow ledger with host-applied caps and separate authorization for layered run control. ### Why run budgets should usually be strict diff --git a/how-to/integrating-cycles-with-anthropic-typescript.md b/how-to/integrating-cycles-with-anthropic-typescript.md index 5fa0c927..4f453754 100644 --- a/how-to/integrating-cycles-with-anthropic-typescript.md +++ b/how-to/integrating-cycles-with-anthropic-typescript.md @@ -38,14 +38,14 @@ const ask = withCycles( { client: cycles, actionKind: "llm.completion", - actionName: "claude-sonnet-4", + actionName: "claude-sonnet-4-6", estimate: () => 2_000_000, actual: (r: Anthropic.Message) => r.usage.input_tokens * 300 + r.usage.output_tokens * 1_500, }, async (prompt: string) => { return anthropic.messages.create({ - model: "claude-sonnet-4-20250514", + model: "claude-sonnet-4-6", max_tokens: 1024, messages: [{ role: "user", content: prompt }], }); @@ -74,7 +74,7 @@ setDefaultClient(cyclesClient); const anthropic = new Anthropic(); -// Claude Sonnet 4 pricing (microcents per token) +// Claude Sonnet 4.6 pricing (microcents per token; verified 2026-07-24) const INPUT_PRICE = 300; // $3.00 / 1M tokens const OUTPUT_PRICE = 1_500; // $15.00 / 1M tokens const DEFAULT_MAX_TOKENS = 1024; @@ -83,7 +83,7 @@ const sendMessage = withCycles( { client: cyclesClient, actionKind: "llm.completion", - actionName: "claude-sonnet-4-20250514", + actionName: "claude-sonnet-4-6", estimate: (prompt: string) => { const inputTokens = Math.ceil(prompt.length / 4); return inputTokens * INPUT_PRICE + DEFAULT_MAX_TOKENS * OUTPUT_PRICE; @@ -103,7 +103,7 @@ const sendMessage = withCycles( } const response = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", + model: "claude-sonnet-4-6", max_tokens: maxTokens, messages: [{ role: "user", content: prompt }], }); @@ -159,7 +159,7 @@ async function streamWithBudget(prompt: string) { estimate, unit: "USD_MICROCENTS", actionKind: "llm.completion", - actionName: "claude-sonnet-4-20250514", + actionName: "claude-sonnet-4-6", }); try { @@ -171,7 +171,7 @@ async function streamWithBudget(prompt: string) { // 2. Stream the response const stream = anthropic.messages.stream({ - model: "claude-sonnet-4-20250514", + model: "claude-sonnet-4-6", max_tokens: maxTokens, messages: [{ role: "user", content: prompt }], }); @@ -233,7 +233,7 @@ async function chatWithTools(prompt: string): Promise { estimate: 2_000_000, unit: "USD_MICROCENTS", actionKind: "llm.completion", - actionName: "claude-sonnet-4-20250514", + actionName: "claude-sonnet-4-6", }); } catch (err) { if (err instanceof BudgetExceededError) { @@ -244,7 +244,7 @@ async function chatWithTools(prompt: string): Promise { try { const response = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", + model: "claude-sonnet-4-6", max_tokens: 1024, tools: TOOLS, messages, @@ -300,9 +300,11 @@ Adjust these constants for the model you use: | Model | Input (microcents/token) | Output (microcents/token) | |-------|--------------------------|---------------------------| -| Claude Haiku 3.5 | 80 | 400 | -| Claude Sonnet 4 | 300 | 1,500 | -| Claude Opus 4 | 1,500 | 7,500 | +| Claude Haiku 4.5 | 100 | 500 | +| Claude Sonnet 4.6 | 300 | 1,500 | +| Claude Opus 4.8 | 500 | 2,500 | + +Rates verified against Anthropic's pricing page on July 24, 2026. Recheck pricing before deploying, especially if you switch models, use prompt caching, or select regional inference. ## Key points diff --git a/how-to/integrating-cycles-with-anthropic.md b/how-to/integrating-cycles-with-anthropic.md index 85e3782e..37d28ddd 100644 --- a/how-to/integrating-cycles-with-anthropic.md +++ b/how-to/integrating-cycles-with-anthropic.md @@ -29,10 +29,10 @@ from runcycles import CyclesClient, CyclesConfig, cycles, set_default_client set_default_client(CyclesClient(CyclesConfig.from_env())) -@cycles(estimate=2_000_000, action_kind="llm.completion", action_name="claude-sonnet-4") +@cycles(estimate=2_000_000, action_kind="llm.completion", action_name="claude-sonnet-4-6") def ask(prompt: str) -> str: return Anthropic().messages.create( - model="claude-sonnet-4-20250514", + model="claude-sonnet-4-6", max_tokens=1024, messages=[{"role": "user", "content": prompt}], ).content[0].text @@ -71,7 +71,7 @@ PRICE_PER_OUTPUT_TOKEN = 1_500 # $15.00 / 1M tokens in microcents + result["usage"]["output_tokens"] * PRICE_PER_OUTPUT_TOKEN ), action_kind="llm.completion", - action_name="claude-sonnet-4-20250514", + action_name="claude-sonnet-4-6", unit="USD_MICROCENTS", ttl_ms=60_000, ) @@ -81,7 +81,7 @@ def send_message(prompt: str, max_tokens: int = 1024) -> dict: max_tokens = min(max_tokens, ctx.caps.max_tokens) response = anthropic_client.messages.create( - model="claude-sonnet-4-20250514", + model="claude-sonnet-4-6", max_tokens=max_tokens, messages=[{"role": "user", "content": prompt}], ) @@ -127,7 +127,7 @@ def chat_with_tools(prompt: str) -> str: res = client.create_reservation(ReservationCreateRequest( idempotency_key=key, subject=Subject(tenant="acme", agent="tool-agent"), - action=Action(kind="llm.completion", name="claude-sonnet-4-20250514", + action=Action(kind="llm.completion", name="claude-sonnet-4-6", tags=[f"turn-{turn}"]), estimate=Amount(unit=Unit.USD_MICROCENTS, amount=2_000_000), ttl_ms=30_000, @@ -147,7 +147,7 @@ def chat_with_tools(prompt: str) -> str: # Call Claude with tools; release the reservation if the call fails try: response = anthropic_client.messages.create( - model="claude-sonnet-4-20250514", + model="claude-sonnet-4-6", max_tokens=1024, tools=TOOLS, messages=messages, @@ -203,9 +203,11 @@ Adjust these constants for the model you use: | Model | Input (microcents/token) | Output (microcents/token) | |-------|--------------------------|---------------------------| -| Claude Haiku 3.5 | 80 | 400 | -| Claude Sonnet 4 | 300 | 1,500 | -| Claude Opus 4 | 1,500 | 7,500 | +| Claude Haiku 4.5 | 100 | 500 | +| Claude Sonnet 4.6 | 300 | 1,500 | +| Claude Opus 4.8 | 500 | 2,500 | + +Rates verified against Anthropic's pricing page on July 24, 2026. Recheck pricing before deploying, especially if you switch models, use prompt caching, or select regional inference. ## Key points diff --git a/how-to/integrating-cycles-with-aws-bedrock.md b/how-to/integrating-cycles-with-aws-bedrock.md index 0b2381e8..7239f0c1 100644 --- a/how-to/integrating-cycles-with-aws-bedrock.md +++ b/how-to/integrating-cycles-with-aws-bedrock.md @@ -19,9 +19,9 @@ This guide shows how to add budget governance to AWS Bedrock model invocations u npm install runcycles @aws-sdk/client-bedrock-runtime ``` -## Non-streaming calls with withCycles +## Non-streaming calls with `withCycles` -For non-streaming `InvokeModel` calls, use the `withCycles` HOF: +For non-streaming `InvokeModel` calls, use the `withCycles` higher-order function. The example intentionally uses Amazon Bedrock's platform-specific Claude Sonnet 4 model ID, which AWS currently classifies as legacy in some regions with an October 14, 2026 end-of-life date. Confirm availability in your region and replace the ID with a supported Bedrock model before deploying. ```typescript import { InvokeModelCommand, BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"; diff --git a/how-to/integrating-cycles-with-fastapi.md b/how-to/integrating-cycles-with-fastapi.md index f823ddc4..a4f9e6b8 100644 --- a/how-to/integrating-cycles-with-fastapi.md +++ b/how-to/integrating-cycles-with-fastapi.md @@ -177,4 +177,4 @@ See [`examples/fastapi_integration.py`](https://github.com/runcycles/cycles-clie - [Handling Streaming Responses](/how-to/handling-streaming-responses-with-cycles) — budget-managed streaming - [Testing with Cycles](/how-to/testing-with-cycles) — testing budget-guarded code - [Production Operations Guide](/how-to/production-operations-guide) — running Cycles in production -- [FastAPI example (Python)](https://github.com/runcycles/cycles-client-python/tree/main/examples/fastapi_integration.py) — runnable FastAPI integration +- [FastAPI example (Python)](https://github.com/runcycles/cycles-client-python/blob/main/examples/fastapi_integration.py) — runnable FastAPI integration diff --git a/how-to/integrating-cycles-with-groq.md b/how-to/integrating-cycles-with-groq.md index a9297d5a..ddb37afe 100644 --- a/how-to/integrating-cycles-with-groq.md +++ b/how-to/integrating-cycles-with-groq.md @@ -5,7 +5,7 @@ description: "Add budget governance to Groq API calls using the OpenAI SDK with # Integrating Cycles with Groq -This guide shows how to add budget governance to [Groq](https://groq.com/) API calls. Groq provides an OpenAI-compatible API, so you use the standard OpenAI SDK with a different `base_url`. All Cycles patterns from the [OpenAI integration](/how-to/integrating-cycles-with-openai) apply directly. +This guide shows how to add budget governance to [Groq](https://groq.com/) API calls. Groq provides an OpenAI-compatible Chat Completions API, so the examples use the OpenAI SDK with a different `base_url`. Confirm Groq support before copying provider-specific OpenAI features beyond that shared surface. ## Prerequisites @@ -30,16 +30,16 @@ from runcycles import CyclesClient, CyclesConfig, cycles, set_default_client set_default_client(CyclesClient(CyclesConfig.from_env())) groq = OpenAI(base_url="https://api.groq.com/openai/v1", api_key="gsk_...") -@cycles(estimate=50_000, action_kind="llm.completion", action_name="llama-4-scout") +@cycles(estimate=100_000, action_kind="llm.completion", action_name="openai/gpt-oss-120b") def ask(prompt: str) -> str: return groq.chat.completions.create( - model="meta-llama/llama-4-scout-17b-16e-instruct", + model="openai/gpt-oss-120b", messages=[{"role": "user", "content": prompt}], ).choices[0].message.content print(ask("What is budget authority?")) ``` -Same OpenAI SDK, same `@cycles` decorator — just a different `base_url`. Notice the estimate is much lower than GPT-4o because Groq's pricing is 10-50x cheaper. +Same SDK shape, same `@cycles` decorator, different `base_url`. Size the estimate from the selected Groq model's current price and a conservative token ceiling. ::: ## Basic pattern @@ -59,9 +59,10 @@ groq = OpenAI( api_key=os.environ["GROQ_API_KEY"], ) -# Llama 4 Scout on Groq -PRICE_PER_INPUT_TOKEN = 11 # $0.11 / 1M tokens -PRICE_PER_OUTPUT_TOKEN = 34 # $0.34 / 1M tokens +# GPT-OSS 120B on Groq, checked 2026-07-24: +# $0.15 / 1M input tokens and $0.60 / 1M output tokens. +PRICE_PER_INPUT_TOKEN = 15 +PRICE_PER_OUTPUT_TOKEN = 60 @cycles( estimate=lambda prompt, **kw: len(prompt.split()) * 2 * PRICE_PER_INPUT_TOKEN @@ -71,7 +72,7 @@ PRICE_PER_OUTPUT_TOKEN = 34 # $0.34 / 1M tokens + result["usage"]["completion_tokens"] * PRICE_PER_OUTPUT_TOKEN ), action_kind="llm.completion", - action_name="meta-llama/llama-4-scout-17b-16e-instruct", + action_name="openai/gpt-oss-120b", unit="USD_MICROCENTS", ) def chat(prompt: str, max_tokens: int = 1024) -> dict: @@ -80,7 +81,7 @@ def chat(prompt: str, max_tokens: int = 1024) -> dict: max_tokens = min(max_tokens, ctx.caps.max_tokens) response = groq.chat.completions.create( - model="meta-llama/llama-4-scout-17b-16e-instruct", + model="openai/gpt-oss-120b", messages=[{"role": "user", "content": prompt}], max_tokens=max_tokens, ) @@ -113,14 +114,14 @@ const groq = new OpenAI({ apiKey: process.env.GROQ_API_KEY, }); -const INPUT_PRICE = 11; -const OUTPUT_PRICE = 34; +const INPUT_PRICE = 15; +const OUTPUT_PRICE = 60; const chat = withCycles( { client: cycles, actionKind: "llm.completion", - actionName: "meta-llama/llama-4-scout-17b-16e-instruct", + actionName: "openai/gpt-oss-120b", estimate: (prompt: string) => { const inputTokens = Math.ceil(prompt.length / 4); return inputTokens * INPUT_PRICE + 1024 * OUTPUT_PRICE; @@ -137,7 +138,7 @@ const chat = withCycles( } return groq.chat.completions.create({ - model: "meta-llama/llama-4-scout-17b-16e-instruct", + model: "openai/gpt-oss-120b", max_tokens: maxTokens, messages: [{ role: "user", content: prompt }], }); @@ -147,31 +148,30 @@ const chat = withCycles( ## Groq pricing reference -Groq hosts open-source models on custom LPU hardware. Pricing is significantly lower than proprietary models: +Groq's on-demand list prices were rechecked on July 24, 2026: -| Model | Input (per 1M tokens) | Output (per 1M tokens) | Input (microcents/token) | Output (microcents/token) | +| Model | Input (per 1M tokens) | Output (per 1M tokens) | Input (microcents/1K tokens) | Output (microcents/1K tokens) | |---|---|---|---|---| -| Llama 4 Scout 17B | $0.11 | $0.34 | 11 | 34 | -| Llama 3.3 70B | $0.59 | $0.79 | 59 | 79 | -| Llama 3.1 8B | $0.05 | $0.08 | 5 | 8 | - -For comparison, GPT-4o is 250/1,000 microcents per token — **23x more expensive** than Llama 4 Scout on Groq for input, **29x more** for output. +| `openai/gpt-oss-20b` | $0.075 | $0.30 | 7,500 | 30,000 | +| `openai/gpt-oss-120b` | $0.15 | $0.60 | 15,000 | 60,000 | +| `qwen/qwen3.6-27b` | $0.60 | $3.00 | 60,000 | 300,000 | ::: info Note -Groq pricing changes. Check [groq.com/pricing](https://groq.com/pricing) for current rates. +Groq pricing and model availability change. Check [Groq pricing](https://groq.com/pricing) and [model deprecations](https://console.groq.com/docs/deprecations) before deploying. Llama 4 Scout shut down for free and developer tiers on July 17, 2026; Llama 3.1 8B and Llama 3.3 70B are scheduled to shut down for those tiers on August 16, 2026. ::: ## Model-downgrade degradation pattern -The most powerful Cycles + Groq pattern: when your primary model's budget runs low, automatically downgrade to a cheaper Groq model instead of denying the request entirely. +An application can try a lower-estimate Groq route after the primary route's reservation is rejected. Cycles does not choose the fallback or detect a “low budget” threshold automatically; the application owns that policy. ```python from runcycles import BudgetExceededError -# Primary: GPT-4o (expensive, high quality) +# Primary provider route primary_client = OpenAI() +PRIMARY_MODEL = os.environ["PRIMARY_MODEL"] -# Fallback: Llama 4 Scout on Groq (cheap, good quality) +# Lower-estimate Groq route fallback_client = OpenAI( base_url="https://api.groq.com/openai/v1", api_key=os.environ["GROQ_API_KEY"], @@ -180,29 +180,29 @@ fallback_client = OpenAI( @cycles( estimate=1_500_000, action_kind="llm.completion", - action_name="gpt-4o", + action_name="primary-model-route", ) def primary_chat(prompt: str) -> dict: response = primary_client.chat.completions.create( - model="gpt-4o", + model=PRIMARY_MODEL, messages=[{"role": "user", "content": prompt}], ) - return {"content": response.choices[0].message.content, "model": "gpt-4o"} + return {"content": response.choices[0].message.content, "model": PRIMARY_MODEL} @cycles( - estimate=50_000, + estimate=100_000, action_kind="llm.completion", - action_name="llama-4-scout", + action_name="openai/gpt-oss-120b", ) def fallback_chat(prompt: str) -> dict: response = fallback_client.chat.completions.create( - model="meta-llama/llama-4-scout-17b-16e-instruct", + model="openai/gpt-oss-120b", messages=[{"role": "user", "content": prompt}], ) - return {"content": response.choices[0].message.content, "model": "llama-4-scout"} + return {"content": response.choices[0].message.content, "model": "openai/gpt-oss-120b"} def chat_with_downgrade(prompt: str) -> dict: - """Try GPT-4o first; fall back to Groq if budget is exhausted.""" + """Try the primary route, then a lower-estimate Groq route.""" try: return primary_chat(prompt) except BudgetExceededError: @@ -210,22 +210,22 @@ def chat_with_downgrade(prompt: str) -> dict: ``` This pattern gives you: -- **Full quality** when budget allows (GPT-4o at $2.50/$10 per 1M tokens) -- **Continued service** when budget is low (Llama 4 Scout at $0.11/$0.34 per 1M tokens) -- **Per-model observability** — Cycles tracks spend separately for each `action_name` +- **An application-owned fallback** after the primary reservation is rejected +- **A separately estimated Groq route** that may still fit the remaining ledger +- **Per-model attribution** in reservation records through distinct `action_name` values See [Degradation Paths](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer) for more strategies. ## Key points - **Same SDK, different `base_url`.** Groq uses the OpenAI-compatible API — no new SDK to learn. -- **Much lower estimates.** Groq models are 10-50x cheaper than GPT-4o. Adjust your `estimate` values accordingly. -- **Model downgrade is the killer pattern.** Use Groq as a budget-aware fallback when your primary model's budget runs low. -- **All OpenAI patterns apply.** Everything from the [OpenAI integration guide](/how-to/integrating-cycles-with-openai) works with Groq — decorators, streaming, caps, metrics. +- **Model-specific estimates.** Calculate from current Groq list or contracted rates; do not copy an estimate from a different model. +- **Fallback is application policy.** A rejected primary reservation can trigger a separately budgeted Groq route. +- **Compatibility has limits.** The OpenAI-compatible Chat Completions shape enables shared client code, but verify streaming, tool, and response-field behavior for the selected Groq model. ## Next steps -- [Integrating with OpenAI](/how-to/integrating-cycles-with-openai) — full OpenAI patterns (all apply to Groq) +- [Integrating with OpenAI](/how-to/integrating-cycles-with-openai) — related OpenAI SDK lifecycle patterns - [Integrating with OpenAI (TypeScript)](/how-to/integrating-cycles-with-openai-typescript) — TypeScript streaming patterns - [Degradation Paths](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer) — model downgrade and other strategies - [Cost Estimation Cheat Sheet](/how-to/cost-estimation-cheat-sheet) — pricing reference for all providers diff --git a/how-to/integrating-cycles-with-langchain.md b/how-to/integrating-cycles-with-langchain.md index c43f22c5..eb009f50 100644 --- a/how-to/integrating-cycles-with-langchain.md +++ b/how-to/integrating-cycles-with-langchain.md @@ -23,7 +23,7 @@ The **middleware path is dramatically better for `create_agent` users**: model c The [`langchain-runcycles`](https://pypi.org/project/langchain-runcycles/) package provides three `AgentMiddleware` subclasses that plug into `langchain.agents.create_agent`: - **`CyclesModelGate`** (v0.1.5+) — runs before every LLM call (`wrap_model_call`). Authorizes via `client.decide()` and/or reserves budget. Returns a `ModelResponse` carrying the denial reason on deny so the agent terminates naturally. -- **`CyclesToolGate`** — intercepts every tool call (`wrap_tool_call`). Authorizes via `client.decide()` and/or reserves budget. Returns a `ToolMessage` on denial so the model can recover gracefully. +- **`CyclesToolGate`** — intercepts tool calls routed through `wrap_tool_call`, performs a budget preflight with `client.decide()` and/or reserves budget, and returns a `ToolMessage` when the budget check rejects. Host tool authorization remains separate. - **`CyclesFanOutGate`** — runs before every model turn (`before_model`). Halts the agent (with `jump_to: "end"`) when a turn cap is reached or an external policy says stop. Compose them in a single `middleware=[...]` list. The natural ordering is **fan-out → model → tool**: runaway loops halt before model spend, model spend reserves before tool side effects. diff --git a/how-to/integrating-cycles-with-mcp.md b/how-to/integrating-cycles-with-mcp.md index 78d0d79a..1b82c1ba 100644 --- a/how-to/integrating-cycles-with-mcp.md +++ b/how-to/integrating-cycles-with-mcp.md @@ -47,7 +47,7 @@ export CYCLES_MOCK=true > **Need setup help?** See [Getting Started with the MCP Server](/quickstart/getting-started-with-the-mcp-server) for per-host configuration (Claude Desktop, Claude Code, Cursor, Windsurf). -## Pattern 1: Simple reserve/commit +## Pattern 1: Simple reserve-commit The most common pattern — reserve budget before a costly operation, commit actual usage after: @@ -78,7 +78,7 @@ An accepted response includes `decision: "ALLOW"` or `decision: "ALLOW_WITH_CAPS "tokensInput": 1200, "tokensOutput": 800, "latencyMs": 2500, - "modelVersion": "claude-sonnet-4-20250514" + "modelVersion": "claude-sonnet-4-6" } } ``` diff --git a/how-to/integrating-cycles-with-ollama.md b/how-to/integrating-cycles-with-ollama.md index cf70d5ef..7d4c9eea 100644 --- a/how-to/integrating-cycles-with-ollama.md +++ b/how-to/integrating-cycles-with-ollama.md @@ -170,7 +170,7 @@ except BudgetExceededError: result = {"content": "GPU budget exhausted — try again later.", "cost": 0} ``` -For shared GPU infrastructure, this prevents one tenant from monopolizing the hardware. The budget authority can set per-tenant limits and Cycles enforces them before inference begins. +For shared GPU infrastructure, a mandatory wrapper can stop a tenant's protected inference calls once its configured compute budget is unavailable. Scheduler fairness, GPU isolation, and calls that bypass the wrapper remain infrastructure concerns. See [Degradation Paths](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer) for patterns like queueing, model downgrade, and caching. diff --git a/how-to/integrating-cycles-with-openai-typescript.md b/how-to/integrating-cycles-with-openai-typescript.md index 65dfffcd..c2174756 100644 --- a/how-to/integrating-cycles-with-openai-typescript.md +++ b/how-to/integrating-cycles-with-openai-typescript.md @@ -38,14 +38,14 @@ const ask = withCycles( { client: cycles, actionKind: "llm.completion", - actionName: "gpt-4o", - estimate: () => 1_500_000, + actionName: "gpt-5.6-luna", + estimate: () => 1_000_000, actual: (r: OpenAI.ChatCompletion) => - (r.usage?.prompt_tokens ?? 0) * 250 + (r.usage?.completion_tokens ?? 0) * 1_000, + (r.usage?.prompt_tokens ?? 0) * 100 + (r.usage?.completion_tokens ?? 0) * 600, }, async (prompt: string) => { return openai.chat.completions.create({ - model: "gpt-4o", + model: "gpt-5.6-luna", messages: [{ role: "user", content: prompt }], }); }, @@ -73,16 +73,17 @@ setDefaultClient(cyclesClient); const openai = new OpenAI(); -// GPT-4o pricing (microcents per token) -const INPUT_PRICE = 250; // $2.50 / 1M tokens -const OUTPUT_PRICE = 1_000; // $10.00 / 1M tokens +// GPT-5.6 Luna standard pricing (microcents per token) +const MODEL = "gpt-5.6-luna"; +const INPUT_PRICE = 100; // $1.00 / 1M uncached input tokens +const OUTPUT_PRICE = 600; // $6.00 / 1M output tokens const DEFAULT_MAX_TOKENS = 1024; const chatCompletion = withCycles( { client: cyclesClient, actionKind: "llm.completion", - actionName: "gpt-4o", + actionName: MODEL, estimate: (prompt: string) => { const inputTokens = Math.ceil(prompt.length / 4); return inputTokens * INPUT_PRICE + DEFAULT_MAX_TOKENS * OUTPUT_PRICE; @@ -102,7 +103,7 @@ const chatCompletion = withCycles( } const response = await openai.chat.completions.create({ - model: "gpt-4o", + model: MODEL, max_completion_tokens: maxTokens, messages: [{ role: "user", content: prompt }], }); @@ -145,8 +146,9 @@ import { const cyclesClient = new CyclesClient(CyclesConfig.fromEnv()); const openai = new OpenAI(); -const INPUT_PRICE = 250; -const OUTPUT_PRICE = 1_000; +const MODEL = "gpt-5.6-luna"; +const INPUT_PRICE = 100; +const OUTPUT_PRICE = 600; async function streamWithBudget(prompt: string) { const estimatedInputTokens = Math.ceil(prompt.length / 4); @@ -158,7 +160,7 @@ async function streamWithBudget(prompt: string) { estimate, unit: "USD_MICROCENTS", actionKind: "llm.completion", - actionName: "gpt-4o", + actionName: MODEL, }); try { @@ -170,7 +172,7 @@ async function streamWithBudget(prompt: string) { // 2. Stream the response const stream = await openai.chat.completions.create({ - model: "gpt-4o", + model: MODEL, max_completion_tokens: maxTokens, messages: [{ role: "user", content: prompt }], stream: true, @@ -195,7 +197,7 @@ async function streamWithBudget(prompt: string) { await handle.commit(actualCost, { tokensInput: promptTokens, tokensOutput: completionTokens, - modelVersion: "gpt-4o", + modelVersion: MODEL, }); } catch (err) { await handle.release("stream_error"); @@ -214,6 +216,9 @@ Adjust these constants for the model you use: | Model | Input (microcents/token) | Output (microcents/token) | |-------|--------------------------|---------------------------| +| gpt-5.6-sol | 500 | 3,000 | +| gpt-5.6-terra | 250 | 1,500 | +| gpt-5.6-luna | 100 | 600 | | gpt-4o | 250 | 1,000 | | gpt-4o-mini | 15 | 60 | | gpt-4.1 | 200 | 800 | @@ -222,6 +227,8 @@ Adjust these constants for the model you use: | o3 | 200 | 800 | | o4-mini | 110 | 440 | +The snippets use standard uncached GPT-5.6 Luna pricing for requests with at most 272,000 input tokens. GPT-5.6 cache reads are cheaper, explicit cache writes cost 1.25 times the uncached input rate, and longer requests use higher rates for the full request. If you use those features, compute actual cost from the provider's detailed usage fields. OpenAI recommends the Responses API for reasoning, tool-calling, and multi-turn workflows; Chat Completions remains supported for these single-turn examples. See the [GPT-5.6 model guide](https://developers.openai.com/api/docs/guides/latest-model) and [GPT-5.6 Luna model page](https://developers.openai.com/api/docs/models/gpt-5.6-luna). + See [Cost Estimation Cheat Sheet](/how-to/cost-estimation-cheat-sheet) for the full pricing reference. ## Key points diff --git a/how-to/integrating-cycles-with-openai.md b/how-to/integrating-cycles-with-openai.md index 46475eb7..4efda9f7 100644 --- a/how-to/integrating-cycles-with-openai.md +++ b/how-to/integrating-cycles-with-openai.md @@ -50,10 +50,10 @@ from runcycles import CyclesClient, CyclesConfig, cycles, set_default_client set_default_client(CyclesClient(CyclesConfig.from_env())) -@cycles(estimate=1_500_000, action_kind="llm.completion", action_name="gpt-4o") +@cycles(estimate=1_000_000, action_kind="llm.completion", action_name="gpt-5.6-luna") def ask(prompt: str) -> str: return OpenAI().chat.completions.create( - model="gpt-4o", + model="gpt-5.6-luna", messages=[{"role": "user", "content": prompt}], ).choices[0].message.content @@ -80,9 +80,11 @@ config = CyclesConfig.from_env() set_default_client(CyclesClient(config)) openai_client = OpenAI() -# Per-token pricing in USD microcents (1 USD = 100_000_000 microcents) -PRICE_PER_INPUT_TOKEN = 250 # $2.50 / 1M tokens -PRICE_PER_OUTPUT_TOKEN = 1_000 # $10.00 / 1M tokens +# GPT-5.6 Luna standard pricing in USD microcents +# (1 USD = 100_000_000 microcents) +MODEL = "gpt-5.6-luna" +PRICE_PER_INPUT_TOKEN = 100 # $1.00 / 1M uncached input tokens +PRICE_PER_OUTPUT_TOKEN = 600 # $6.00 / 1M output tokens @cycles( estimate=lambda prompt, **kw: len(prompt.split()) * 2 * PRICE_PER_INPUT_TOKEN @@ -92,7 +94,7 @@ PRICE_PER_OUTPUT_TOKEN = 1_000 # $10.00 / 1M tokens + result["usage"]["completion_tokens"] * PRICE_PER_OUTPUT_TOKEN ), action_kind="llm.completion", - action_name="gpt-4o", + action_name=MODEL, unit="USD_MICROCENTS", ttl_ms=60_000, ) @@ -104,9 +106,9 @@ def chat_completion(prompt: str, max_tokens: int = 1024) -> dict: max_tokens = min(max_tokens, ctx.caps.max_tokens) response = openai_client.chat.completions.create( - model="gpt-4o", + model=MODEL, messages=[{"role": "user", "content": prompt}], - max_tokens=max_tokens, + max_completion_tokens=max_tokens, ) # Report metrics @@ -141,7 +143,11 @@ For production use, consider using `tiktoken` for accurate input token counts: ```python import tiktoken -enc = tiktoken.encoding_for_model("gpt-4o") +try: + enc = tiktoken.encoding_for_model(MODEL) +except KeyError: + # Keep this fallback aligned with the model's documented tokenizer. + enc = tiktoken.get_encoding("o200k_base") def estimate_cost(prompt: str, max_tokens: int = 1024) -> int: input_tokens = len(enc.encode(prompt)) @@ -151,6 +157,8 @@ def estimate_cost(prompt: str, max_tokens: int = 1024) -> int: ) ``` +The constants above use standard uncached pricing for requests with at most 272,000 input tokens. GPT-5.6 cache reads are cheaper, explicit cache writes cost 1.25 times the uncached input rate, and longer requests use higher rates for the full request. If you use those features, calculate `actual` from the provider's detailed usage fields rather than the simplified formula above. OpenAI recommends the Responses API for reasoning, tool-calling, and multi-turn workflows; Chat Completions remains supported for this single-turn example. See the [GPT-5.6 model guide](https://developers.openai.com/api/docs/guides/latest-model) and [GPT-5.6 Luna model page](https://developers.openai.com/api/docs/models/gpt-5.6-luna). + ## Handling budget exhaustion When the budget is insufficient, the `@cycles` decorator raises `BudgetExceededError` **without** calling OpenAI: diff --git a/how-to/integrating-cycles-with-openclaw.md b/how-to/integrating-cycles-with-openclaw.md index 217c021d..67145078 100644 --- a/how-to/integrating-cycles-with-openclaw.md +++ b/how-to/integrating-cycles-with-openclaw.md @@ -21,24 +21,24 @@ This guide shows how to add budget enforcement to OpenClaw agents using the [`cy AI agents make autonomous decisions — calling models, invoking tools, retrying on failure — with no human in the loop. Without runtime enforcement: -- **Runaway spend** — a single [runaway agent](/incidents/runaway-agents-tool-loops-and-budget-overruns-the-incidents-cycles-is-designed-to-prevent) can blow through an entire budget in minutes. Provider spending caps are account-wide and react too slowly. Rate limits don't account for cost. +- **Runaway spend** — a single [runaway agent](/incidents/runaway-agents-tool-loops-and-budget-overruns-the-incidents-cycles-is-designed-to-prevent) can consume substantial budget quickly. Provider budgets, credits, and quotas use their own scopes and semantics; request-count limits do not account for variable call cost. - **Uncontrolled side-effects** — an agent can send hundreds of emails, trigger deployments, or call dangerous APIs with nothing to stop it. Cost limits alone don't help — some actions are consequential regardless of price. - **Noisy neighbors** — in multi-tenant or multi-user setups, one agent can consume the entire team budget, starving other users. - **No session-level cost visibility** — when a session ends, you have no idea what it spent, which tools it called most, or whether it was cost-efficient. - **Abrupt failure** — budget runs out and the agent crashes instead of adapting. -This plugin solves all five — and goes further. Every model call and tool invocation is budget-checked *before* execution. When budget runs low, models are automatically downgraded, expensive tools are disabled, and the agent is told about its remaining budget via prompt hints so it can self-regulate. Side-effects are capped per tool via `toolCallLimits`. Spend is isolated per user, session, or team. And every session produces a full cost breakdown. +The plugin adds budget checks to model and tool calls that pass through OpenClaw's supported lifecycle hooks. With `modelFallbacks`, low-budget strategies, and `toolCallLimits` configured, it can downgrade matching models, disable expensive tools, add remaining-budget prompt hints, and cap per-tool invocation counts. Standard subject fields in `budgetScope` select enforceable tenant, workspace, app, workflow, agent, or toolset ledgers; `userId` and `sessionId` are attribution dimensions unless you map them to those fields. Session summaries report the plugin's estimated model and tool costs. Beyond enforcement, the plugin actively protects you: -- **Burn rate anomaly detection** catches runaway tool loops before they exhaust budget — if spending spikes 3x above the session average, the plugin emits `cycles.budget.burn_rate_anomaly` to your OTLP backend for immediate alerting +- **Burn rate anomaly detection** emits `cycles.budget.burn_rate_anomaly` when the current window exceeds the configured comparison threshold; your OTLP backend decides whether to alert or intervene - **Predictive exhaustion warnings** estimate when budget will run out and emit `cycles.budget.exhaustion_forecast_ms` before it happens, so you can fund the budget or wind down gracefully - **Automatic retry with backoff** on transient Cycles server errors (429/503/504) prevents spurious denials during load spikes - **Reservation heartbeat** auto-extends long-running tool reservations so cost tracking doesn't silently break when a tool exceeds the default 60s TTL -- **Full observability** via the built-in OTLP HTTP adapter (pipe 12 metrics into Datadog, Prometheus, Grafana, or any OTLP collector — configured through `otlpMetricsEndpoint`) and opt-in session event logs for debugging exactly what happened +- **Budget observability** via the built-in OTLP HTTP adapter (14 metrics when the current features are enabled, configured through `otlpMetricsEndpoint`) and opt-in session logs of the plugin's reserve, commit, deny, block, and release decisions - **Unconfigured tool detection** reports which tools are using default cost estimates so you can tune `toolBaseCosts` after every session -The result: predictable spend, controlled behavior, and full visibility — even when agents run autonomously for hours. +The result is a mandatory budget boundary for the hook paths the plugin covers, plus configurable degradation and budget telemetry. External model and tool outcomes still belong in application or provider logs. Install, configure 3 fields, done. No agent code changes required. @@ -176,12 +176,12 @@ Model fallbacks support both single values and ordered chains. When budget is lo "config": { "tenant": "acme", "modelFallbacks": { - "anthropic/claude-opus-4-20250514": ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-haiku-4-5-20251001"], + "anthropic/claude-opus-4-8": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5-20251001"], "openai/gpt-4o": "openai/gpt-4o-mini" }, "modelBaseCosts": { - "anthropic/claude-opus-4-20250514": 1500000, - "anthropic/claude-sonnet-4-20250514": 300000, + "anthropic/claude-opus-4-8": 500000, + "anthropic/claude-sonnet-4-6": 300000, "anthropic/claude-haiku-4-5-20251001": 100000, "openai/gpt-4o": 1000000, "openai/gpt-4o-mini": 100000 @@ -448,7 +448,7 @@ The plugin tracks per-tool and per-model cost breakdowns throughout the session. - Tenant, budget, user, and session identifiers - Final remaining/spent/reserved balances - Total reservations made -- Per-component cost breakdown (e.g., `tool:web_search`, `model:anthropic/claude-sonnet-4-20250514`) +- Per-component cost breakdown (e.g., `tool:web_search`, `model:anthropic/claude-sonnet-4-6`) - Per-tool invocation counts (e.g., `{ web_search: 15, code_execution: 3 }`) - Session timing (start/end timestamps) - Average cost and estimated remaining calls @@ -530,7 +530,7 @@ Surface hierarchical budget information by setting a parent budget ID: } ``` -When `parentBudgetId` is set, the pool balance is included in budget snapshots and prompt hints (e.g., "Team pool: 50000000 remaining."). Reservations target the individual scope — the Cycles server handles hierarchical deduction from the pool. +When `parentBudgetId` is set, the matching balance is included in budget snapshots and prompt hints (for example, "Team pool: 50000000 remaining."). This setting is read-only visibility: reservations still target `budgetScope`, and `parentBudgetId` does not add another enforced ledger. To enforce a broader limit as well, create a budget at a broader standard scope that is actually present in the reservation subject. ## Dry-run mode @@ -623,7 +623,7 @@ The plugin also warns about common misconfigurations on startup (e.g., `downgrad With `logLevel: "debug"`, you'll see per-call activity: ``` -[openclaw-budget-guard] before_model_resolve: model=anthropic/claude-sonnet-4-20250514 level=healthy +[openclaw-budget-guard] before_model_resolve: model=anthropic/claude-sonnet-4-6 level=healthy [openclaw-budget-guard] before_prompt_build: injecting hint (142 chars) [openclaw-budget-guard] Tool "web_search" has no entry in toolBaseCosts — using default estimate (100000 USD_MICROCENTS) [openclaw-budget-guard] before_tool_call: tool=web_search callId=abc123 estimate=100000 @@ -795,11 +795,11 @@ For production agents handling real spend. Blocks on exhaustion, downgrades mode "failClosed": true, "lowBudgetStrategies": ["downgrade_model", "disable_expensive_tools", "limit_remaining_calls"], "modelFallbacks": { - "anthropic/claude-opus-4-20250514": ["anthropic/claude-sonnet-4-20250514", "anthropic/claude-haiku-4-5-20251001"] + "anthropic/claude-opus-4-8": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5-20251001"] }, "modelBaseCosts": { - "anthropic/claude-opus-4-20250514": 1500000, - "anthropic/claude-sonnet-4-20250514": 300000, + "anthropic/claude-opus-4-8": 500000, + "anthropic/claude-sonnet-4-6": 300000, "anthropic/claude-haiku-4-5-20251001": 100000 }, "toolBaseCosts": { @@ -858,7 +858,7 @@ Aggressive cost savings. Low thresholds, model downgrade with token limits, expe "maxTokensWhenLow": 512, "expensiveToolThreshold": 200000, "modelFallbacks": { - "anthropic/claude-opus-4-20250514": "anthropic/claude-haiku-4-5-20251001", + "anthropic/claude-opus-4-8": "anthropic/claude-haiku-4-5-20251001", "openai/gpt-4o": "openai/gpt-4o-mini" } } @@ -878,8 +878,8 @@ v0.5.0 introduces the **reserve-then-commit** pattern for models: the plugin res { "config": { "modelBaseCosts": { - "anthropic/claude-sonnet-4-20250514": 300000, - "anthropic/claude-opus-4-20250514": 1500000 + "anthropic/claude-sonnet-4-6": 300000, + "anthropic/claude-opus-4-8": 500000 } } } @@ -966,7 +966,7 @@ The plugin retries Cycles server requests on transient HTTP errors (429, 503, 50 } ``` -With default settings, a 429 response triggers up to 2 retries with 500ms and 1000ms delays. Each retry generates a fresh idempotency key, creating a separate reservation attempt at the Cycles server. +With default settings, a 429 response triggers up to 2 retries with 500ms and 1000ms delays. The plugin builds one request body and idempotency key for the logical reservation, then reuses both across those transport retries. A successful retry therefore resolves to the same idempotent reservation operation rather than creating a second hold. ### Heartbeat for long-running tools @@ -1015,7 +1015,7 @@ When estimated time-to-exhaustion drops below 120 seconds (based on current burn ## Session event log (v0.6.0) -Enable a full audit trail of every budget decision: +Enable a session log of the plugin's budget decisions: ```json { diff --git a/how-to/integrating-cycles-with-pydantic-ai.md b/how-to/integrating-cycles-with-pydantic-ai.md index 573c6882..468399a3 100644 --- a/how-to/integrating-cycles-with-pydantic-ai.md +++ b/how-to/integrating-cycles-with-pydantic-ai.md @@ -5,7 +5,7 @@ description: "Guard Pydantic AI agent runs with Cycles budget reservations for c # Integrating Cycles with Pydantic AI -This guide shows how to guard [Pydantic AI](https://ai.pydantic.dev/) agent runs with Cycles budget reservations so that every agent invocation is cost-controlled and observable. +This guide shows how to guard [Pydantic AI](https://pydantic.dev/docs/ai/overview/) agent runs with Cycles budget reservations so that every agent invocation is cost-controlled and observable. ## Prerequisites diff --git a/how-to/integrating-cycles-with-vercel-ai-sdk.md b/how-to/integrating-cycles-with-vercel-ai-sdk.md index 828c94b2..22fc7790 100644 --- a/how-to/integrating-cycles-with-vercel-ai-sdk.md +++ b/how-to/integrating-cycles-with-vercel-ai-sdk.md @@ -5,7 +5,7 @@ description: "Add budget governance to a Next.js app using the Vercel AI SDK wit # Integrating Cycles with the Vercel AI SDK -This guide shows how to add budget governance to a Next.js application using the [Vercel AI SDK](https://sdk.vercel.ai/) and the `runcycles` TypeScript client. +This guide shows how to add budget governance to a Next.js application using the [Vercel AI SDK](https://ai-sdk.dev/) and the `runcycles` TypeScript client. The Vercel AI SDK uses streaming by default, so this guide uses the `reserveForStream` pattern — reserving budget before the stream starts, keeping the reservation alive during streaming, and committing actual usage when the stream finishes. diff --git a/how-to/integrations-overview.md b/how-to/integrations-overview.md index ed20db11..9c36b38d 100644 --- a/how-to/integrations-overview.md +++ b/how-to/integrations-overview.md @@ -5,7 +5,7 @@ description: "Overview of all supported Cycles integrations — LLM providers, f # Integrations Overview -Cycles integrates with LLM providers, agent frameworks, and web servers. Each integration wraps model calls with the reserve → commit → release lifecycle so that every call is budget-checked before execution. +Cycles has integration patterns for LLM providers, agent frameworks, and web servers. The guides show where to place reserve → execute → commit/release around protected paths. Coverage depends on the hooks and calls each integration actually instruments; uninstrumented traffic is unaffected. ## Supported integrations @@ -135,7 +135,7 @@ See [Webhook Integrations](/how-to/webhook-integrations) for full examples with For the layer-by-layer view of where these integrations sit relative to other agent control approaches — wrappers, provider-client patches, framework hooks, LLM gateways, observability — and why runtime authority complements them rather than replaces them: - [Python AI Agent Control: Cost, Risk, and Audit by Layer](/blog/python-ai-agent-control-cost-risk-audit-layers) — six layers walked through, what each covers across cost / risk / audit, and where each stops short. -- [Beyond Budget: How Cycles Controls Agent Actions, Not Just Spend](/blog/beyond-budget-how-cycles-controls-agent-actions) — how applications combine tool authorization with caller-assigned `RISK_POINTS` at instrumented boundaries. +- [How Cycles Meters Caller-Assigned Action Exposure](/blog/beyond-budget-how-cycles-controls-agent-actions) — how applications combine tool authorization with caller-assigned `RISK_POINTS` at instrumented boundaries. - [Why Local-First Agent Runtimes Need Runtime Authority](/blog/every-local-first-agent-runtime-needs-budget-authority) — local-first / BYOK category context for OpenClaw, Cline, Aider, Continue, and similar runtimes. - [Agents Are Cross-Cutting. Your Controls Aren't.](/blog/agents-are-cross-cutting-your-controls-arent) — the structural argument for why agent governance has to span every integration the agent uses. diff --git a/how-to/managing-webhooks.md b/how-to/managing-webhooks.md index d2e9ad1e..3ba2acf5 100644 --- a/how-to/managing-webhooks.md +++ b/how-to/managing-webhooks.md @@ -273,7 +273,7 @@ curl -X PATCH http://localhost:7979/v1/admin/webhooks/whsub_abc123 \ curl -X PATCH http://localhost:7979/v1/admin/webhooks/whsub_abc123 \ -H "X-Admin-API-Key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ - -d '{"event_types": ["budget.exhausted", "budget.threshold_crossed", "reservation.denied"]}' + -d '{"event_types": ["budget.exhausted", "reservation.commit_overage", "reservation.denied"]}' # Switch to a category-only subscription: clear event_types, keep categories. # Valid on update (unlike create); the server rejects only the empty-both state. @@ -423,29 +423,34 @@ curl "http://localhost:7979/v1/events?event_type=budget.exhausted" \ ## Webhook URL Security -By default, webhook URLs that resolve to private IP ranges are blocked (SSRF protection). To manage: +The events service always applies a delivery-time SSRF baseline unless its development-only escape hatch is enabled. It rejects `0.0.0.0/8`, `10.0.0.0/8`, `100.64.0.0/10`, `127.0.0.0/8`, `169.254.0.0/16`, `172.16.0.0/12`, `192.168.0.0/16`, `::1/128`, `fe80::/10`, `fc00::/7`, and any-local or unspecified addresses. Admin-configured CIDR blocks are additive; `allowed_url_patterns` only narrows accepted targets and cannot bypass the baseline. + +To view and narrow the admin-side policy: ```bash # View current security config curl http://localhost:7979/v1/admin/config/webhook-security \ -H "X-Admin-API-Key: $ADMIN_KEY" -# Allow internal endpoints (production) +# Restrict production delivery to an approved public endpoint curl -X PUT http://localhost:7979/v1/admin/config/webhook-security \ -H "X-Admin-API-Key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ - "allowed_url_patterns": ["https://*.internal.example.com/*"], + "allowed_url_patterns": ["https://hooks.example.com/cycles/*"], "blocked_cidr_ranges": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] }' -# Enable HTTP for development/testing +# Enable HTTP at the admin boundary for development/testing curl -X PUT http://localhost:7979/v1/admin/config/webhook-security \ -H "X-Admin-API-Key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{"allow_http": true, "blocked_cidr_ranges": []}' ``` +Local or private-network delivery also requires +`WEBHOOK_URL_GUARD_ALLOW_PRIVATE_NETWORKS=true` on the events service and an events-service restart. Both that environment variable and `allow_http: true` are required for a private HTTP target. Never enable the private-network escape hatch in production. + ## Next Steps - [Webhook Integrations](/how-to/webhook-integrations) — PagerDuty, Slack, ServiceNow examples with signature verification diff --git a/how-to/migrating-from-custom-rate-limiter-to-cycles.md b/how-to/migrating-from-custom-rate-limiter-to-cycles.md index 3fa04ffc..39e48275 100644 --- a/how-to/migrating-from-custom-rate-limiter-to-cycles.md +++ b/how-to/migrating-from-custom-rate-limiter-to-cycles.md @@ -306,7 +306,7 @@ The migration is safe because: | Action-level risk control | No | RISK_POINTS budgets | | Webhook alerts | Custom implementation | Built-in (51 registered event types across 7 categories, PagerDuty/Slack) | | Multi-tenant isolation | Manual Redis key prefixing | Built-in scope hierarchy | -| Delegation attenuation | No | Sub-budget carving for sub-agents | +| Delegation attenuation | No | Explicit child ledgers plus orchestrator-owned tool/depth restrictions | | Shadow mode validation | No | `decide()` endpoint for shadow evaluation | | Graceful degradation | No | ALLOW_WITH_CAPS with tool denylists | diff --git a/how-to/multi-agent-shared-workspace-budget-patterns.md b/how-to/multi-agent-shared-workspace-budget-patterns.md index f2326fb6..7bd9623f 100644 --- a/how-to/multi-agent-shared-workspace-budget-patterns.md +++ b/how-to/multi-agent-shared-workspace-budget-patterns.md @@ -1,19 +1,23 @@ --- title: "Multi-Agent Shared Workspace Budget Patterns" -description: "Recommended patterns for multiple agents sharing a workspace budget, including hierarchical scopes, per-agent sub-budgets, and concurrency-safe design." +description: "Recommended patterns for multiple agents sharing a workspace budget, including overlapping scope ledgers, per-agent ceilings, and atomic reservations." --- # Multi-Agent Shared Workspace Budget Patterns -When multiple agents operate within the same workspace — a team of planners, executors, and reviewers working on a shared task — they need to share a finite budget without overspending. This guide covers recommended patterns for structuring budgets in multi-agent systems. +When multiple agents operate within the same workspace — a team of planners, executors, and reviewers working on a shared task — they need to share finite reservation capacity without racing each other. This guide covers recommended patterns for structuring budgets in multi-agent systems. ::: warning Concurrency is the core challenge Multiple agents checking and spending against a shared budget creates race conditions. Always use Cycles reservations (not balance reads) for spending decisions. See [Concurrent Agent Overspend](/incidents/concurrent-agent-overspend) for a detailed explanation of the failure mode. ::: +::: info What “cap” means here +Atomic reservations cap concurrent submitted estimates on mandatory instrumented paths. Actual external cost is known only after execution and settlement follows the configured commit overage policy. Use conservative estimates and treat the application/provider record as the economic outcome. +::: + ## Pattern 1: Shared workspace budget with no per-agent limits -The simplest pattern. All agents draw from a single workspace-level budget. The server's atomic reservation prevents overspend. +The simplest pattern. Every instrumented agent submits the same workspace scope. Atomic reservations prevent their submitted estimates from oversubscribing that explicitly provisioned ledger. **Scope:** `tenant:acme-corp/workspace:project-alpha` @@ -42,18 +46,18 @@ def planner_call(prompt: str) -> str: return call_llm(prompt) ``` -**When to use:** Small teams of cooperating agents where individual fairness doesn't matter — you just want a hard cap on total spend. +**When to use:** Small teams of cooperating agents where individual fairness does not matter and one shared estimate-admission ceiling is sufficient. **Trade-off:** A single expensive agent can exhaust the budget for all others. -## Pattern 2: Per-agent sub-budgets under a shared workspace cap +## Pattern 2: Per-agent ledgers under a shared workspace cap -Give each agent its own budget, with a workspace-level parent that acts as a hard cap. Even if per-agent budgets sum to more than the workspace budget, the workspace scope prevents collective overspend. +Give each agent its own explicitly provisioned ledger, with a workspace ledger acting as a shared cap. These balances are not transferred from the workspace ledger. Each protected call submits both scopes, and the server checks matching ledgers atomically. **Scope hierarchy:** ``` -tenant:acme-corp/workspace:project-alpha → $50 (hard cap) +tenant:acme-corp/workspace:project-alpha → $50 (shared reservation ceiling) tenant:acme-corp/workspace:project-alpha/agent:planner → $20 tenant:acme-corp/workspace:project-alpha/agent:executor → $30 tenant:acme-corp/workspace:project-alpha/agent:reviewer → $10 @@ -89,7 +93,7 @@ done **Why per-agent budgets can exceed the workspace budget:** The workspace scope is checked at reservation time alongside the agent scope. If the workspace is exhausted, the reservation is denied — regardless of the agent's remaining budget. Over-allocating at the agent level provides flexibility: if the planner finishes under budget, the executor can use more of the shared pool. -**When to use:** Multi-agent workflows where you want both individual fairness and a collective hard cap. +**When to use:** Multi-agent workflows where you want both individual estimate ceilings and a collective reservation ceiling. ## Pattern 3: Workflow-scoped budgets for task isolation @@ -186,7 +190,7 @@ Set up alerts when any scope drops below 10% remaining with active reservations. ## Key principles 1. **Reserve, don't read.** Balance queries are informational. Reservations are authoritative. Never use a balance read to decide whether to proceed. -2. **Use parent scopes as hard caps.** Per-agent budgets provide fairness; workspace/tenant scopes provide safety. +2. **Use broader scopes as shared admission ceilings.** Per-agent ledgers provide individual limits; workspace/tenant ledgers constrain combined reservation estimates. 3. **Design for denial.** Any agent can be denied at any time. Graceful degradation is not optional. 4. **Set the `agent` field.** Always identify which agent is spending. This enables per-agent monitoring, debugging, and budget allocation. diff --git a/how-to/observability-setup.md b/how-to/observability-setup.md index 924b8e4d..44b3ff1d 100644 --- a/how-to/observability-setup.md +++ b/how-to/observability-setup.md @@ -141,18 +141,27 @@ The runtime server (`cycles-server` ≥ `0.1.25.10`) emits custom Micrometer cou | `cycles_reservations_release_total` | `tenant`, `actor_type`, `decision`, `reason` | Every successful release. `actor_type` distinguishes tenant-driven from admin-on-behalf-of releases. | | `cycles_reservations_extend_total` | `tenant`, `decision`, `reason` | Every extend attempt. | | `cycles_reservations_expired_total` | `tenant` | Per reservation actually marked EXPIRED by the sweep (not per candidate). | +| `cycles_reservations_quarantined_total` | `tenant`, `reason` | Malformed reservation records quarantined by maintenance. | +| `cycles_reservations_created_at_index_reads_total` | `outcome` | Created-at index reads and completeness-gated fallback outcomes. | +| `cycles_maintenance_runs_total` | `job`, `outcome` | Scheduled maintenance run outcomes. | +| `cycles_maintenance_duration_seconds` | `job`, `outcome` | Scheduled maintenance duration timer. | | `cycles_events_total` | `tenant`, `decision`, `reason`, `overage_policy` | Outcome of every `POST /v1/events` one-shot debit. | | `cycles_overdraft_incurred_total` | `tenant` | Count of commits/events that actually accrued non-zero debt (unit-free — amount is in the balance store, not leaked to metrics). | | `cycles_evidence_emit_failed_total` | `artifact_type` | Evidence-source enqueue failures (fail-open) — the rare loss window where a lifecycle op committed but its evidence record could not be queued. | -The admin server additionally exposes (`cycles_admin_events_emitted_total` and `cycles_admin_webhook_dispatched_total` since `0.1.25.9`; `cycles_admin_events_payload_invalid_total` since `0.1.25.12`; `cycles_admin_audit_writes_total` since `0.1.25.20`): +The admin server currently exposes seven custom counters: | Metric | Description | |---|---| -| `cycles_admin_webhook_dispatched_total` | Webhook-delivery enqueue attempts (`result=queued`/`failure`). | +| `cycles_admin_webhook_dispatched_total` | Webhook-delivery enqueue outcomes (`result=queued`/`failure`/`boundary_skipped`). | | `cycles_admin_events_emitted_total` | Events produced by admin controllers (budget/tenant/policy/api_key/system). | | `cycles_admin_events_payload_invalid_total` | Payload contract violations caught at emit time. | | `cycles_admin_audit_writes_total` | Audit-write attempts by `path_class` and `outcome` (`written`/`error`/`sampled-out`). Alert on any `outcome="error"`. | +| `cycles_admin_tenant_close_outbox_dead_letter_total` | Tenant-close outbox dead letters by `resource_type`. | +| `cycles_admin_tenant_close_reconcile_incomplete_total` | Reconciliation runs that ended with incomplete tenant-close work. | +| `cycles_admin_tenant_close_reconcile_errors_total` | Tenant-close reconciliation errors. | + +The events service adds 17 delivery, evidence, dispatcher, and security counters plus one delivery-latency timer. Use the [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference) for the complete names, labels, and enum values rather than copying a partial inventory into dashboards. The high-cardinality `tenant` tag is controlled per service by `cycles.metrics.tenant-tag.enabled`: the runtime server defaults it to **`true`**, the events service defaults it to **`false`**, and the admin `cycles_admin_*` counters carry no tenant tag at all. Disable it on the runtime server in deployments with many thousands of tenants. Empty/null tag values are normalised to the sentinel `UNKNOWN` so series names stay stable. diff --git a/how-to/prometheus-metrics-reference.md b/how-to/prometheus-metrics-reference.md index 781e5780..e0f837ec 100644 --- a/how-to/prometheus-metrics-reference.md +++ b/how-to/prometheus-metrics-reference.md @@ -27,7 +27,7 @@ For the operation counters below, null or blank tag values are normalised to the ## Runtime server (`cycles-server`) -Introduced in v0.1.25.10. All counters live under the `cycles.*` namespace. +Introduced in v0.1.25.10 and expanded in later v0.1.25 releases. The current runtime inventory is 11 counters and one timer under the `cycles.*` namespace. | Source | Prometheus | Type | Tags | Description | |---|---|---|---|---| @@ -36,6 +36,10 @@ Introduced in v0.1.25.10. All counters live under the `cycles.*` namespace. | `cycles.reservations.release` | `cycles_reservations_release_total` | Counter | `tenant`, `actor_type`, `decision`, `reason` | Every release outcome (`decision` is `RELEASED` or `DENY`). `actor_type` distinguishes tenant-driven releases from v0.1.25.8 admin-on-behalf-of releases. | | `cycles.reservations.extend` | `cycles_reservations_extend_total` | Counter | `tenant`, `decision`, `reason` | Every `POST /v1/reservations/{id}/extend` outcome. | | `cycles.reservations.expired` | `cycles_reservations_expired_total` | Counter | `tenant` | Each reservation the expiry sweep actually marks EXPIRED. Skipped reservations (still in grace, already finalised) do not increment. | +| `cycles.reservations.quarantined` | `cycles_reservations_quarantined_total` | Counter | `tenant`, `reason` | Malformed reservation records quarantined by maintenance. `reason`: `INVALID_EXPIRY`, `INVALID_ESTIMATE`, `MISSING_SCOPES`, or `MALFORMED_SCOPES`. | +| `cycles.reservations.created_at_index.reads` | `cycles_reservations_created_at_index_reads_total` | Counter | `outcome` | Read-path result for the optional created-at sorted index. `outcome`: `INDEX`, `SCAN_DISABLED`, `SCAN_NOT_READY`, `SCAN_DRIFT`, or `SCAN_ERROR`. | +| `cycles.maintenance.runs` | `cycles_maintenance_runs_total` | Counter | `job`, `outcome` | Scheduled maintenance run outcomes. | +| `cycles.maintenance.duration` | `cycles_maintenance_duration_seconds` | Timer | `job`, `outcome` | Duration of scheduled maintenance runs. | | `cycles.events` | `cycles_events_total` | Counter | `tenant`, `decision`, `reason`, `overage_policy` | Every `POST /v1/events` outcome. | | `cycles.overdraft.incurred` | `cycles_overdraft_incurred_total` | Counter | `tenant` | Any commit or event that actually accrued non-zero debt. Unit-free signal — debt amount is tracked by the balance store, not here, to avoid leaking user-value distributions into metrics. | | `cycles.evidence.emit_failed` | `cycles_evidence_emit_failed_total` | Counter | `artifact_type` | Evidence-source enqueue failures (fail-open path) — a lifecycle op committed but its evidence record could not be queued (e.g. Redis died just after the ledger write). No `tenant` tag. | @@ -48,6 +52,8 @@ Introduced in v0.1.25.10. All counters live under the `cycles.*` namespace. | `reason` | `OK` on success, `IDEMPOTENT_REPLAY` on idempotent replays, an `ErrorCode` name on denials (`BUDGET_EXCEEDED`, `BUDGET_FROZEN`, `BUDGET_CLOSED`, `RESERVATION_EXPIRED`, `RESERVATION_FINALIZED`, `IDEMPOTENCY_MISMATCH`, `UNIT_MISMATCH`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, `MAX_EXTENSIONS_EXCEEDED`, `NOT_FOUND`, …), or `INTERNAL_ERROR` on unexpected failures. `UNKNOWN` when the code path doesn't produce one. | | `overage_policy` | `REJECT`, `ALLOW_IF_AVAILABLE`, `ALLOW_WITH_OVERDRAFT` — which commit overage policy was in effect for the scope. `UNKNOWN` when not resolved. | | `actor_type` | `tenant` (tenant-driven) or `admin_on_behalf_of` (admin-driven, v0.1.25.8+). | +| `job` | `reservation_expiry`, `audit_retention`, `event_retention`, `created_at_repair`, or `created_at_sweep`. | +| `outcome` (maintenance) | `success`, `failed`, `skipped_locked`, `skipped_disabled`, `lease_error`, or `lease_lost`. | ### Not instrumented (by design) @@ -56,7 +62,7 @@ Introduced in v0.1.25.10. All counters live under the `cycles.*` namespace. ## Events service (`cycles-server-events`) -Introduced in v0.1.25.6. Mirrors the runtime's conventions: `cycles.webhook.*` rewrites to `cycles_webhook_*_total` (counters) or `cycles_webhook_*_seconds` (timer). Unlike the runtime, this service emits an explicit latency timer because it's the HTTP *client* — Spring's auto `http.server.requests` doesn't cover its primary I/O surface. +Introduced in v0.1.25.6 and expanded with evidence, boundary, dispatcher, and security instrumentation. The current inventory is 17 counters and one timer. `cycles.webhook.*` rewrites to `cycles_webhook_*_total` (counters) or `cycles_webhook_*_seconds` (timer). This service emits an explicit latency timer because it is the HTTP *client*—Spring's auto `http.server.requests` does not cover its primary I/O surface. | Source | Prometheus | Type | Tags | Description | |---|---|---|---|---| @@ -65,9 +71,19 @@ Introduced in v0.1.25.6. Mirrors the runtime's conventions: `cycles.webhook.*` r | `cycles.webhook.delivery.failed` | `cycles_webhook_delivery_failed_total` | Counter | `tenant`, `event_type`, `reason` | Failed deliveries. `reason` is one of `event_not_found`, `subscription_not_found`, `subscription_inactive`, `http_4xx`, `http_5xx`, `transport_error`, `ssrf_blocked`. | | `cycles.webhook.delivery.retried` | `cycles_webhook_delivery_retried_total` | Counter | `tenant`, `event_type` | Deliveries that re-entered the retry queue. | | `cycles.webhook.delivery.stale` | `cycles_webhook_delivery_stale_total` | Counter | `tenant` | Deliveries auto-failed on pickup for exceeding `dispatch.max-delivery-age-ms` (default 24h). | +| `cycles.webhook.delivery.dead_lettered` | `cycles_webhook_delivery_dead_lettered_total` | Counter | `reason` | Delivery jobs moved to dead letter. | +| `cycles.webhook.delivery.boundary_skipped` | `cycles_webhook_delivery_boundary_skipped_total` | Counter | `tenant`, `event_type`, `category` | Deliveries intentionally skipped at a configured dispatch boundary. | | `cycles.webhook.subscription.auto_disabled` | `cycles_webhook_subscription_auto_disabled_total` | Counter | `tenant`, `reason` | Subscriptions auto-disabled after consecutive failures crossed the threshold. `reason` is `consecutive_failures` (the accompanying `webhook.disabled` Event's payload uses the longer `disable_reason=consecutive_failures_exceeded_threshold`). Always emitted together with a `webhook.disabled` Event (v0.1.25.11). | | `cycles.webhook.delivery.latency` | `cycles_webhook_delivery_latency_seconds` | Timer | `tenant`, `event_type`, `outcome` | Round-trip time on deliveries that actually produced a transport response. `outcome`: `success` or `failure`. Upstream failures (event_not_found, etc.) have no meaningful latency and do not record to this timer. | | `cycles.webhook.events.payload.invalid` | `cycles_webhook_events_payload_invalid_total` | Counter | `type`, `rule` | Non-fatal shape discrepancy found by `EventPayloadValidator` on an ingested event. No tenant dimension — the discrepancy is about payload shape, not tenant traffic. `rule` values: `missing_required`, `unknown_event_type`, `unknown_category`, `category_mismatch`, `budget_data_shape`, `reset_spent_shape`, `trace_id_shape`. | +| `cycles.evidence.claimed` | `cycles_evidence_claimed_total` | Counter | `artifact_type` | Evidence-source records claimed for processing. | +| `cycles.evidence.stored` | `cycles_evidence_stored_total` | Counter | `artifact_type` | Signed evidence envelopes stored successfully. | +| `cycles.evidence.dead_lettered` | `cycles_evidence_dead_lettered_total` | Counter | `artifact_type`, `reason` | Evidence-source records moved to dead letter. | +| `cycles.evidence.retry_deferred` | `cycles_evidence_retry_deferred_total` | Counter | `artifact_type`, `reason` | Evidence work deferred for a later retry. | +| `cycles.webhook.dispatcher.event.published` | `cycles_webhook_dispatcher_event_published_total` | Counter | `event_type` | Dispatcher lifecycle events published successfully. | +| `cycles.webhook.dispatcher.event.deferred` | `cycles_webhook_dispatcher_event_deferred_total` | Counter | `event_type`, `reason` | Dispatcher lifecycle events deferred. | +| `cycles.webhook.dispatcher.event.dead_lettered` | `cycles_webhook_dispatcher_event_dead_lettered_total` | Counter | `event_type`, `reason` | Dispatcher lifecycle events moved to dead letter. | +| `cycles.webhook.security.config.indeterminate` | `cycles_webhook_security_config_indeterminate_total` | Counter | — | Delivery attempts blocked because the webhook-security configuration could not be determined safely. | ### Tag value reference (events) @@ -76,6 +92,8 @@ Introduced in v0.1.25.6. Mirrors the runtime's conventions: `cycles.webhook.*` r | `event_type` | Event kind from the [Event Payloads Reference](/protocol/event-payloads-reference) (e.g. `reservation.denied`, `budget.exhausted`, `webhook.disabled`). Up to 51 registered values, plus additive implementation events over time. | | `status_code_family` | `2xx` (success bucket). Non-2xx responses land on `cycles_webhook_delivery_failed_total` with `reason` instead. | | `reason` (on `_failed_total`) | `event_not_found`, `subscription_not_found`, `subscription_inactive`, `http_4xx`, `http_5xx`, `transport_error` (timeouts, connection resets, DNS/SSL failures — status code 0), `ssrf_blocked`. | +| `artifact_type` | `decide`, `reserve`, `commit`, `release`, `error`, or `UNKNOWN`. | +| `event_type` (dispatcher lifecycle) | `webhook.disabled`, `system.webhook_delivery_failed`, or `UNKNOWN`. | ## Admin server (`cycles-server-admin`) @@ -86,7 +104,10 @@ Exposed since admin observability rollout (v0.1.25.9+). Metric names use the `cy | `cycles_admin_audit_writes_total` | Counter | `path_class`, `outcome` | Audit-trail write accounting. `outcome` values: `written`, `error` (Redis write failed — alert on nonzero), `sampled-out` (pre-auth sampling dropped the entry per `audit.sample.unauthenticated`). `path_class` groups endpoints for coarse-grained triage. Shipped v0.1.25.20 alongside the audit-on-failure coverage. | | `cycles_admin_events_emitted_total` | Counter | `type`, `result` | Admin-emitted Event accounting. `result`: `success` or `failure`. | | `cycles_admin_events_payload_invalid_total` | Counter | `type`, `expected_class` | Jackson round-trip found an Event payload that didn't match its declared schema. Non-fatal — admin continues to accept the event. Shipped v0.1.25.12. | -| `cycles_admin_webhook_dispatched_total` | Counter | `result` | Enqueue-to-dispatcher accounting. The end-to-end delivery metric is `cycles_webhook_delivery_*` on the events service. | +| `cycles_admin_webhook_dispatched_total` | Counter | `result` | Enqueue-to-dispatcher accounting. `result`: `queued`, `failure`, or `boundary_skipped`. The end-to-end delivery metric is `cycles_webhook_delivery_*` on the events service. | +| `cycles_admin_tenant_close_outbox_dead_letter_total` | Counter | `resource_type` | Tenant-close outbox records moved to dead letter. | +| `cycles_admin_tenant_close_reconcile_incomplete_total` | Counter | — | Tenant-close reconciliation runs that ended with incomplete work. | +| `cycles_admin_tenant_close_reconcile_errors_total` | Counter | — | Tenant-close reconciliation errors. | ## Sample scrape config @@ -118,7 +139,7 @@ scrape_configs: ## Cardinality guidance -The `tenant` tag is the dominant cardinality driver. A deployment with 10,000 tenants and all seven runtime counters produces ~70,000 time series just from the tenant dimension. If Prometheus memory / scrape duration becomes a concern: +The `tenant` tag is the dominant cardinality driver. It appears on eight runtime counters and six events-service metrics in the current inventory; each metric can expand further across its bounded decision, reason, event, or outcome labels. If Prometheus memory or scrape duration becomes a concern: 1. **Flip `cycles.metrics.tenant-tag.enabled` to `false`** on the runtime server (the events service already defaults to `false`). Counters drop the `tenant` tag; you lose per-tenant drill-downs but keep decision / reason / outcome signals. 2. **Aggregate at scrape time** with `metric_relabel_configs` to drop the tag selectively on high-cardinality metrics while keeping it on the ones you still want tenant-sliced. diff --git a/how-to/redis-backup-restore-disaster-recovery.md b/how-to/redis-backup-restore-disaster-recovery.md index 84554ddb..be054d80 100644 --- a/how-to/redis-backup-restore-disaster-recovery.md +++ b/how-to/redis-backup-restore-disaster-recovery.md @@ -122,7 +122,7 @@ A successful file copy is not a successful recovery test. 2. Keep the isolated instance unreachable from all Cycles services and clients. 3. Confirm Redis starts without persistence-load errors. 4. Compare `INFO persistence`, `DBSIZE`, and representative key types with the backup manifest. -5. Point isolated runtime and admin instances at a disposable copy of the restored Redis and perform the read-only checks in [Validate Cycles state](#validate-cycles-state). Test the events worker only with outbound network access blocked: it is an active queue consumer, not a read-only service. +5. Point isolated runtime and admin instances at a disposable copy of the restored Redis and perform the read-only checks in [Validate Cycles state](#_4-validate-cycles-state). Test the events worker only with outbound network access blocked: it is an active queue consumer, not a read-only service. 6. Record restore duration, data checks, and any manual steps. The measured duration—not the archive copy time—is your practical RTO. Run this drill on a schedule and after changes to Redis version, persistence mode, encryption keys, or Cycles storage behavior. diff --git a/how-to/rolling-over-billing-periods-with-reset-spent.md b/how-to/rolling-over-billing-periods-with-reset-spent.md index 2a6e5b36..36a43b3c 100644 --- a/how-to/rolling-over-billing-periods-with-reset-spent.md +++ b/how-to/rolling-over-billing-periods-with-reset-spent.md @@ -121,7 +121,7 @@ Budget ledgers carry declarative period metadata — `rollover_policy` (`NONE`, - **External cron.** A scheduled job that reads a list of active tenants from your own tenancy database and calls `RESET_SPENT` for each on the first of the month. - **Stripe webhook-driven.** A handler for Stripe's `invoice.finalized` event that rolls over the corresponding tenant as part of invoice reconciliation. -- **Event-driven.** Subscribe to `budget.threshold_crossed` at 100% utilization and roll over automatically if your plan logic calls for it. +- **Balance-driven.** Poll balances and apply your own plan rules when utilization changes. Do not use exhaustion alone as a billing-period boundary: it can happen before the calendar or invoice period ends, and the current runtime does not emit configurable pre-exhaustion threshold events. In every case, make the idempotency key include the target period, so a retry or duplicate trigger does not double-rollover. diff --git a/how-to/searching-and-sorting-admin-list-endpoints.md b/how-to/searching-and-sorting-admin-list-endpoints.md index 86489619..429796d8 100644 --- a/how-to/searching-and-sorting-admin-list-endpoints.md +++ b/how-to/searching-and-sorting-admin-list-endpoints.md @@ -62,14 +62,14 @@ Current admin-list defaults are endpoint-specific: tenants and API keys use `cre Pagination is cursor-based: -- `limit` — maximum results per page. The default is 50 on every endpoint. Where the spec declares a cap it is 100 (`/v1/admin/tenants`, `/v1/admin/webhooks`, `/v1/admin/events`); `/v1/admin/budgets`, `/v1/admin/api-keys`, and `/v1/admin/audit/logs` declare no maximum. Out-of-range values on capped endpoints return `400 INVALID_REQUEST`. +- `limit` — maximum results per page. The default is 50 on every endpoint. The runtime `/v1/reservations` cap is 200. The capped admin endpoints use 100 (`/v1/admin/tenants`, `/v1/admin/webhooks`, `/v1/admin/events`); `/v1/admin/budgets`, `/v1/admin/api-keys`, and `/v1/admin/audit/logs` declare no maximum. Out-of-range values on capped endpoints return `400 INVALID_REQUEST`. - `cursor` — opaque string from a previous response's `next_cursor`. Do not construct or modify it. - `has_more` — boolean in the response. `true` means there is at least one more page. - `next_cursor` — the value to pass as `cursor` on the next call. Absent when `has_more` is `false`. ### Cursor binding -When `sort_by` is provided, the returned cursor encodes the sort key so "Load more" continues in sort order. The spec does not define a cursor-invalidation error — a cursor reused under a different sort key, direction, or filter set is handled gracefully rather than rejected, but the resulting page order is whatever the cursor encoded, not what your new parameters asked for. +When `sort_by` is provided, the returned cursor encodes the sort key so "Load more" continues in sort order. Current admin servers bind the cursor to the result-set parameters: reusing it after changing the sort key, direction, or filters returns `400 INVALID_REQUEST`. The response uses the generic error code rather than a cursor-specific code. **Reset the cursor whenever you change the sort key, sort direction, or any filter.** The client's job is to either preserve those parameters across all pages of a traversal or start over from page one. @@ -200,7 +200,9 @@ v0.1.25.28 renamed the previous single `` sentinel. Historical ## Hydration warning on sorted reservation listings -On `/v1/reservations`, current runtime servers hydrate all matches for sorted queries, then sort and slice. If a sorted query hydrates 2,000 or more rows, the server logs a WARN so operators can narrow filters or plan sorted indices. Rows beyond 2,000 are not truncated in v0.1.25.39+. +On `/v1/reservations`, `sort_by=created_at_ms` can use the optional, completeness-gated created-at sorted index in runtime v0.1.25.54 and later. The index is used only when it is enabled and ready; otherwise the server falls back without returning incomplete results. Track `cycles_reservations_created_at_index_reads_total{outcome=...}` to distinguish index reads from disabled, not-ready, drift, and error fallbacks. + +The other six reservation sort keys—and `created_at_ms` when the index cannot be used—hydrate all matches, then sort and slice. If a sorted query hydrates 2,000 or more rows, the server logs a WARN so operators can narrow filters. Rows beyond 2,000 are not truncated in v0.1.25.39+. For faster, more predictable queries, narrow the filter: add `status`, `idempotency_key`, a time window, or a subject field (`workspace`, `app`, `workflow`, `agent`, `toolset`). The admin list endpoints keep their own endpoint-specific sort behavior. @@ -208,11 +210,11 @@ For faster, more predictable queries, narrow the filter: add `status`, `idempote | `error` | Meaning | |---------|---------| -| `INVALID_REQUEST` | Unknown `sort_by`, unknown `sort_dir`, out-of-range `limit`, or `search` over 128 chars | +| `INVALID_REQUEST` | Unknown `sort_by`, unknown `sort_dir`, out-of-range `limit`, `search` over 128 chars, or a cursor reused with different result-set parameters | | `FORBIDDEN` | Tenant-scoped key attempted a cross-tenant listing | | `UNAUTHORIZED` | Invalid API key | -The error code is carried in the `error` field of the standard `ErrorResponse` body. Note there is no cursor-specific error code — see [Cursor binding](#cursor-binding) for how stale cursors behave. +The error code is carried in the `error` field of the standard `ErrorResponse` body. There is no cursor-specific error code; reset pagination after any result-set parameter changes. ## Next steps diff --git a/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production.md b/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production.md index e101f9a5..fc2ab399 100644 --- a/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production.md +++ b/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production.md @@ -40,19 +40,23 @@ Shadow mode is the bridge between those two states. ## What shadow mode is -In the Cycles protocol, shadow mode is enabled by setting `dry_run: true` on a reservation request. The server evaluates the same reservation and budget logic it would use in enforcement mode, but instead of blocking execution, it returns what **would have happened** — the decision, caps, and affected scopes — in the response. The server must not persist anything for a dry run: no balances are modified, no reservation is created, and no commit or release is required. Recording those would-be outcomes for analysis is the caller's job. +In the Cycles protocol, shadow mode is enabled by setting `dry_run: true` on a reservation request. The server evaluates the same reservation and budget logic it would use in enforcement mode, but instead of blocking execution, it returns what **would have happened**—the decision, caps, and affected scopes—in the response. No balances are modified, no reservation is created, and no commit or release is required. + +The current reference server emits `reservation.denied` for a denied dry-run or `decide` evaluation, and an enabled evidence pipeline may retain a signed evaluation artifact. It does not emit a corresponding allowed lifecycle event or know the eventual external outcome. Record every response and actual outcome in the application when you need a complete shadow dataset. That means your system can answer questions like: - would this action have been allowed? - which scope would have denied it? -- how often would this workflow exceed its run budget? +- how often would this execution exceed a workflow ledger mapped to its run ID? - which tenants are consistently near their limits? -- how accurate are our estimates versus actual usage? +- how accurate are our estimates versus actual usage recorded by the application? In other words, shadow mode gives you production-grade policy feedback without introducing production-grade disruption. -As a fleet-wide complement to per-request `dry_run`, servers implementing the v0.1.26 admin extension also preview a tenant-level `observe_mode` setting, which lets operators put an entire tenant into shadow evaluation without changing any call sites. +The protocol's v0.1.26 admin extension previews tenant-level `observe_mode` fields. The current v0.1.25.x server accepts those fields for forward compatibility but does not apply fleet-wide shadow semantics or emit observed decisions from them. Use per-request `dry_run: true` and application-side logging today. + +Cycles' standard budget hierarchy is `tenant → workspace → app → workflow → agent → toolset`. If you want a separate ledger for each run, map the run ID to `subjects.workflow`; putting a run ID only in `dimensions` adds attribution but does not create a budget scope. ## What shadow mode is not @@ -134,7 +138,7 @@ For example: - tenant budget - workflow budget -- run budget +- workflow budget keyed by run ID This shows whether the problem is broad account-level consumption or a local execution issue. @@ -145,6 +149,8 @@ How often are your reservations too high or too low? If estimates are consistently inflated, you may create unnecessary policy pressure. If estimates are consistently too low, your controls may be less protective than expected. +Because a dry run creates no reservation to commit, record actual usage in application telemetry and join it to the logged dry-run response. + ### 4. Workflow distribution Which workflows consume the most exposure? @@ -187,7 +193,7 @@ Do not try to model every possible action on day one. Start with a small set of budget scopes, usually: - tenant -- run +- workflow, optionally keyed by run ID Those two often provide the clearest operational signal. @@ -242,7 +248,7 @@ A successful shadow period usually produces a few things. ### Clear budget boundaries -You begin to understand what reasonable tenant, workflow, and run limits look like. +You begin to understand what reasonable tenant and workflow limits look like, including workflows keyed per execution when that mapping fits your application. ### Estimate quality improves @@ -268,7 +274,7 @@ If nobody looks at the would-deny outcomes, shadow mode becomes passive logging. ### Mistake 2: Starting with too many scopes -If you begin with tenant, application, workflow, agent, tool, run, environment, and custom policy layers all at once, it becomes difficult to understand what is actually driving decisions. +If you begin with tenant, workspace, app, workflow, agent, and toolset scopes all at once, it becomes difficult to understand what is actually driving decisions. Start small. @@ -329,10 +335,10 @@ That makes Cycles easier to adopt operationally. For many teams, a strong first rollout looks like this: - evaluate **tenant budgets** in shadow mode -- evaluate **run budgets** in shadow mode +- evaluate **workflow budgets**, optionally keyed by run ID, in shadow mode - instrument model calls and expensive tools -- record would-deny events -- compare estimated vs actual usage +- log would-deny responses in the application +- compare estimates with actual usage from application telemetry - review top offending runs and tenants - add degradation rules before hard enforcement @@ -367,7 +373,7 @@ That is critical because autonomous systems are hard to model perfectly in advan By using shadow mode, teams can: - learn real consumption patterns -- tune tenant, workflow, and run budgets +- tune tenant and workflow budgets, including per-execution workflow mappings - refine estimate quality - identify runaway behavior - design degradation paths diff --git a/how-to/tenant-creation-and-management-in-cycles.md b/how-to/tenant-creation-and-management-in-cycles.md index e9592132..a511fcc0 100644 --- a/how-to/tenant-creation-and-management-in-cycles.md +++ b/how-to/tenant-creation-and-management-in-cycles.md @@ -138,7 +138,7 @@ Response: |---|---| | `status` | Filter by status: `ACTIVE`, `SUSPENDED`, or `CLOSED` | | `parent_tenant_id` | Filter by parent tenant (for hierarchical tenants) | -| `observe_mode` | Filter by observe-mode flag (`true`/`false`) | +| `observe_mode` | Preview filter: `DISABLED`, `OBSERVE`, or `ENFORCE`; accepted but not applied by current v0.1.25.x admin servers | | `search` | Case-insensitive substring match over `tenant_id` and `name` | | `sort_by` / `sort_dir` | Sort key and direction (default: `created_at` descending) | | `cursor` | Pagination cursor from a previous response | diff --git a/how-to/using-bulk-actions-for-tenants-and-webhooks.md b/how-to/using-bulk-actions-for-tenants-and-webhooks.md index 115c878f..8789369f 100644 --- a/how-to/using-bulk-actions-for-tenants-and-webhooks.md +++ b/how-to/using-bulk-actions-for-tenants-and-webhooks.md @@ -94,11 +94,11 @@ Bulk actions cap at **500 matched rows per call**. If your filter resolves to mo "error": "LIMIT_EXCEEDED", "message": "filter matches more than 500 tenants; narrow the filter and retry", "request_id": "req_...", - "details": { "total_matched": 501 } + "details": { "total_matched": 637 } } ``` -In the reference implementation, `total_matched` in the error details acts as a sentinel — the server fetches only up to one row past the cap, so "501" means "over the limit" rather than an exact count. Treat any value above 500 as "too many", not a precise total. No rows are touched. To proceed, narrow the filter (add `status`, `search`, or a scoping field) and run multiple calls with distinct idempotency keys. +`total_matched` is the exact server-counted result size, including when it exceeds the 500-row execution cap. No rows are touched. To proceed, narrow the filter (add `status`, `search`, or a scoping field) and run multiple calls with distinct idempotency keys. ### Count mismatch — `COUNT_MISMATCH` diff --git a/how-to/using-the-cycles-client-programmatically.md b/how-to/using-the-cycles-client-programmatically.md index 5b97fa6a..9393913f 100644 --- a/how-to/using-the-cycles-client-programmatically.md +++ b/how-to/using-the-cycles-client-programmatically.md @@ -7,7 +7,7 @@ description: "Use the Cycles programmatic client in Python, Java, and TypeScript The decorator / annotation handles most use cases automatically. But sometimes you need direct control — building requests manually, managing the lifecycle yourself, or calling endpoints that the decorator does not cover. -The Python `CyclesClient`, the Java `CyclesClient` interface, and the TypeScript `CyclesClient` class all provide programmatic access to every Cycles protocol endpoint. +The Python `CyclesClient`, Java `CyclesClient`, and TypeScript `CyclesClient` provide the core runtime operations: decide; reserve, commit, release, and extend; reservation list/get; balances; and usage events. They do not expose helpers for every public or preview endpoint, such as evidence retrieval and JWKS discovery; use direct HTTP for an endpoint your client's current API does not cover. ## Getting the client diff --git a/how-to/using-the-cycles-dashboard.md b/how-to/using-the-cycles-dashboard.md index 72e75002..fc704905 100644 --- a/how-to/using-the-cycles-dashboard.md +++ b/how-to/using-the-cycles-dashboard.md @@ -104,7 +104,7 @@ There is no pivot menu or deliveries side panel — the chip is a filtered navig ### Terminal-state row toggle (v0.1.25.46) -Default sort on every list view is `created_at desc`, which pins recently-transitioned terminal rows to the top — closed tenants, disabled webhooks, revoked / expired API keys, closed budgets. Before v0.1.25.46 these dominated the first screen and operators had to add an explicit status filter to get them out of the way. +List defaults are endpoint-specific: Tenants and API Keys use `created_at desc`, Budgets use `utilization desc`, and Webhooks inherit the admin default `consecutive_failures desc`. Terminal rows can still crowd operational views or displace the active rows operators need most, so v0.1.25.46 added an explicit visibility toggle. Tenants, Budgets, Webhooks, and API Keys now hide terminal rows by default and surface a "Show closed (N)" / "Show disabled (N)" / "Show revoked (N)" toggle with the hidden count. Flipping the toggle partitions the list so active rows stay on top and terminal rows drop to the bottom — column-sort order is preserved within each group. Matches the GitHub / Linear / Gmail convention for done / archived items. @@ -115,7 +115,7 @@ Tenants, Budgets, Webhooks, and API Keys now hide terminal rows by default and s | Webhooks | `status=DISABLED` | | API Keys | `status IN (REVOKED, EXPIRED)` | -Toggle state mirrors to URL as `?include_terminal=1` on top-level views so deep-links survive across reloads. Picking a terminal status explicitly from the dropdown (e.g. `status=CLOSED`) auto-engages the toggle so the list isn't silently empty. Tenant-detail sub-tabs (Budgets / API Keys / Policies) default off and don't mirror to URL (they share a URL with the parent). +Toggle state mirrors to URL as `?include_terminal=1` on the top-level Tenants, Budgets, and Webhooks views so deep-links survive across reloads. The top-level API Keys view and tenant-detail sub-tabs do not mirror the toggle to the URL. Picking a terminal status explicitly from the dropdown (for example, `status=CLOSED`) auto-engages the toggle so the list isn't silently empty. ### WebhookDetailView stats row (v0.1.25.51) @@ -183,7 +183,7 @@ Force-release uses dual authentication — the dashboard's nginx routes `/v1/res The Events page is correlation-first, not time-first: -- Event rows carry a `correlation_id` when applicable (event-stream cluster — threshold → trip → reset chains, or one admin operation's fan-out) plus `request_id` (the originating HTTP request). Clicking either filters to the related events; audit rows join via `trace_id`/`request_id` rather than `correlation_id`. +- Event rows carry a `correlation_id` when the emitting service populates one, plus `request_id` for the originating HTTP request. The current reference runtime leaves `correlation_id` absent on its implemented event paths; selected admin lifecycle, bulk, and cascade operations populate server-composed values. Clicking a present identifier filters to related events; audit rows join via `trace_id`/`request_id` rather than `correlation_id`. - Expandable detail rows show the full event payload — including `data`, `actor`, `metadata`, and delivery outcome if the event went out over a webhook. - Filters: event type, category, tenant, scope, time range, correlation ID. diff --git a/how-to/webhook-integrations.md b/how-to/webhook-integrations.md index 463230e3..77577d9f 100644 --- a/how-to/webhook-integrations.md +++ b/how-to/webhook-integrations.md @@ -5,7 +5,7 @@ description: Connect Cycles webhook events to PagerDuty, Slack, ServiceNow, and # Webhook Integrations -Cycles emits webhook events for every state change — budget exhaustion, reservation denials, API key revocations, tenant lifecycle changes, and more. This guide shows concrete examples of webhook payloads and how to integrate with common services. +Cycles emits webhook events from implemented budget, reservation, API-key, tenant, and other lifecycle hooks. The schema also registers planned event types that are not emitted yet; check the [Event Payloads Reference](/protocol/event-payloads-reference) for current status. This guide shows concrete payload examples and integrations with common services. ::: info How webhooks are delivered Webhook subscriptions are **configured** via the Admin Server (port 7979). Events are **delivered** by the [Cycles Events Service](/quickstart/deploying-the-events-service) — a separate, optional outbound worker that consumes from Redis and posts to your endpoints with HMAC-SHA256 signatures. The Events Service must be deployed for webhook delivery to work. @@ -15,7 +15,7 @@ Webhook subscriptions are **configured** via the Admin Server (port 7979). Event ### reservation.denied -Emitted when a reservation or decide request is denied (budget exceeded, overdraft limit, etc.). +Emitted when a dry-run reservation or `/v1/decide` evaluation returns `DENY` (budget exceeded, overdraft limit, etc.). The current live reservation error path returns a 4xx response and does not emit this event; monitor the runtime denial counter or application errors for live failures. ```json { @@ -27,16 +27,13 @@ Emitted when a reservation or decide request is denied (budget exceeded, overdra "scope": "tenant:acme-corp/workspace:prod/agent:support-bot", "actor": { "type": "api_key", - "key_id": "key_9f8e7d6c-5b4a-3210", - "source_ip": "10.0.1.42" + "key_id": "key_9f8e7d6c-5b4a-3210" }, "source": "cycles-server", "data": { "scope": "tenant:acme-corp/workspace:prod/agent:support-bot", - "unit": "USD_MICROCENTS", "reason_code": "BUDGET_EXCEEDED", - "requested_amount": 5000000, - "remaining": 0 + "requested_amount": 5000000 }, "request_id": "req_abc123" } @@ -44,7 +41,7 @@ Emitted when a reservation or decide request is denied (budget exceeded, overdra ### budget.exhausted -Emitted when remaining budget hits zero. +Emitted once when remaining budget transitions from above zero to zero. ```json { @@ -64,42 +61,45 @@ Emitted when remaining budget hits zero. "unit": "USD_MICROCENTS", "threshold": 1.0, "utilization": 1.0, - "allocated": 100000000, + "allocated": 10000000, "remaining": 0, - "spent": 85000000, - "reserved": 15000000, + "spent": 9000000, + "reserved": 1000000, "direction": "rising" } } ``` -### budget.threshold_crossed +The payload is the balance snapshot that caused the transition; the envelope identifies the tenant, scope, and actor. Query the balance API before remediation because the ledger may have changed since emission. -Emitted when utilization crosses a configured threshold. Default thresholds if not specified on the subscription: **80%, 95%, and 100%** (via `WebhookThresholdConfig.budget_utilization`). The `direction` field is `"rising"` when utilization increases past the threshold and `"falling"` when it drops back below, preventing duplicate alerts. +### reservation.commit_overage + +Emitted when a commit's actual amount exceeds its reservation estimate. The current v0.1.25.46+ runtime populates all eight data fields: ```json { "event_id": "evt_1122334455667788", - "event_type": "budget.threshold_crossed", - "category": "budget", + "event_type": "reservation.commit_overage", + "category": "reservation", "timestamp": "2026-04-01T13:15:00.789Z", "tenant_id": "acme-corp", - "scope": "tenant:acme-corp/workspace:prod", + "scope": "tenant:acme-corp/workflow:support", "source": "cycles-server", "data": { - "scope": "tenant:acme-corp/workspace:prod", + "reservation_id": "res_a1b2c3d4", + "scope": "tenant:acme-corp/workflow:support", "unit": "USD_MICROCENTS", - "threshold": 0.80, - "utilization": 0.82, - "allocated": 100000000, - "remaining": 18000000, - "spent": 67000000, - "reserved": 15000000, - "direction": "rising" + "estimated_amount": 40000000, + "actual_amount": 48000000, + "overage": 8000000, + "overage_policy": "ALLOW_IF_AVAILABLE", + "debt_incurred": 0 } } ``` +`budget.threshold_crossed` is registered in the governance schema but is not emitted by the current reference runtime. Build pre-exhaustion alerts from balance polling or application metrics. + ### budget.over_limit_entered Emitted when debt exceeds overdraft_limit. @@ -326,7 +326,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ "event_types": [ "budget.exhausted", "budget.over_limit_entered", - "budget.threshold_crossed", + "reservation.commit_overage", "reservation.denied", "api_key.auth_failed" ], @@ -348,7 +348,7 @@ PAGERDUTY_ROUTING_KEY = "your-pagerduty-integration-key" SEVERITY_MAP = { "budget.exhausted": "critical", "budget.over_limit_entered": "critical", - "budget.threshold_crossed": "warning", + "reservation.commit_overage": "warning", "reservation.denied": "warning", "api_key.auth_failed": "info", } @@ -386,10 +386,10 @@ async def forward_to_pagerduty(request: Request): | Cycles Event | PagerDuty Severity | When | |---|---|---| -| `budget.exhausted` | Critical | Budget remaining = 0, all reservations denied | +| `budget.exhausted` | Critical | Remaining reached zero; new positive reservations deriving this scope and unit may be denied | | `budget.over_limit_entered` | Critical | Debt exceeded overdraft limit; new reservations blocked until debt repaid | -| `budget.threshold_crossed` (95%) | Warning | Budget nearly depleted | -| `reservation.denied` | Warning | Agent couldn't reserve budget | +| `reservation.commit_overage` | Warning | Actual usage exceeded the reservation estimate | +| `reservation.denied` | Warning | A dry-run or decide evaluation would deny | ## Integration: Slack @@ -405,7 +405,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ -d '{ "url": "https://your-middleware.example.com/cycles-to-slack", "event_types": [ - "budget.threshold_crossed", + "reservation.commit_overage", "budget.exhausted", "budget.over_limit_entered", "budget.funded", @@ -427,7 +427,7 @@ const SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/T.../B.../xxx'; const EMOJI = { 'budget.exhausted': ':rotating_light:', 'budget.over_limit_entered': ':no_entry:', - 'budget.threshold_crossed': ':warning:', + 'reservation.commit_overage': ':warning:', 'budget.funded': ':money_with_wings:', 'reservation.denied': ':no_entry:', 'tenant.suspended': ':pause_button:', @@ -465,6 +465,15 @@ app.post('/cycles-to-slack', express.raw({ type: 'application/json' }), async (r if (data.reason_code) { text += `Reason: ${data.reason_code}\n`; } + if (data.estimated_amount !== undefined) { + text += `Estimated: ${formatAmount(data.estimated_amount, data.unit)}\n`; + } + if (data.actual_amount !== undefined) { + text += `Actual: ${formatAmount(data.actual_amount, data.unit)}\n`; + } + if (data.overage !== undefined) { + text += `Overage: ${formatAmount(data.overage, data.unit)}\n`; + } await fetch(SLACK_WEBHOOK_URL, { method: 'POST', @@ -482,11 +491,12 @@ app.post('/cycles-to-slack', express.raw({ type: 'application/json' }), async (r ### Example Slack messages ``` -:warning: budget.threshold_crossed +:warning: reservation.commit_overage Tenant: `acme-corp` -Scope: `tenant:acme-corp/workspace:prod` -Utilization: 82.0% -Remaining: $18.00 +Scope: `tenant:acme-corp/workflow:support` +Estimated: $0.40 +Actual: $0.48 +Overage: $0.08 :rotating_light: budget.exhausted Tenant: `acme-corp` @@ -515,7 +525,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ "event_types": [ "budget.over_limit_entered", "budget.exhausted", - "system.store_connection_lost" + "api_key.auth_failed" ], "signing_secret": "snow-secret-123" }' @@ -537,7 +547,7 @@ SIGNING_SECRET = "snow-secret-123" PRIORITY_MAP = { "budget.over_limit_entered": "2", # High "budget.exhausted": "2", # High - "system.store_connection_lost": "1", # Critical + "api_key.auth_failed": "2", # High } @app.post("/cycles-to-servicenow") @@ -592,7 +602,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ "event_types": [ "budget.exhausted", "budget.over_limit_entered", - "budget.threshold_crossed", + "reservation.commit_overage", "reservation.denied" ], "signing_secret": "dd-webhook-secret" @@ -613,7 +623,7 @@ SIGNING_SECRET = "dd-webhook-secret" ALERT_TYPE_MAP = { "budget.exhausted": "error", "budget.over_limit_entered": "error", - "budget.threshold_crossed": "warning", + "reservation.commit_overage": "warning", "reservation.denied": "warning", } @@ -661,7 +671,7 @@ async def forward_to_datadog(request: Request): ### Event overlays in Datadog -Budget events posted via the Events API appear in Datadog's [Events Explorer](https://docs.datadoghq.com/service_management/events/explorer/) and can be overlaid on Datadog dashboards. Use `tags` for filtering — e.g., show only `budget.exhausted` events on your cost dashboard. +Budget events posted via the Events API appear in Datadog's [Events Explorer](https://docs.datadoghq.com/events/explorer/) and can be overlaid on Datadog dashboards. Use `tags` for filtering — e.g., show only `budget.exhausted` events on your cost dashboard. ## Integration: Microsoft Teams @@ -678,7 +688,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ "event_types": [ "budget.exhausted", "budget.over_limit_entered", - "budget.threshold_crossed", + "reservation.commit_overage", "reservation.denied", "tenant.suspended" ], @@ -705,7 +715,7 @@ SIGNING_SECRET = "teams-webhook-secret" CARD_COLOR_MAP = { "budget.exhausted": "attention", # Red "budget.over_limit_entered": "attention", - "budget.threshold_crossed": "warning", # Yellow + "reservation.commit_overage": "warning", # Yellow "reservation.denied": "warning", "tenant.suspended": "accent", # Blue } @@ -742,6 +752,12 @@ async def forward_to_teams(request: Request): facts.append({"title": "Remaining", "value": format_amount(data["remaining"], data.get("unit"))}) if data.get("reason_code"): facts.append({"title": "Reason", "value": data["reason_code"]}) + if data.get("estimated_amount") is not None: + facts.append({"title": "Estimated", "value": format_amount(data["estimated_amount"], data.get("unit"))}) + if data.get("actual_amount") is not None: + facts.append({"title": "Actual", "value": format_amount(data["actual_amount"], data.get("unit"))}) + if data.get("overage") is not None: + facts.append({"title": "Overage", "value": format_amount(data["overage"], data.get("unit"))}) card = { "type": "message", @@ -774,18 +790,19 @@ async def forward_to_teams(request: Request): ### Example Teams card -The card renders as a structured fact table showing: event type (budget.threshold_crossed), tenant (acme-corp), source service, scope path, utilization percentage (82.0%), and remaining budget ($18.00). +The card renders as a structured fact table showing the event type, tenant, source service, scope path, and the event's populated data fields. ``` ┌─────────────────────────────────────┐ -│ ⚠ Cycles: budget.threshold_crossed │ +│ ⚠ Cycles: reservation.commit_overage│ │ │ │ Tenant: acme-corp │ -│ Event: budget.threshold_crossed│ +│ Event: reservation.commit_overage│ │ Source: cycles-server │ │ Scope: tenant:acme-corp/... │ -│ Utilization: 82.0% │ -│ Remaining: $18.00 │ +│ Estimated: $0.40 │ +│ Actual: $0.48 │ +│ Overage: $0.08 │ └─────────────────────────────────────┘ ``` @@ -805,7 +822,7 @@ curl -X POST http://localhost:7979/v1/admin/webhooks \ "budget.exhausted", "budget.over_limit_entered", "reservation.denied", - "system.store_connection_lost" + "api_key.auth_failed" ], "signing_secret": "og-webhook-secret" }' @@ -825,7 +842,7 @@ SIGNING_SECRET = "og-webhook-secret" PRIORITY_MAP = { "budget.exhausted": "P2", "budget.over_limit_entered": "P1", - "system.store_connection_lost": "P1", + "api_key.auth_failed": "P2", "reservation.denied": "P3", } @@ -907,8 +924,8 @@ def handle(): handle_budget_exhausted(event) elif event_type == "reservation.denied": handle_denial(event) - elif event_type == "budget.threshold_crossed": - handle_threshold(event) + elif event_type == "reservation.commit_overage": + handle_commit_overage(event) mark_processed(event_id) return "OK", 200 @@ -933,7 +950,7 @@ curl -X POST http://localhost:7979/v1/webhooks \ -d '{ "url": "https://acme-corp.example.com/budget-alerts", "event_types": [ - "budget.threshold_crossed", + "reservation.commit_overage", "budget.exhausted", "reservation.denied" ] @@ -948,29 +965,33 @@ curl -X POST http://localhost:7979/v1/webhooks \ ## Webhook URL Security -By default, Cycles blocks webhook URLs that resolve to private IP ranges (SSRF protection): +The events service applies a delivery-time SSRF baseline even when the admin-configured CIDR list is empty: -- **Blocked by default:** `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, `::1/128`, `fc00::/7` -- **HTTPS required** in production. HTTP URLs are rejected unless explicitly enabled. +- **Blocked by default:** `0.0.0.0/8`, `10.0.0.0/8`, `100.64.0.0/10`, `127.0.0.0/8`, `169.254.0.0/16`, `172.16.0.0/12`, `192.168.0.0/16`, `::1/128`, `fe80::/10`, and `fc00::/7`. Any-local and unspecified addresses are also rejected. +- **HTTPS required** unless the admin webhook-security configuration explicitly sets `allow_http: true`. +- **Admin CIDR blocks are additive.** Clearing `blocked_cidr_ranges` does not remove the events-service baseline. -To test with local endpoints or internal services: +Local development requires both controls below. Restart the events service after setting its environment variable: ```bash -# Enable HTTP and remove CIDR blocks (development only!) +# Events service only — development/testing escape hatch +export WEBHOOK_URL_GUARD_ALLOW_PRIVATE_NETWORKS=true + +# Admin service — allow an HTTP target curl -X PUT http://localhost:7979/v1/admin/config/webhook-security \ -H "X-Admin-API-Key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{"allow_http": true, "blocked_cidr_ranges": []}' ``` -For production with internal endpoints, use `allowed_url_patterns` to allowlist specific internal domains: +Never set `WEBHOOK_URL_GUARD_ALLOW_PRIVATE_NETWORKS=true` in production. `allowed_url_patterns` can narrow delivery to approved public destinations, but it does not bypass private-address blocking: ```bash curl -X PUT http://localhost:7979/v1/admin/config/webhook-security \ -H "X-Admin-API-Key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ - "allowed_url_patterns": ["https://*.internal.example.com/*"], + "allowed_url_patterns": ["https://hooks.example.com/cycles/*"], "blocked_cidr_ranges": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] }' ``` @@ -979,19 +1000,17 @@ curl -X PUT http://localhost:7979/v1/admin/config/webhook-security \ | Event Type | Produced By | `source` Field | Use Case | |---|---|---|---| -| `budget.threshold_crossed` | Runtime server | `cycles-server` | Warning: budget nearing limit (default thresholds: 80%, 95%, 100%) | -| `budget.exhausted` | Runtime server | `cycles-server` | Critical: remaining = 0, all reservations denied | +| `budget.exhausted` | Runtime server | `cycles-server` | Critical: remaining reached zero on the affected ledger | | `budget.over_limit_entered` | Runtime server | `cycles-server` | Critical: debt exceeded overdraft limit; new reservations blocked | -| `budget.over_limit_exited` | Admin server | `cycles-admin` | Recovery: debt repaid below limit | -| `budget.debt_incurred` | Runtime server | `cycles-server` | Info: commit created debt via ALLOW_WITH_OVERDRAFT | -| `reservation.denied` | Runtime server | `cycles-server` | Warning: agent couldn't reserve budget | +| `budget.debt_incurred` | Runtime server | `cycles-server` | Info: a commit or direct debit created debt via ALLOW_WITH_OVERDRAFT | +| `reservation.denied` | Runtime server | `cycles-server` | Calibration: a dry-run or decide evaluation returned DENY | | `reservation.commit_overage` | Runtime server | `cycles-server` | Info: actual spend exceeded estimated amount | | `reservation.expired` | Runtime server (expiry sweep) | `cycles-server` | Info: reservation TTL expired without commit/release | | `tenant.suspended` | Admin server | `cycles-admin` | Alert: tenant operations paused | | `tenant.closed` | Admin server | `cycles-admin` | Alert: tenant permanently closed | | `api_key.auth_failed` | Admin server | `cycles-admin` | Security: authentication failure | | `api_key.revoked` | Admin server | `cycles-admin` | Security: key access removed | -| `system.store_connection_lost` | Any service | `cycles-server` | Critical: Redis connection failure | +| `webhook.disabled` | Admin/events services | `cycles-admin` or `cycles-events` | Alert: a webhook was disabled manually or after delivery failures | | `system.webhook_delivery_failed` | Events service | `cycles-events` | Meta: webhook delivery permanently failed after all retries | ## Next steps diff --git a/incidents/concurrent-agent-overspend.md b/incidents/concurrent-agent-overspend.md index 999669b4..2bd29dae 100644 --- a/incidents/concurrent-agent-overspend.md +++ b/incidents/concurrent-agent-overspend.md @@ -128,7 +128,7 @@ async function agentTask(agentId: string, task: string): Promise { } } -// Run 5 agents concurrently — Cycles guarantees budget safety +// Run 5 agents concurrently — reservations serialize estimate admission const results = await Promise.all( agents.map((agent) => agentTask(agent.id, agent.task)) ); @@ -349,7 +349,7 @@ For more testing patterns, see [Testing with Cycles](/how-to/testing-with-cycles ## Key points - **Balance reads are informational, not authoritative.** Querying `/v1/balances` tells you the current state, but it does not reserve anything. Two agents can read the same balance and both decide to spend. -- **Reservations are authoritative.** A successful reservation guarantees the budget is locked for that agent. Other agents see the reduced remaining balance. +- **Reservations are authoritative for estimate admission.** A successful reservation holds the submitted estimate. Other agents see the reduced remaining balance; later settlement still follows the commit overage policy. - **The `remaining` field accounts for reservations.** It equals `allocated - spent - reserved - debt`. Active reservations reduce `remaining` even before they commit. ## Real-world scenarios @@ -363,7 +363,7 @@ This pattern appears in: ## Prevention -1. **Always reserve before spending.** Never rely on balance reads for authorization. The `reserve` call is the only concurrency-safe way to claim budget. A successful reservation is a guarantee; a balance read is a suggestion. +1. **Always reserve before protected spending.** Never rely on balance reads for admission. The `reserve` call is the concurrency-safe way to hold the submitted estimate; a balance read is only a snapshot. 2. **Use hierarchical scopes.** Even if agents have individual budgets, a shared parent scope acts as a hard cap. If 5 agents each have a $5 budget but the team scope is $10, the team scope prevents collective overspend: diff --git a/protocol/authentication-tenancy-and-api-keys-in-cycles.md b/protocol/authentication-tenancy-and-api-keys-in-cycles.md index 67d571c9..a1936882 100644 --- a/protocol/authentication-tenancy-and-api-keys-in-cycles.md +++ b/protocol/authentication-tenancy-and-api-keys-in-cycles.md @@ -5,7 +5,7 @@ description: "How Cycles authenticates API requests and scopes all budget operat # Authentication, Tenancy, and API Keys in Cycles -Every request to the Cycles API is authenticated. Every budget operation is tenant-scoped. +Every protected budget operation is authenticated and tenant-scoped. Liveness/readiness and other explicitly public operational or evidence endpoints are exceptions. These two properties — authentication and tenancy — are foundational. They determine who is making the request, which budgets are visible, and which reservations can be accessed. @@ -13,22 +13,22 @@ These two properties — authentication and tenancy — are foundational. They d Cycles authenticates requests using the `X-Cycles-API-Key` header. -Every request must include this header. If it is missing or the key is invalid, the server returns `401 UNAUTHORIZED`. +Tenant-authenticated runtime requests must include this header. If it is missing or the key is invalid, the server returns `401 UNAUTHORIZED`. Admin and public endpoints follow their own authentication rules. ``` X-Cycles-API-Key: your-api-key ``` -There is no session, no token exchange, no OAuth flow. Authentication is a single header on every request. +There is no session, token exchange, or OAuth flow in the reference API. Protected tenant requests use one API-key header; admin-only routes use `X-Admin-API-Key`. ### Public endpoints (no API key) -Two endpoints are explicitly public (declared with `security: []` in the protocol spec) and require no API key, both on the evidence surface: +The runtime YAML declares two public evidence endpoints with `security: []`: - `GET /v1/evidence/{evidence_id}` — signed-envelope retrieval. The `evidence_id` is an unguessable content-hash capability, and the envelope is content-addressed and signed, so public read cannot forge or alter it. - `GET /v1/.well-known/cycles-jwks.json` — the signer's public JWK Set (public keys only, the standard posture for a verification key set), used to verify evidence signatures. -Every other endpoint requires authentication. +The reference server also leaves `/actuator/health/liveness` and `/actuator/health/readiness` public for probes. Aggregate health, metrics, API documentation, and the remaining operational/API routes require the configured tenant or admin credential. ## The effective tenant @@ -215,18 +215,18 @@ The authentication and tenancy model provides several guarantees: - **Isolation**: tenants cannot see or modify each other's budgets, reservations, or balances - **Ownership**: reservations are permanently bound to the creating tenant -- **Validation**: every request is checked against the effective tenant before processing +- **Validation**: every protected tenant operation is checked against the effective tenant before processing - **Consistency**: the same tenancy rules apply to all endpoints (reserve, commit, release, extend, decide, events, balances, listing) -These properties hold regardless of whether the client is trusted. The server enforces them on every request. +These properties hold regardless of whether the client is trusted. The server enforces them on protected tenant operations. ## Summary -Authentication in Cycles is a single API key header (`X-Cycles-API-Key`) on every request. +Tenant-plane authentication uses the `X-Cycles-API-Key` header. Admin-only routes use `X-Admin-API-Key`, and explicitly public routes require neither. The server derives an effective tenant from the key and enforces tenant isolation across all operations: -- **Subject.tenant** must match the effective tenant on every mutation and query +- **Subject.tenant** must match the effective tenant on operations that carry a `Subject`; tenant query parameters are validation-only where supported - **Reservation ownership** is enforced on commit, release, extend, and get - **Balance visibility** is scoped to the effective tenant - **403 FORBIDDEN** is returned for any tenant mismatch diff --git a/protocol/caps-and-the-three-way-decision-model-in-cycles.md b/protocol/caps-and-the-three-way-decision-model-in-cycles.md index 78234d92..f0fb2a04 100644 --- a/protocol/caps-and-the-three-way-decision-model-in-cycles.md +++ b/protocol/caps-and-the-three-way-decision-model-in-cycles.md @@ -21,9 +21,9 @@ The middle option — ALLOW_WITH_CAPS — is what makes Cycles more useful than One wire-level detail matters here: `decision: DENY` only ever appears on responses that do not hold budget — `POST /v1/decide` responses and dry-run (`dry_run: true`) reservation responses. -A live (non-dry-run) reservation never returns `decision: DENY`. When budget is insufficient, the server rejects the request with an HTTP `409` error — `BUDGET_EXCEEDED`, or another 409 code such as `OVERDRAFT_LIMIT_EXCEEDED` or `DEBT_OUTSTANDING` — instead of a 200 response carrying a DENY decision. +A live (non-dry-run) reservation never returns `decision: DENY`. When budget is unavailable, the server rejects the request with an HTTP `409` error — `BUDGET_EXCEEDED`, or another 409 code such as `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, or `TENANT_CLOSED` — instead of a 200 response carrying a DENY decision. -Where callers find the denial reason follows the same split. On a DENY decision (decide or dry run), the response's `reason_code` field carries the machine-readable reason — `BUDGET_EXCEEDED`, `BUDGET_FROZEN`, `BUDGET_CLOSED`, `BUDGET_NOT_FOUND`, `OVERDRAFT_LIMIT_EXCEEDED`, or `DEBT_OUTSTANDING`. On a live denial, the equivalent information is in the 409 error response's `error` field. See [Decision reason codes](/protocol/error-codes-and-error-handling-in-cycles#decision-reason-codes). +Where callers find the denial reason follows the same split. On a DENY decision (decide or dry run), the response's `reason_code` field carries the machine-readable reason — `BUDGET_EXCEEDED`, `BUDGET_FROZEN`, `BUDGET_CLOSED`, `BUDGET_NOT_FOUND`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, or `TENANT_CLOSED`. On a live denial, the equivalent information is in the 409 error response's `error` field. See [Decision reason codes](/protocol/error-codes-and-error-handling-in-cycles#decision-reason-codes). ## Why binary decisions are not enough diff --git a/protocol/correlation-and-tracing-in-cycles.md b/protocol/correlation-and-tracing-in-cycles.md index 9c551550..e811f549 100644 --- a/protocol/correlation-and-tracing-in-cycles.md +++ b/protocol/correlation-and-tracing-in-cycles.md @@ -7,7 +7,7 @@ description: "W3C Trace Context correlation in Cycles — the three-tier model ( Cycles produces data on four planes — runtime responses, webhook deliveries, audit-log entries, and emitted events. Correlation identifiers stitch those planes into a single causal picture. -As of the 2026-04-18 spec revision (`cycles-protocol-v0.yaml`), Cycles implements a W3C Trace Context-compatible correlation contract across every server in the suite. This page is the authoritative reference. +The current YAML specifications define a W3C Trace Context-compatible correlation contract across the server suite. The reference services implement request and trace propagation; server-composed admin `correlation_id` values are also implemented. One current gap is called out below: the reference runtime does not yet populate the protocol's deterministic event-cluster `correlation_id`. ## The three-tier model @@ -17,16 +17,20 @@ Cycles carries three correlation identifiers, each with a different grain. |---|---|---| | `request_id` | One HTTP request | Per-request | | `trace_id` | One logical operation (may span many requests) | Per-operation | -| `correlation_id` | An event-stream cluster (groups related events) | Server-set: deterministic hash (protocol clusters) or operation ID (admin operations) | +| `correlation_id` | An event-stream cluster or admin operation fan-out | Server-set: deterministic hash required by the runtime protocol, or an implemented operation ID for selected admin operations | - **`request_id`** is server-generated for every inbound HTTP request. It appears in every error response, audit-log entry, and event that is causally downstream of that request. Use it to correlate the side effects of one specific HTTP call. -- **`trace_id`** identifies a logical operation that may cross several HTTP boundaries (for example: a client's reserve → multiple provider retries → commit). It is a 32-hex-character W3C Trace Context-compatible identifier. Use it to reconstruct the full operation across planes. -- **`correlation_id`** is set by the server, in one of two shapes depending on the emitting plane. **Protocol event-stream clusters** use a deterministic hash over `(tenant_id, scope, action_kind_or_risk_class, window, window_key)` — every event for the same cluster carries the same value, which is what lets you JOIN threshold-alert → trip → reset chains and `observed_denied` ↔ `reservation.denied` pairs. **Governance/admin operations** (lifecycle, bulk actions, webhook management, tenant-close cascades) use explicit server-composed operation IDs such as `webhook_create:` or `webhook_bulk_action::` — see the [event payload reference](/protocol/event-payloads-reference) for the shapes. Either way it is scoped to the event stream only — it does not appear on responses or audit rows. +- **`trace_id`** identifies a logical operation that may cross several HTTP boundaries (for example: a client's reserve → provider call → commit) when the caller propagates it. It is a 32-hex-character W3C Trace Context-compatible identifier. Use it to join the Cycles records that carry the field and application telemetry for the rest. +- **`correlation_id`** is server-managed and scoped to event rows; callers do not set it through runtime request bodies or tracing headers. The runtime YAML requires a deterministic hash over `(tenant_id, scope, action_kind_or_risk_class, window, window_key)` for event-stream clusters, but the current reference runtime passes `null` on its implemented emit paths, so consumers must not depend on that join yet. The current admin server does populate explicit operation IDs for selected lifecycle, bulk, webhook, and tenant-close operations, such as `webhook_create:` and `webhook_bulk_action::`. See the [Event Payloads Reference](/protocol/event-payloads-reference) for implemented shapes. ::: warning Don't confuse with `metadata.trace_id` [Standard Metrics and Metadata](/protocol/standard-metrics-and-metadata-in-cycles) documents application-level correlation keys that callers can put in the `metadata` map on commits and events — free-form strings the server stores but does not interpret. Name them distinctly (e.g. `external_trace_id`, `app_request_id`) rather than reusing `trace_id`/`request_id`, which are the server-managed identifiers described on this page (32-hex W3C, flowing on response headers, error bodies, events, audit rows, and webhook deliveries). The two coexist: your `metadata.external_trace_id` joins Cycles data with your own distributed tracing, while the server `trace_id` joins across Cycles planes. ::: +::: warning Propagation does not add fields to reservation records +The caller must send the same trace context on each related HTTP request; independently server-generated trace IDs are per request. The current `Reservation` model does not persist `trace_id`, and successful reserve, commit, release, and extend calls do not each emit lifecycle events. Keep the reservation ID and trace ID together in application logs when you need an end-to-end reserve/provider/settlement reconstruction. +::: + ## Inbound header precedence For every inbound HTTP request, the server derives `trace_id` in this strict order: @@ -84,7 +88,7 @@ Standard event payloads carry: |---|---| | `request_id` | Populated on every event causally downstream of an HTTP request — including async and queued work that spans thread / process boundaries. Pre-v0.1.25 events may lack it. | | `trace_id` | OPTIONAL on the schema; populated by conformant v0.1.25.14+ runtime servers. | -| `correlation_id` | Server-set, two shapes: a deterministic hash over `(tenant_id, scope, action_kind_or_risk_class, window, window_key)` for protocol event-stream clusters, or an explicit operation ID (`webhook_create:`, `webhook_bulk_action::`, cascade IDs) for governance/admin operations. Groups related events in the stream. | +| `correlation_id` | Server-managed. The runtime YAML requires deterministic event clusters, but the current reference runtime leaves this field absent on implemented runtime emits. Selected admin operations populate explicit IDs such as `webhook_create:`, bulk-action IDs, and cascade IDs. | ### Inside audit-log entries @@ -105,10 +109,10 @@ The `WebhookDelivery` schema carries three OPTIONAL fields as of governance-admi `trace_id` travels: - **Inbound request → response header** — echoed in `X-Cycles-Trace-Id` on every HTTP response. -- **Request → audit-log entry** — written into the audit row for the request's operation. +- **Request → audit-log entry** — written for admin operations and the runtime's admin-on-behalf-of release path that create an audit row. Ordinary tenant reserve/commit/release calls do not each create an `AuditLogEntry`. - **Request → emitted events** — attached to every event that is a side effect of the request, including events emitted from async workers (`ReservationExpiryService` for example mints a fresh `trace_id` per sweep batch so all `reservation.expired` events in that batch correlate to each other). - **Events → webhook deliveries** — carried through to each outbound HTTP POST as `X-Cycles-Trace-Id` and embedded in `traceparent`. -- **Across thread / queue / process boundaries** — REQUIRED. Async workers MUST propagate the originating `trace_id` when they emit events, write audit rows, or dispatch webhooks. +- **Across thread / queue / process boundaries** — REQUIRED when work is causally downstream of a request. Internally originated sweepers may mint their own context; the reference reservation-expiry sweeper creates one trace ID per batch. ## Querying by correlation identifiers @@ -118,6 +122,7 @@ The admin plane supports exact-match filters on correlation identifiers: | Query parameter | Effect | |---|---| +| `correlation_id=` | Narrows to events carrying one implemented server-composed correlation ID. Most useful for admin bulk actions, webhook lifecycle operations, and tenant-close cascades; current runtime events generally lack it. | | `trace_id=<32-hex>` | Narrows to events emitted during one logical operation. May span multiple requests. | | `request_id=` | Narrows to events that are side effects of one specific HTTP request. | @@ -128,7 +133,7 @@ The admin plane supports exact-match filters on correlation identifiers: | `trace_id=<32-hex>` | Narrows to audit rows for one logical operation. | | `request_id=` | Narrows to audit rows for one specific HTTP request. | -These are the only two admin endpoints with server-side correlation filters. The webhook-delivery list endpoints (`GET /v1/admin/webhooks/{subscription_id}/deliveries` and the tenant-scoped variant) accept only `status` / `from` / `to` / pagination parameters — there is no `trace_id` query parameter. To join deliveries into a trace, filter client-side on the `trace_id` field of each `WebhookDelivery` record. +These are the only two admin endpoints with server-side request/trace filters; only the events endpoint also accepts `correlation_id`. The webhook-delivery list endpoints (`GET /v1/admin/webhooks/{subscription_id}/deliveries` and the tenant-scoped variant) accept only `status` / `from` / `to` / pagination parameters — there is no `trace_id` query parameter. To join deliveries into a trace, filter client-side on the `trace_id` field of each `WebhookDelivery` record. Both filters are post-hydration predicates applied null-safely — entries with null field values (historical writes, off-request emissions, internal sweeper work) cannot satisfy a supplied filter value. Pre-v0.1.25.14 runtime entries and pre-v0.1.25.31 admin entries may lack `trace_id` and silently drop out of these joins; use `request_id` for those (the `request_id` contract predates `trace_id`). @@ -171,7 +176,7 @@ curl -s "http://localhost:7979/v1/admin/webhooks//deliveries" \ | jq --arg tid "$TID" '.deliveries[] | select(.trace_id == $tid) | {status, response_status, trace_flags}' ``` -Three calls, one ID — two server-side filters plus one client-side filter — full causal picture across runtime response → audit row → emitted events → webhook fan-out. +Three calls, one trace ID — two server-side filters plus one client-side filter — return the records that each plane retained for that trace. Application authorization and external execution outcomes still come from your own trace-linked logs. ## Logging `trace_id` in client code diff --git a/protocol/cycles-evidence-envelopes-in-cycles.md b/protocol/cycles-evidence-envelopes-in-cycles.md index ebe9b71f..cfa7dd61 100644 --- a/protocol/cycles-evidence-envelopes-in-cycles.md +++ b/protocol/cycles-evidence-envelopes-in-cycles.md @@ -55,7 +55,7 @@ It is **transport metadata, not attested** — present for the caller's convenie ### Denials → the `error` artifact -A non-dry `reserve` over budget is **not** a `200` with `decision: DENY` — it is an `HTTP 409` with `error: BUDGET_EXCEEDED`, captured as an `error` envelope (`endpoint: "POST /v1/reservations"`, `http_status: 409`). The other budget/lifecycle denials behave the same — `BUDGET_FROZEN`, `BUDGET_CLOSED`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, `UNIT_MISMATCH`, and the commit/release terminal-state denials `RESERVATION_FINALIZED` (409) and `RESERVATION_EXPIRED` (410). Pre-evaluation failures (validation, auth, malformed body) carry **no** `cycles_evidence` — no decision was reached, so there is nothing to attest. (A dry-run preflight denial, by contrast, is a `200` captured as `reserve` evidence — it is the canonical "would this be allowed?" attestation.) +A non-dry `reserve` over budget is **not** a `200` with `decision: DENY` — it is an `HTTP 409` with `error: BUDGET_EXCEEDED`, captured as an `error` envelope (`endpoint: "POST /v1/reservations"`, `http_status: 409`). The other post-evaluation budget/lifecycle denials behave the same — `BUDGET_FROZEN`, `BUDGET_CLOSED`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, `TENANT_CLOSED`, `UNIT_MISMATCH`, and the commit/release terminal-state denials `RESERVATION_FINALIZED` (409) and `RESERVATION_EXPIRED` (410). Pre-evaluation failures (validation, auth, malformed body) carry **no** `cycles_evidence` — no decision was reached, so there is nothing to attest. (A dry-run preflight denial, by contrast, is a `200` captured as `reserve` evidence — it is the canonical "would this be allowed?" attestation.) ## `evidence_id` — the content-hash recipe (normative) diff --git a/protocol/event-payloads-reference.md b/protocol/event-payloads-reference.md index 376de7c3..67d872ac 100644 --- a/protocol/event-payloads-reference.md +++ b/protocol/event-payloads-reference.md @@ -13,7 +13,7 @@ The v0.1.25 Admin API `EventType` enum registers **51 event types** total across **Registered enum values currently emitted** (count toward the 51 total): - **Reservation:** `reservation.denied`, `reservation.expired`, `reservation.commit_overage` (runtime). -- **Budget:** `budget.exhausted`, `budget.over_limit_entered`, `budget.debt_incurred`, `budget.reset_spent` (runtime + admin v0.1.25.18+); `budget.funded`, `budget.debited`, `budget.reset`, `budget.debt_repaid` (admin v0.1.25.38+). +- **Budget:** runtime emits `budget.exhausted`, `budget.over_limit_entered`, and `budget.debt_incurred`; admin emits `budget.created`, `budget.updated`, `budget.frozen`, `budget.unfrozen`, `budget.funded`, `budget.debited`, `budget.reset`, `budget.reset_spent`, and `budget.debt_repaid`; tenant close emits `budget.closed_via_tenant_cascade`. - **Tenant:** `tenant.created`, `tenant.updated` (current admin implementation); `tenant.suspended`, `tenant.reactivated`, `tenant.closed` (admin v0.1.25.38+, single-op + bulk-action paths). - **API key:** `api_key.created`, `api_key.revoked`, and `api_key.auth_failed` (admin v0.1.25+); `api_key.permissions_changed` (admin v0.1.25.7+). - **Policy:** `policy.created` and `policy.updated` (admin v0.1.25+). @@ -23,7 +23,7 @@ The v0.1.25 Admin API `EventType` enum registers **51 event types** total across Registered values not named above remain planned unless a later section says otherwise. -**Additive reference-server payloads** (observable in the reference implementation but not part of the published admin-openapi enum — consumers must ignore unrecognized event types gracefully): +**Historical additive runtime payloads** (documented in the v0.1.25.3 release history, but not present in the current runtime `EventType` model or emitted by current controller paths): - Reservation lifecycle samples: `reservation.reserved`, `reservation.committed`, `reservation.released`, `reservation.extended`. - Runtime ledger application: `event.applied`. @@ -70,7 +70,7 @@ Every event shares this envelope structure. The `data` field varies by event typ | `source` | string | Yes | Emitting service: `cycles-server` (runtime events), `cycles-admin` (admin-plane events including bulk-action emits and webhook lifecycle events since v0.1.25.38/.39), or `cycles-events` (dispatcher-emitted `webhook.disabled` on auto-disable, v0.1.25.11). | | `actor` | object | When applicable | Who triggered: `type` (`api_key`, `admin`, `system`, `scheduler`), `key_id`, `source_ip` | | `data` | object | Varies | Event-specific payload (see below). Some events emit `null`. | -| `correlation_id` | string | When applicable | Server-set family key — deterministic hash for event-stream clusters, explicit operation IDs (`webhook_create:` etc.) for admin operations | +| `correlation_id` | string | When applicable | Server-managed family key. The YAML requires deterministic runtime event clusters, but the current reference runtime leaves this absent on implemented runtime emits. Selected admin operations populate explicit IDs such as `webhook_create:`, bulk-action IDs, and cascade IDs. | | `request_id` | string | When provided | From `X-Request-Id` header on originating request | | `trace_id` | string | When provided | W3C Trace Context-compatible correlation identifier (32 lowercase hex characters). Links the event to the originating request, its audit entry, and sibling events within the same logical operation. | | `metadata` | object | When provided | Operator-defined key-value pairs | @@ -79,61 +79,68 @@ Every event shares this envelope structure. The `data` field varies by event typ ## Reservation Events -### `reservation.reserved` — Additive Reference-Server Payload (v0.1.25.3) +### `reservation.reserved` — Historical Additive Payload (v0.1.25.3) -**Trigger:** A reservation is created successfully. +**Historical trigger:** A reservation was created successfully. -**Emitted from:** `POST /v1/reservations` (ALLOW or ALLOW_WITH_CAPS response). - -The envelope's `scope`, `tenant_id`, and `actor` fields identify the reservation context. The `data` payload carries the reservation identifier and the amount held. +The current reference runtime does not emit this event. Query reservation state and keep application telemetry for successful reserve operations. --- -### `reservation.committed` — Additive Reference-Server Payload (v0.1.25.3) - -**Trigger:** A reservation is committed with actual spend recorded. +### `reservation.committed` — Historical Additive Payload (v0.1.25.3) -**Emitted from:** `POST /v1/reservations/{id}/commit`. +**Historical trigger:** A reservation was committed with actual spend recorded. -If `actual > estimated`, a companion `reservation.commit_overage` event is also emitted (see below). +The current reference runtime does not emit this event. A commit that requests more than the estimate can emit `reservation.commit_overage`, and budget-state changes can emit the implemented budget events. --- -### `reservation.released` — Additive Reference-Server Payload (v0.1.25.3) +### `reservation.released` — Historical Additive Payload (v0.1.25.3) -**Trigger:** A reservation is cancelled. +**Historical trigger:** A reservation was cancelled. -**Emitted from:** `POST /v1/reservations/{id}/release`. If the release was performed by an admin operator (dual-auth path introduced in v0.1.25.8), the envelope's `actor.type` will be `admin` and the audit log records `metadata.actor_type=admin_on_behalf_of`. +The current reference runtime does not emit this event. An admin-on-behalf-of release does write its required audit entry; ordinary release state remains queryable through the reservation API. --- -### `reservation.extended` — Additive Reference-Server Payload (v0.1.25.3) +### `reservation.extended` — Historical Additive Payload (v0.1.25.3) -**Trigger:** A reservation TTL is extended via heartbeat. +**Historical trigger:** A reservation TTL was extended via heartbeat. -**Emitted from:** `POST /v1/reservations/{id}/extend`. +The current reference runtime does not emit this event. Query the reservation for current expiry state and log successful extensions in the application when needed. --- ### `reservation.denied` — Currently Emitted -**Trigger:** A reservation or decide request returns DENY. +**Trigger:** `POST /v1/decide` or a reservation request with `dry_run: true` returns `decision: DENY`. -**Emitted from:** `POST /v1/reservations` (DENY response), `POST /v1/decide` (DENY response) +**Emitted from:** `POST /v1/reservations` when the nonpersisting dry-run response is DENY, and `POST /v1/decide` when its response is DENY. A live reservation denial is an HTTP error such as `409 BUDGET_EXCEEDED`; the current controller does not emit `reservation.denied` for that exception path. Monitor `cycles_reservations_reserve_total{decision="DENY"}` or application errors for live denial rate. ```json { "event_type": "reservation.denied", "data": { "scope": "tenant:acme-corp/workspace:prod/workflow:support", + "unit": "USD_MICROCENTS", "reason_code": "BUDGET_EXCEEDED", - "requested_amount": 500000 + "requested_amount": 500000, + "remaining": 100000, + "action": { + "kind": "llm.chat", + "name": "support-reply" + }, + "subject": { + "tenant": "acme-corp", + "workspace": "prod", + "workflow": "support" + } } } ``` ::: tip Fields populated at emission time -The `reservation.denied` event model defines 9 fields, but the current server emission populates `scope`, `reason_code`, and `requested_amount`. The remaining fields (`unit`, `remaining`, `action`, `subject`, `policy_id`, `deny_detail`) are defined in the model and may be populated in future releases. +The governance schema defines 9 fields. The current server emitter populates `scope`, `unit`, `reason_code`, `requested_amount`, `action`, and `subject`; a denied reservation dry run also derives `remaining` from returned balances, while `/decide` currently omits `remaining`. `policy_id` and `deny_detail` remain unpopulated. ::: | Field | Type | Populated | Description | @@ -141,10 +148,10 @@ The `reservation.denied` event model defines 9 fields, but the current server em | `scope` | string | Yes | Scope path that denied the reservation | | `reason_code` | string | Yes | Why denied. Known values: `BUDGET_EXCEEDED`, `OVERDRAFT_LIMIT_EXCEEDED`, `DEBT_OUTSTANDING`, `BUDGET_FROZEN`, `BUDGET_CLOSED`, and — from cycles-server 0.1.25.47 (spec v0.1.25.13) — `TENANT_CLOSED` on fresh dry-run/decide DENYs for a closed owning tenant. Open string — extensions (v0.1.26+) may emit additional values such as `ACTION_QUOTA_EXCEEDED`, `ACTION_KIND_DENIED`, `ACTION_KIND_NOT_ALLOWED`. | | `requested_amount` | number | Yes | Amount the reservation requested | -| `unit` | string | Not yet | Budget unit (`USD_MICROCENTS`, `TOKENS`, `CREDITS`, `RISK_POINTS`) | -| `remaining` | number | Not yet | Budget remaining at the scope that denied | -| `action` | object | Not yet | Action metadata from the reservation request | -| `subject` | object | Not yet | Subject metadata from the reservation request | +| `unit` | string | Yes | Budget unit (`USD_MICROCENTS`, `TOKENS`, `CREDITS`, `RISK_POINTS`) | +| `remaining` | number | Dry-run reserve only | Minimum remaining amount derived from returned balances; omitted by the current `/decide` emitter | +| `action` | object | Yes | Action metadata from the evaluation request | +| `subject` | object | Yes | Subject metadata from the evaluation request | | `policy_id` | string | Not yet | Policy ID that caused the denial, when applicable (added v0.1.25.8) | | `deny_detail` | object | Not yet | Operator-grade structured context (added v0.1.25.8). Populated by extensions; may include `quota_violation`, `blocked_by_policy`, `blocked_by_scope`, `suggested_fix`, `budget_remaining`. | @@ -247,29 +254,9 @@ This event type is defined in the protocol but not yet emitted by the Cycles ser ## Budget Events -### `budget.approaching_limit` — Additive Reference-Server Payload (v0.1.25.3, dedup fixed v0.1.25.5) - -**Trigger:** A scope's utilization crosses the configured "approaching" threshold (default **80%**). - -**Emitted from:** `EventEmitterService.emitBalanceEvents()` on reservation / commit / event. - -The envelope identifies the scope; the `data` payload reports `utilization`, `remaining`, and the threshold crossed. Subscriptions that want pager-ready escalation should filter on `event_type=budget.approaching_limit OR budget.at_limit OR budget.over_limit`. - ---- - -### `budget.at_limit` — Additive Reference-Server Payload (v0.1.25.3, dedup fixed v0.1.25.5) - -**Trigger:** Utilization crosses the "at-limit" threshold (default **95%**). +### Historical threshold aliases -**Emitted from:** Same emission path as `approaching_limit`. Dedup logic prevents re-emission while the scope remains in the same state band on subsequent mutations. - ---- - -### `budget.over_limit` — Additive Reference-Server Payload (v0.1.25.3, dedup fixed v0.1.25.5) - -**Trigger:** Utilization reaches or exceeds **100%**. - -**Emitted from:** Same emission path. Distinct from `budget.over_limit_entered`, which fires when debt first exceeds `overdraft_limit` under `ALLOW_WITH_OVERDRAFT`. +Earlier v0.1.25.x builds documented additive `budget.approaching_limit`, `budget.at_limit`, and `budget.over_limit` aliases. They are not in the current runtime `EventType` model and the current `EventEmitterService` does not emit them. Use `budget.exhausted` for the implemented zero-remaining transition and calculate earlier utilization alerts from balances or metrics. The registered `budget.threshold_crossed` type remains unimplemented. --- @@ -285,30 +272,50 @@ See [Rolling over billing periods with RESET_SPENT](/how-to/rolling-over-billing --- -### `event.applied` — Additive Reference-Server Payload (v0.1.25.3) +### `event.applied` — Historical Additive Payload (v0.1.25.3) -**Trigger:** A direct debit via `POST /v1/events` is applied successfully (no pre-reservation path). +**Historical trigger:** A direct debit via `POST /v1/events` was applied successfully. -**Emitted from:** Runtime events controller. The envelope's `scope` and `actor` identify the debit; the `data` payload reports the amount charged. +The current reference runtime does not emit `event.applied`. It returns the applied direct-debit response and can emit implemented budget-state events when that debit changes a ledger. --- ### `budget.exhausted` — Currently Emitted -**Trigger:** A budget's remaining amount reaches zero after a reservation or commit. +**Trigger:** A budget's remaining amount transitions from above zero to zero after a reservation, commit, or direct debit. -**Emitted from:** `EventEmitterService.emitBalanceEvents()` (when `remaining.amount == 0`) +**Emitted from:** `EventEmitterService.emitBalanceEvents()` (when pre-operation remaining is above zero and post-operation remaining is zero) ```json { "event_type": "budget.exhausted", - "data": null + "data": { + "scope": "tenant:acme-corp/workspace:prod", + "unit": "USD_MICROCENTS", + "threshold": 1.0, + "utilization": 1.0, + "allocated": 10000000, + "remaining": 0, + "spent": 9000000, + "reserved": 1000000, + "direction": "rising" + } } ``` -::: tip Envelope contains context -While the `data` field is `null` for this event, the envelope's `scope`, `tenant_id`, and `actor` fields identify which budget exhausted and what triggered it. Query the budget's current state via the admin API for balance details. -::: +| Field | Type | Description | +|---|---|---| +| `scope` | string | Affected scope path | +| `unit` | string | Budget unit | +| `threshold` | number | `1.0` for the implemented exhaustion transition | +| `utilization` | number | `(spent + reserved) / allocated` when allocated is positive | +| `allocated` | number | Current allocated amount | +| `remaining` | number | `0` for this event | +| `spent` | number | Current spent amount | +| `reserved` | number | Current reserved amount | +| `direction` | string | `rising` | + +The envelope also identifies the tenant, actor, and request context. Query the balance API before remediation because later operations can change the ledger after the event is emitted. --- @@ -345,7 +352,7 @@ While the `data` field is `null` for this event, the envelope's `scope`, `tenant ### `budget.debt_incurred` — Currently Emitted -**Trigger:** A commit creates new debt via `ALLOW_WITH_OVERDRAFT` policy (actual cost exceeds available budget). +**Trigger:** A reservation commit or direct debit creates new debt via `ALLOW_WITH_OVERDRAFT`. **Emitted from:** `EventEmitterService.emitBalanceEvents()` (when new debt is created) @@ -355,42 +362,33 @@ While the `data` field is `null` for this event, the envelope's `scope`, `tenant "data": { "scope": "tenant:acme-corp/workspace:prod", "unit": "USD_MICROCENTS", + "reservation_id": "res_a1b2c3d4", + "debt_incurred": 250000, "total_debt": 750000, - "overdraft_limit": 1000000 + "overdraft_limit": 1000000, + "overage_policy": "ALLOW_WITH_OVERDRAFT" } } ``` -::: tip Fields populated at emission time -The `budget.debt_incurred` event model defines 7 fields, but the current server emission populates `scope`, `unit`, `total_debt`, and `overdraft_limit`. The remaining fields (`reservation_id`, `debt_incurred`, `overage_policy`) are defined in the model and may be populated in future releases. -::: - -| Field | Type | Populated | Description | -|---|---|---|---| -| `scope` | string | Yes | Affected scope path | -| `unit` | string | Yes | Budget unit | -| `total_debt` | number | Yes | Total accumulated debt on this scope | -| `overdraft_limit` | number | Yes | Configured overdraft ceiling | -| `reservation_id` | string | Not yet | Reservation whose commit caused the debt | -| `debt_incurred` | number | Not yet | New debt from this commit | -| `overage_policy` | string | Not yet | Policy applied (`ALLOW_WITH_OVERDRAFT`) | +| Field | Type | Description | +|---|---|---| +| `scope` | string | Affected scope path | +| `unit` | string | Budget unit | +| `reservation_id` | string | Reservation whose commit caused the debt; omitted for a direct debit | +| `debt_incurred` | number | New debt created on this scope by the operation | +| `total_debt` | number | Total accumulated debt on this scope | +| `overdraft_limit` | number | Configured overdraft ceiling | +| `overage_policy` | string | Policy applied (`ALLOW_WITH_OVERDRAFT`) | --- ### Planned Budget Events -The following budget events are defined in the protocol but not yet emitted. They will be implemented as admin service and budget lifecycle operations gain event hooks. +The following registered budget events are not emitted by the current reference services: | Event Type | Trigger | |---|---| -| `budget.created` | Budget ledger created via admin API | -| `budget.updated` | Budget configuration changed | -| `budget.funded` | CREDIT, DEBIT, RESET, or REPAY_DEBT funding operation | -| `budget.debited` | Funds removed from budget | -| `budget.reset` | Budget reset to new allocated amount | -| `budget.debt_repaid` | Outstanding debt repaid via REPAY_DEBT | -| `budget.frozen` | Budget status set to FROZEN | -| `budget.unfrozen` | Budget restored from FROZEN | | `budget.closed` | Budget permanently closed | | `budget.threshold_crossed` | Utilization crossed configured threshold (e.g., 80%, 95%) | | `budget.over_limit_exited` | Debt dropped below overdraft limit after repayment | @@ -665,7 +663,7 @@ Current services emit part of every category below. The tables distinguish direc | Category | Total Defined | Currently Emitted | Notes | |---|---|---|---| | Reservation | 6 | `reservation.denied`, `reservation.expired`, and `reservation.commit_overage` emitted by runtime paths; the cascade aggregate (`reservation.released_via_tenant_cascade`, spec-declared since governance v0.1.25.35) emitted by the admin server on tenant close | Spike events still planned | -| Budget | 17 | `budget.exhausted`, `over_limit_entered`, `debt_incurred`, `reset_spent`, and admin funding events emitted by current services; `budget.closed_via_tenant_cascade` (spec-declared since v0.1.25.35) emitted on tenant close; legacy threshold aliases are additive reference-server payloads | Remaining lifecycle/threshold types still planned | +| Budget | 17 | Runtime exhaustion/over-limit/debt events; admin create/update/freeze/unfreeze/funding events; `budget.closed_via_tenant_cascade` on tenant close | `budget.closed`, `budget.threshold_crossed`, `budget.over_limit_exited`, and `budget.burn_rate_anomaly` are not emitted | | Tenant | 6 | `tenant.created`, `tenant.updated`, `tenant.suspended`, `tenant.reactivated`, and `tenant.closed` emitted by the current admin service | `tenant.settings_changed` still planned | | API Key | 7 | `api_key.created`, `.revoked`, `.permissions_changed`, `.auth_failed`, plus `.revoked_via_tenant_cascade` | Expiry and rate-spike events still planned | | Policy | 3 | `policy.created`, `policy.updated` | `policy.deleted` still planned | diff --git a/protocol/how-events-work-in-cycles-direct-debit-without-reservation.md b/protocol/how-events-work-in-cycles-direct-debit-without-reservation.md index f25fa568..14f9f240 100644 --- a/protocol/how-events-work-in-cycles-direct-debit-without-reservation.md +++ b/protocol/how-events-work-in-cycles-direct-debit-without-reservation.md @@ -51,7 +51,7 @@ Events are useful when: - recording a model call that already happened through an external gateway - importing usage from a billing provider into Cycles for unified budget tracking - logging a known-cost action like sending an email or creating a ticket -- accounting for background work that was not instrumented with reserve/commit +- accounting for background work that was not instrumented with reserve-commit - migrating from a legacy usage system into Cycles ## When not to use events diff --git a/protocol/standard-metrics-and-metadata-in-cycles.md b/protocol/standard-metrics-and-metadata-in-cycles.md index 372c2cf9..9187b440 100644 --- a/protocol/standard-metrics-and-metadata-in-cycles.md +++ b/protocol/standard-metrics-and-metadata-in-cycles.md @@ -259,7 +259,7 @@ Standard metrics and metadata enrich budget operations with execution context: - **latency_ms** — operation duration - **model_version** — actual model used - **custom** — extensible metrics map -- **metadata** — correlation IDs, audit context, and debugging data +- **metadata** — application correlation keys, audit context, and debugging data These fields are optional but recommended. They turn budget accounting from raw cost numbers into actionable operational data. @@ -269,19 +269,23 @@ The metrics above describe the **execution-context fields** a client attaches to Cycles also exposes **Prometheus metrics** on each service's `/actuator/prometheus` endpoint for operational monitoring. These are aggregate counters and histograms — they do not replace per-request metrics, they complement them. -The runtime server (`cycles-server` v0.1.25.10+) publishes seven domain counters under the `cycles_*` namespace: +The runtime server (`cycles-server` v0.1.25.10+) currently publishes 11 domain counters and one maintenance timer. The core lifecycle counters are: - `cycles_reservations_reserve_total{tenant, decision, reason, overage_policy}` - `cycles_reservations_commit_total{tenant, decision, reason, overage_policy}` - `cycles_reservations_release_total{tenant, actor_type, decision, reason}` - `cycles_reservations_extend_total{tenant, decision, reason}` - `cycles_reservations_expired_total{tenant}` +- `cycles_reservations_quarantined_total{tenant, reason}` +- `cycles_reservations_created_at_index_reads_total{outcome}` - `cycles_events_total{tenant, decision, reason, overage_policy}` - `cycles_overdraft_incurred_total{tenant}` -The admin server (`cycles-server-admin` v0.1.25.20+) adds `cycles_admin_audit_writes_total{path_class, outcome}` — **alert on `outcome=error` nonzero** to catch silent audit-coverage loss. +Maintenance adds `cycles_maintenance_runs_total{job, outcome}` and `cycles_maintenance_duration_seconds{job, outcome}`; evidence enqueue failures use `cycles_evidence_emit_failed_total{artifact_type}`. -The events service (`cycles-server-events` v0.1.25.6+) publishes webhook delivery, evidence-worker, and dispatcher metrics under `cycles_webhook_*` and `cycles_evidence_*` — see [Server Configuration Reference → Events service metrics](/configuration/server-configuration-reference-for-cycles#events-service-metrics) for the current inventory. +The admin server currently exposes seven custom counters, including `cycles_admin_audit_writes_total{path_class, outcome}`—**alert on `outcome=error` nonzero** to catch silent audit-coverage loss—and tenant-close reconciliation/outbox signals. + +The events service (`cycles-server-events` v0.1.25.6+) currently publishes 17 webhook-delivery, evidence-worker, dispatcher, and security counters plus one delivery-latency timer. See the [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference) for the authoritative inventory and labels. The runtime and events services gate their optional `tenant` label with `cycles.metrics.tenant-tag.enabled`. The runtime defaults it to `true`; the events service defaults it to `false`. Admin `cycles_admin_*` counters do not carry a tenant label, so this toggle does not apply there. diff --git a/protocol/webhook-event-delivery-protocol.md b/protocol/webhook-event-delivery-protocol.md index 1a6a0671..0cb4c5ae 100644 --- a/protocol/webhook-event-delivery-protocol.md +++ b/protocol/webhook-event-delivery-protocol.md @@ -60,12 +60,14 @@ The body is a JSON-serialized Event object: Fields `scope`, `actor`, `data`, `correlation_id`, `request_id`, `trace_id`, and `metadata` are optional (omitted when null). -**Correlation fields.** `request_id` narrows to one HTTP request; `trace_id` (32-hex W3C) narrows to one logical operation (may span many requests); `correlation_id` groups a family of related events — it is server-set in one of two shapes: a deterministic hash over `(tenant_id, scope, action_kind_or_risk_class, window, window_key)` for protocol event-stream clusters, or an explicit operation ID (e.g. `webhook_create:`, `webhook_bulk_action::`) for governance/admin operations. See [Correlation and Tracing](/protocol/correlation-and-tracing-in-cycles). +**Correlation fields.** `request_id` narrows to one HTTP request; `trace_id` (32-hex W3C) joins related requests when the caller propagates the same context. `correlation_id` is server-managed: the runtime YAML requires deterministic event-cluster hashes, but the current reference runtime leaves the field absent on its implemented emits; selected admin operations populate explicit IDs such as `webhook_create:` and `webhook_bulk_action::`. See [Correlation and Tracing](/protocol/correlation-and-tracing-in-cycles). ## Event types (51) The current v0.1.25 Admin API `EventType` enum registers 51 event types across seven categories: budget (17), reservation (6), tenant (6), api_key (7), policy (3), webhook (7), and system (5). Implementations may add future event types, and consumers should ignore unrecognized values gracefully. The per-category tables below list the 47 non-cascade types; the four `*_via_tenant_cascade` types (one each in the budget, reservation, api_key, and webhook categories, added to the enum in governance revision v0.1.25.35) are covered in [Tenant-close cascade fan-out](#tenant-close-cascade-fan-out). +Registration does not guarantee emission. The trigger tables describe each registered type's contract; consult the [Event Payloads Reference](/protocol/event-payloads-reference) for the current reference-service emission matrix before subscribing. + ::: info Count note The 51-type / 7-category count tracks the admin OpenAPI enum. The runtime spec's webhook-event guidance section in `cycles-protocol-v0.yaml` lists 35 event types across 6 categories — it predates the `webhook` lifecycle category and some later enum additions. ::: @@ -76,7 +78,7 @@ The 51-type / 7-category count tracks the admin OpenAPI enum. The runtime spec's |------------|---------| | `budget.created` | Budget ledger created | | `budget.updated` | Budget ledger configuration changed | -| `budget.funded` | CREDIT, DEBIT, RESET, REPAY_DEBT, or RESET_SPENT funding operation | +| `budget.funded` | CREDIT funding operation | | `budget.debited` | Budget debited (funds removed) | | `budget.reset` | Budget resized (`allocated` changed; `spent`/`reserved`/`debt` preserved) | | `budget.reset_spent` | New billing period started (`allocated` set; `spent` cleared or explicitly set; `reserved`/`debt` preserved) | @@ -88,16 +90,16 @@ The 51-type / 7-category count tracks the admin OpenAPI enum. The runtime spec's | `budget.exhausted` | Remaining budget reached zero | | `budget.over_limit_entered` | Debt exceeded overdraft limit | | `budget.over_limit_exited` | Debt dropped below overdraft limit | -| `budget.debt_incurred` | New debt created via ALLOW_WITH_OVERDRAFT commit | +| `budget.debt_incurred` | New debt created by an ALLOW_WITH_OVERDRAFT commit or direct debit | | `budget.burn_rate_anomaly` | Spend rate exceeds baseline multiplier within the configured window | ### Reservation events (5) | Event Type | Trigger | |------------|---------| -| `reservation.denied` | Reservation rejected (budget exceeded, frozen, closed, debt outstanding) | +| `reservation.denied` | A dry-run reservation or `/v1/decide` evaluation returned `DENY`; current live 4xx reservation errors do not emit this event | | `reservation.denial_rate_spike` | Denial rate exceeded threshold within window | -| `reservation.expired` | Reservation TTL expired without commit | +| `reservation.expired` | Reservation TTL expired without commit or release | | `reservation.expiry_rate_spike` | Expiry rate exceeded threshold within window | | `reservation.commit_overage` | Commit actual exceeded reserved estimate | @@ -160,7 +162,7 @@ This is **governance WEBHOOK SUBSCRIPTION INVARIANT 2** (normative, cross-plane, ::: warning Enforced at three layers (issue #209) The guarantee is defense-in-depth across the two services — verify your fleet is fully upgraded: -- **Write** — both provisioning planes reject an admin-only type or category on a concrete-tenant subscription with `400 INVALID_REQUEST`. The tenant self-service plane (`POST`/`PATCH /v1/webhooks`) since **cycles-server-admin 0.1.25.50** (governance v0.1.25.38); the admin plane (`POST /v1/admin/webhooks?tenant_id=X`, `PATCH /v1/admin/webhooks/{id}`) since **0.1.25.51** (governance v0.1.25.40) — update validates the *effective resulting* selectors (each array as it stands after the update — the request's value where provided, the stored value where omitted; `PATCH` replaces a supplied array, it does not merge), so a status-only reactivation validates the still-stored selectors and can't re-enable a disabled offender that holds admin-only ones. `__system__`-owned subscriptions are exempt (system-wide monitoring is legitimate). +- **Write** — both provisioning planes reject an admin-only type or category on a concrete-tenant subscription with `400 INVALID_REQUEST`. The tenant self-service plane (`POST /v1/webhooks`, `PATCH /v1/webhooks/{subscription_id}`) since **cycles-server-admin 0.1.25.50** (governance v0.1.25.38); the admin plane (`POST /v1/admin/webhooks?tenant_id=X`, `PATCH /v1/admin/webhooks/{id}`) since **0.1.25.51** (governance v0.1.25.40) — update validates the *effective resulting* selectors (each array as it stands after the update — the request's value where provided, the stored value where omitted; `PATCH` replaces a supplied array, it does not merge), so a status-only reactivation validates the still-stored selectors and can't re-enable a disabled offender that holds admin-only ones. `__system__`-owned subscriptions are exempt (system-wide monitoring is legitimate). - **Dispatch** — since **0.1.25.51**, live dispatch and replay skip any admin-only event per-event for a concrete-tenant subscription, fail-closed and independent of stored-selector correctness (a default-on startup reconciler additionally strips legacy admin-only selectors from stored non-`DISABLED` rows — best-effort hygiene, see below). - **Last-mile delivery** — since **cycles-server-events 0.1.25.23**, the delivery worker re-checks the boundary immediately before every outbound POST (initial, retry, and recovered redeliveries), catching deliveries queued before the upgrade and every retry. **Rolling-deploy caveat:** this is a per-worker guarantee — airtight only once *all* delivery workers are on 0.1.25.23. diff --git a/quickstart/deploying-the-events-service.md b/quickstart/deploying-the-events-service.md index 756a3cc5..1f7778e3 100644 --- a/quickstart/deploying-the-events-service.md +++ b/quickstart/deploying-the-events-service.md @@ -159,22 +159,32 @@ Signing secrets are encrypted at rest with AES-256-GCM using `WEBHOOK_SECRET_ENC ## Prometheus metrics -The events service publishes webhook delivery metrics under the `cycles_webhook_*` namespace on `/actuator/prometheus`, served on the management port (9980 by default as of v0.1.25.9; was 7980 on pre-.9 builds). Update Prometheus scrape targets accordingly — the metric names and labels are unchanged. +The events service publishes 17 delivery, evidence, dispatcher, and security counters plus one latency timer on `/actuator/prometheus`, served on the management port (9980 by default as of v0.1.25.9; was 7980 on pre-.9 builds). | Metric | Tags | Description | |--------|------|-------------| | `cycles_webhook_delivery_attempts_total` | `tenant`, `event_type` | Every outbound HTTP attempt (including retries) | -| `cycles_webhook_delivery_success_total` | `tenant`, `event_type`, `status_code_family` (`2xx`/`3xx`/`4xx`/`5xx`) | Attempts that received HTTP 2xx | +| `cycles_webhook_delivery_success_total` | `tenant`, `event_type`, `status_code_family` (`2xx`) | Attempts that received HTTP 2xx | | `cycles_webhook_delivery_failed_total` | `tenant`, `event_type`, `reason` | Failed attempts, bucketed by failure reason | | `cycles_webhook_delivery_retried_total` | `tenant`, `event_type` | Retry attempts scheduled on the `dispatch:retry` ZSET | | `cycles_webhook_delivery_stale_total` | `tenant` | Deliveries auto-failed by the `MAX_DELIVERY_AGE_MS` gate | +| `cycles_webhook_delivery_dead_lettered_total` | `reason` | Delivery jobs moved to dead letter | +| `cycles_webhook_delivery_boundary_skipped_total` | `tenant`, `event_type`, `category` | Deliveries intentionally skipped at a configured boundary | | `cycles_webhook_subscription_auto_disabled_total` | `tenant`, `reason` | Subscriptions transitioned to `DISABLED` after `disable_after_failures` | | `cycles_webhook_delivery_latency_seconds` | `tenant`, `event_type`, `outcome` | Timer — HTTP RTT per delivery attempt | | `cycles_webhook_events_payload_invalid_total` | `type`, `rule` | Event payload validation discrepancies (no tenant tag — shape issue, not traffic) | +| `cycles_evidence_claimed_total` | `artifact_type` | Evidence source records claimed | +| `cycles_evidence_stored_total` | `artifact_type` | Signed evidence envelopes stored | +| `cycles_evidence_dead_lettered_total` | `artifact_type`, `reason` | Evidence source records moved to dead letter | +| `cycles_evidence_retry_deferred_total` | `artifact_type`, `reason` | Evidence work deferred for retry | +| `cycles_webhook_dispatcher_event_published_total` | `event_type` | Dispatcher lifecycle events published | +| `cycles_webhook_dispatcher_event_deferred_total` | `event_type`, `reason` | Dispatcher lifecycle events deferred | +| `cycles_webhook_dispatcher_event_dead_lettered_total` | `event_type`, `reason` | Dispatcher lifecycle events moved to dead letter | +| `cycles_webhook_security_config_indeterminate_total` | — | Delivery blocked because security configuration could not be determined safely | The `tenant` tag on all counters is gated by `cycles.metrics.tenant-tag.enabled` (default `false` to bound Prometheus cardinality) — set `CYCLES_METRICS_TENANT_TAG_ENABLED=true` to break metrics out per tenant in smaller deployments. -Alert on `cycles_webhook_subscription_auto_disabled_total` (any increase is a receiver health issue) and on a sustained rise in `cycles_webhook_delivery_failed_total{reason=!~"client_4xx"}` (non-client-error failures indicate dispatch issues). +Alert on `cycles_webhook_subscription_auto_disabled_total` (any increase is a receiver health issue) and on a sustained rise in `cycles_webhook_delivery_failed_total{reason!="http_4xx"}` (non-client-error failures indicate dispatch issues). See the [Prometheus Metrics Reference](/how-to/prometheus-metrics-reference) for label values and cardinality guidance. ## Scaling diff --git a/quickstart/how-to-add-hard-budget-limits-to-spring-ai-with-cycles.md b/quickstart/how-to-add-hard-budget-limits-to-spring-ai-with-cycles.md index 5e79d291..99394efb 100644 --- a/quickstart/how-to-add-hard-budget-limits-to-spring-ai-with-cycles.md +++ b/quickstart/how-to-add-hard-budget-limits-to-spring-ai-with-cycles.md @@ -204,9 +204,11 @@ what happened after the work completed Cycles tells you: ::: info -whether the work is allowed to begin, how much room it has, and what it actually consumed afterward +whether the submitted estimate fits the matching budget ledgers, how much room remains, and what the instrumented work commits afterward ::: +Application authorization still decides whether the caller may perform the action; Cycles accounts for the submitted exposure. + That distinction becomes critical in long-running or multi-step systems. Without it, you are often reacting after the expensive part has already happened. @@ -225,7 +227,9 @@ You can begin by enforcing: - per-tenant budget (set the tenant on `CyclesProperties`, or supply a `SubjectResolver` bean that pulls tenant from your authenticated principal) - per-workflow budget -- optional per-run budget +- optional per-run envelope by resolving that run ID into `subjects.workflow` + +`run` is not a separate Cycles scope. The standard hierarchy is `tenant → workspace → app → workflow → agent → toolset`; a run ID stored only in `dimensions` is attribution and does not create an enforceable ledger. Then expand to: @@ -235,19 +239,11 @@ Then expand to: This staged rollout works well because you do not need to boil the ocean on day one. -## Shadow mode first - -Hard enforcement is powerful, but many teams should begin in shadow mode. - -That means: +## Shadow evaluation before enabling the advisors -- estimate and reserve as if policy were active -- observe what would have been allowed or denied -- compare expected vs actual usage -- tune budgets and thresholds -- move to enforcement once the model is calibrated +The current `cycles-spring-ai-starter` advisors implement the live reserve → model call → commit/release lifecycle. They do not expose a shadow-mode property: a Cycles dry-run response deliberately has no `reservation_id`, while the advisor requires one before it invokes the model. -This is especially useful for existing Spring AI applications, where you want to understand normal usage patterns before introducing hard stops. +To evaluate policy before enabling the advisors, make an explicit Cycles reservation request with `dry_run: true` alongside the existing model path, then log the hypothetical decision and the model's actual usage in your application. Dry runs create no reservation, balance mutation, or commit; the current server does emit `reservation.denied` for denied evaluations, but not a complete record of allowed decisions and outcomes. Once those results meet your cutover criteria, enable the starter for live enforcement. See the [shadow-mode rollout guide](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production). ## Handling failure correctly @@ -294,10 +290,10 @@ Start with the actions most likely to create budget surprises. Examples of useful first policies include: - hard cap per tenant -- hard cap per workflow run -- shadow evaluation for new workflows +- hard cap per workflow, with a run ID mapped to `subjects.workflow` when each execution needs its own ledger +- application-side dry-run evaluation before enabling the advisors on new workflows - downgrade path when reservation fails -- operator-configured tool restrictions applied by the integration +- application-configured tool fallback or denial handling - per-workspace limits for staging vs production These are practical controls that map well to real incidents. @@ -315,7 +311,7 @@ It brings: - pre-execution budget checks - retry-safe enforcement - multi-scope budget enforcement -- support for shadow mode and progressive rollout +- live advisor enforcement after separate dry-run calibration - a clean reserve → commit / release lifecycle In other words, it gives Spring AI applications a way to move from “watching usage” to **governing execution**. @@ -325,7 +321,7 @@ In other words, it gives Spring AI applications a way to move from “watching u A simple rollout path looks like this: ### Phase 1: Observe -Instrument model calls and estimate reservations in shadow mode. +Issue explicit `dry_run: true` evaluations beside the existing model calls and retain the responses and actual usage in application telemetry. ### Phase 2: Guard core model usage Add reservation and commit around the most expensive model calls. @@ -334,7 +330,7 @@ Add reservation and commit around the most expensive model calls. Guard tool invocations and side-effecting actions. ### Phase 4: Add hierarchical budgets -Apply policies at tenant, application, workflow, and run scopes. +Apply policies at tenant, app, and workflow scopes. Map a run ID to the workflow subject only when you intentionally want a separate ledger per execution. ### Phase 5: Enforce degradation paths When reservations fail, downgrade or reroute instead of simply crashing. diff --git a/quickstart/how-to-choose-a-first-cycles-rollout-tenant-budgets-run-budgets-or-model-call-guardrails.md b/quickstart/how-to-choose-a-first-cycles-rollout-tenant-budgets-run-budgets-or-model-call-guardrails.md index f6a80f7e..9a7a5334 100644 --- a/quickstart/how-to-choose-a-first-cycles-rollout-tenant-budgets-run-budgets-or-model-call-guardrails.md +++ b/quickstart/how-to-choose-a-first-cycles-rollout-tenant-budgets-run-budgets-or-model-call-guardrails.md @@ -26,12 +26,18 @@ Each is valid. Each solves a different problem. The best choice depends on the failure mode you are trying to prevent first. +::: warning “Run budget” is an integration pattern +Cycles has no native `run` scope. This guide uses **run budget** to mean a standard `workflow` ledger whose `subjects.workflow` value is the run ID. Every protected action in that execution must submit the same workflow subject for the shared envelope to apply. A run ID stored only in `dimensions` adds attribution, not enforcement. + +The ledger bounds only instrumented actions and the exposure the application submits. The host must handle a denial by stopping or degrading the run, and must authorize tools and side effects separately. +::: + ::: tip Cycles provides three runtime-authority pillars - **Spend** — reserve-commit budget enforcement before instrumented LLM calls and tool actions - **Risky actions** — callers can budget assigned `RISK_POINTS`; applications must apply preflight decisions and any configured caps -- **Audit** — reservations, commits, releases, and direct-usage events create lifecycle records; non-persisting preflight decisions need application logging +- **Audit** — live operations create their applicable reservation, balance, and audit records; successful reserve/commit/release/direct-debit paths do not each emit a current runtime Event, and non-persisting preflight decisions need application logging -All three rollouts on this page create budget lifecycle records when they use live reservations and settlement. Dry-run and `decide` responses are non-persisting unless the application logs them. Tenant budgets and run budgets primarily address spend; run budgets also bound risky agent loops; model-call guardrails are the lowest-friction way to start with per-call LLM spend enforcement. +All three rollouts on this page create the applicable live reservation, balance, and audit records when they use reservations and settlement. Dry-run and `decide` create no reservation or balance mutation; the current server emits `reservation.denied` for denied evaluations, but the application must log all responses and external outcomes for a complete record. Tenant budgets and run budgets primarily address spend; run budgets also bound risky agent loops; model-call guardrails are the lowest-friction way to start with per-call LLM spend enforcement. ::: ## The wrong way to start @@ -108,7 +114,7 @@ Tenant budgets are easy to explain. You can say: - each tenant gets a daily, weekly, or monthly envelope -- all governed actions count against that envelope +- all instrumented actions submitted against that ledger and unit count against the envelope - once exhausted, certain actions stop, downgrade, or defer This is intuitive for operators, finance, product, and customer-facing teams. @@ -173,9 +179,9 @@ Run budgets are where “bounded execution” becomes real. ### What run budgets solve well -Run budgets are strong at: +With consistent instrumentation and host-side denial handling, run budgets are strong at: -- stopping runaway loops +- denying further budgeted actions in runaway loops once the ledger lacks room - limiting recursive tool chains - bounding one workflow execution - protecting against local over-consumption @@ -375,9 +381,9 @@ No matter which first rollout you choose, shadow mode is often the safest way to That means: -- evaluate reservations -- observe would-allow and would-deny decisions -- compare estimates with actuals +- send reservation requests with `dry_run: true` +- log would-allow and would-deny responses in the application +- compare estimates with actuals from application telemetry because dry runs cannot be committed - tune thresholds before hard enforcement This is especially useful if you are unsure whether tenant ceilings, run envelopes, or model-level estimates are well calibrated yet. diff --git a/quickstart/mcp-claude-code.md b/quickstart/mcp-claude-code.md index 12d46d71..e20e4077 100644 --- a/quickstart/mcp-claude-code.md +++ b/quickstart/mcp-claude-code.md @@ -5,7 +5,7 @@ description: "Add Cycles budget tools to Claude Code via MCP, with CLI registrat # Add Cycles to Claude Code -This page is the exact setup for [Claude Code](https://www.claude.com/claude-code). For the protocol overview and reserve/commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). +This page is the exact setup for [Claude Code](https://claude.com/product/claude-code). For the protocol overview and reserve-commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). ::: warning MCP availability is not enforcement Registering this MCP server gives Claude Code access to Cycles tools — `cycles_reserve`, `cycles_commit`, `cycles_release`, and balance queries. **MCP is useful for local assistant workflows and discovery. It is not, by itself, a hard runtime control unless the host or tool harness is required to call Cycles before executing the real action.** For dispatch-path enforcement in Claude Code, [Cycles Budget Guard for Claude Code](/how-to/enforcing-budgets-in-claude-code-with-budget-guard) gates non-exempt tools with `PreToolUse` hooks. For other hosts, place the Cycles check in the execution path — SDK wrapper, gateway, or framework adapter. See [Add Cycles with Claude, Codex, Cursor, or Windsurf](/how-to/add-cycles-with-claude-or-codex) for the application-side recipe. @@ -13,7 +13,7 @@ Registering this MCP server gives Claude Code access to Cycles tools — `cycles ## Prerequisites -- **Claude Code installed** ([install guide](https://docs.claude.com/en/docs/claude-code)) +- **Claude Code installed** ([install guide](https://code.claude.com/docs)) - **Node.js 20+ with `npx` available** — Claude Code launches the MCP server through `npx`. - **A Cycles API key** (`cyc_live_...`) — see [API key setup](/quickstart/getting-started-with-the-mcp-server#prerequisites). Skip this for mock mode. - **Cycles server running** locally or remote. Skip for mock mode. diff --git a/quickstart/mcp-claude-desktop.md b/quickstart/mcp-claude-desktop.md index 37ca26c1..17f90823 100644 --- a/quickstart/mcp-claude-desktop.md +++ b/quickstart/mcp-claude-desktop.md @@ -5,7 +5,7 @@ description: "Add Cycles budget tools to Claude Desktop with the recommended des # Add Cycles to Claude Desktop -This page is the exact setup for [Claude Desktop](https://claude.ai/download). For the protocol overview and reserve/commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). +This page is the exact setup for [Claude Desktop](https://claude.com/download). For the protocol overview and reserve-commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). ::: warning MCP availability is not enforcement Registering this MCP server gives Claude Desktop access to Cycles tools — `cycles_reserve`, `cycles_commit`, `cycles_release`, and balance queries. **MCP is useful for local assistant workflows and discovery. It is not, by itself, a hard runtime control unless the host or tool harness is required to call Cycles before executing the real action.** For production, place the Cycles check in the execution path — SDK wrapper, gateway, or framework adapter. See [Add Cycles with Claude, Codex, Cursor, or Windsurf](/how-to/add-cycles-with-claude-or-codex) for the application-side recipe. @@ -13,7 +13,7 @@ Registering this MCP server gives Claude Desktop access to Cycles tools — `cyc ## Prerequisites -- **Claude Desktop installed** ([download](https://claude.ai/download)) +- **Claude Desktop installed** ([download](https://claude.com/download)) - **Node.js 20+** on PATH if you use the manual `npx` configuration below. - **A Cycles API key** (`cyc_live_...`) — see [API key setup](/quickstart/getting-started-with-the-mcp-server#prerequisites). Skip this if you only want to try mock mode below. - **Cycles server running** locally or remote. Skip this for mock mode. diff --git a/quickstart/mcp-cursor.md b/quickstart/mcp-cursor.md index 50252d5b..4bece8a4 100644 --- a/quickstart/mcp-cursor.md +++ b/quickstart/mcp-cursor.md @@ -5,7 +5,7 @@ description: "Add Cycles budget tools to Cursor via MCP, with project and global # Add Cycles to Cursor -This page is the exact setup for [Cursor](https://cursor.com). For the protocol overview and reserve/commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). +This page is the exact setup for [Cursor](https://cursor.com). For the protocol overview and reserve-commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). ::: warning MCP availability is not enforcement Registering this MCP server gives Cursor access to Cycles tools — `cycles_reserve`, `cycles_commit`, `cycles_release`, and balance queries. **MCP is useful for local assistant workflows and discovery. It is not, by itself, a hard runtime control unless the host or tool harness is required to call Cycles before executing the real action.** For production, place the Cycles check in the execution path — SDK wrapper, gateway, or framework adapter. See [Add Cycles with Claude, Codex, Cursor, or Windsurf](/how-to/add-cycles-with-claude-or-codex) for the application-side recipe. diff --git a/quickstart/mcp-windsurf.md b/quickstart/mcp-windsurf.md index 326f599d..018d6c08 100644 --- a/quickstart/mcp-windsurf.md +++ b/quickstart/mcp-windsurf.md @@ -5,7 +5,7 @@ description: "Add Cycles budget tools to legacy Windsurf builds via MCP, with co # Add Cycles to Windsurf / Devin Desktop -[Windsurf has transitioned to Devin Desktop](https://devin.ai/desktop). The configuration paths below apply to legacy Windsurf builds; for current Devin Desktop releases, verify the active MCP configuration location in the app before applying them. For the protocol overview and reserve/commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). +[Windsurf has transitioned to Devin Desktop](https://devin.ai/desktop). The configuration paths below apply to legacy Windsurf builds; for current Devin Desktop releases, verify the active MCP configuration location in the app before applying them. For the protocol overview and reserve-commit lifecycle, see the [umbrella MCP quickstart](/quickstart/getting-started-with-the-mcp-server). ::: warning MCP availability is not enforcement Registering this MCP server gives Windsurf access to Cycles tools — `cycles_reserve`, `cycles_commit`, `cycles_release`, and balance queries. **MCP is useful for local assistant workflows and discovery. It is not, by itself, a hard runtime control unless the host or tool harness is required to call Cycles before executing the real action.** For production, place the Cycles check in the execution path — SDK wrapper, gateway, or framework adapter. See [Add Cycles with Claude, Codex, Cursor, or Windsurf](/how-to/add-cycles-with-claude-or-codex) for the application-side recipe. diff --git a/security.md b/security.md index c92f3573..92d49171 100644 --- a/security.md +++ b/security.md @@ -9,28 +9,28 @@ Cycles is infrastructure that sits in the execution path of autonomous agents. S ## Data residency -All Cycles state lives in Redis. Cycles is currently self-hosted only: Redis runs in your infrastructure, no data leaves your network, and you control the region, the instance type, and the retention policy. +All Cycles state lives in Redis. Cycles is currently self-hosted only: Redis runs in your infrastructure, and you control the region, instance type, and retention policy. Cycles state does not leave your network unless you configure an outbound path such as webhook delivery or your own export pipeline. -Cycles stores budget state — reservation amounts, balances, event records, and tenant configuration. It does not store LLM prompts, responses, or any content from agent interactions. +Cycles stores budget state — reservation amounts, balances, event records, and tenant configuration. It does not require or automatically capture LLM prompts and responses. Callers can still place sensitive content in action names, tags, metadata, or identifiers, so integrations should submit only the context their audit policy permits. A managed cloud offering (runcycles.ai) is planned. It is not yet available. ## Event audit trail -Every budget operation — reservation, commit, release, event — creates a structured record: +Persisted reservations and direct-usage events create structured budget lifecycle data. Commit, release, extend, and expiry update reservation state. Non-persisting `decide` and dry-run evaluations do not create a reservation record, and not every registered event type is emitted. | Field | Description | |---|---| | `reservation_id` / `event_id` | Unique identifier for the operation | -| `subject` | Full scope hierarchy (tenant, workspace, app, workflow, agent, toolset) | -| `action` | What happened (kind, name, tags) | +| `subject` | Caller-supplied scope levels that are present (tenant, workspace, app, workflow, agent, toolset) | +| `action` | Caller-supplied action kind, name, and tags when provided | | `estimate` | Budget locked before execution (reservations) | | `actual` | Usage recorded after execution (commits and events) | | `status` | ACTIVE, COMMITTED, RELEASED, EXPIRED (reservations); APPLIED (events) | -| `metrics` | Operational metadata (tokens, latency, model version) | -| `metadata` | Arbitrary key-value pairs for audit context | +| `metrics` | Caller-supplied operational metadata when provided | +| `metadata` | Caller-supplied key-value context when provided | -Every reservation, commit, release, and event is logged with the scope context needed for audit. The trail answers "which agent spent how much, on what, and when" from the budget ledger alone. +These records can answer which submitted budget scope reserved or settled an amount and when. They do not prove identity-policy authorization, tool arguments, business rationale, or external outcomes. Preserve correlation identifiers and join Cycles lifecycle data to application authorization and execution logs for a complete action record. When configured, CyclesEvidence adds signed, content-addressed receipts for supported protocol decisions. ### Querying the audit trail @@ -104,12 +104,12 @@ See [Webhook Integrations](/how-to/webhook-integrations#signature-verification) Webhook URLs are validated on creation and update to prevent Server-Side Request Forgery: - **HTTPS required** — HTTP URLs are rejected by default (`allow_http: false`) -- **Private IP blocking** — Resolved IPs are checked against private/reserved ranges (loopback, RFC 1918, link-local, IPv6 private). This check is enforced by default. -- **URL pattern allowlisting** — Optional `allowed_url_patterns` restrict accepted URLs to specific domains +- **Private and reserved IP blocking** — the events-service delivery guard always applies a baseline denylist unless its development-only private-network escape hatch is enabled. +- **URL pattern allowlisting** — optional `allowed_url_patterns` narrows accepted URLs; it does not override the delivery-time IP denylist. -Default blocked CIDRs: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, `::1/128`, `fc00::/7` +Delivery-time baseline CIDRs: `0.0.0.0/8`, `10.0.0.0/8`, `100.64.0.0/10`, `127.0.0.0/8`, `169.254.0.0/16`, `172.16.0.0/12`, `192.168.0.0/16`, `::1/128`, `fe80::/10`, and `fc00::/7`. Any-local and unspecified addresses are also rejected. -The blocked-CIDR list is admin-configurable via `GET/PUT /v1/admin/config/webhook-security`. The spec warns that removing blocked CIDR ranges may expose the server to SSRF attacks — treat the default list as a floor, not a suggestion. See the [Admin API Guide](/admin-api/guide#pillar-4-events-webhooks-v0-1-25) for examples. +Admin-configured blocked CIDRs are additive at delivery time. Clearing them does not remove the events service's baseline. Local development requires both admin `allow_http: true` and `WEBHOOK_URL_GUARD_ALLOW_PRIVATE_NETWORKS=true` on the events service; never enable that escape hatch in production. See the [Admin API Guide](/admin-api/guide#pillar-4-events-webhooks-v0-1-25) for examples. ### Signing secret encryption at rest diff --git a/troubleshoot/llm-cost-spike-debugging.md b/troubleshoot/llm-cost-spike-debugging.md index a5c12beb..37d977c5 100644 --- a/troubleshoot/llm-cost-spike-debugging.md +++ b/troubleshoot/llm-cost-spike-debugging.md @@ -68,7 +68,7 @@ Diagnosis after the fact is reactive. The class of problem is "the application e - **Per-tenant budgets.** A noisy tenant cannot consume cross-tenant headroom because their own ledger is exhausted. Other tenants are unaffected. See [Multi-tenant SaaS guide](/how-to/multi-tenant-saas-with-cycles). - **Per-action authority.** Cap not just total spend but per-call cost (e.g., max input tokens, max output tokens, allowed models). Prevents the model-upgrade regression by *policy* — code that asks for a model not on the allow-list is denied. See [Action authority](/concepts/action-authority-controlling-what-agents-do). - **Atomic reservations.** Solves the concurrent-agent case where ten parallel runs all believe there is enough budget. See [Concurrent agent overspend](/incidents/concurrent-agent-overspend). -- **Run-level budgets.** Each agent run gets a fixed per-run budget. Loops self-terminate when the budget is exhausted, even if the per-tenant budget is fine. See [How to model tenant, workflow, and run budgets](/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles). +- **Per-run workflow ledgers.** Give each agent run a unique workflow value and require every protected step to reserve against it. When the ledger is exhausted, the next live reservation fails; the host must stop or degrade the loop. See [How to model tenant, workflow, and run budgets](/how-to/how-to-model-tenant-workflow-and-run-budgets-in-cycles). A reactive cost-spike playbook is necessary. A reactive *only* posture is not — the fix is structurally eliminating the class. diff --git a/why-cycles.md b/why-cycles.md index c62594b9..934e4b49 100644 --- a/why-cycles.md +++ b/why-cycles.md @@ -7,24 +7,24 @@ description: "For teams shipping AI agents to customers or internally — blast- **Start with the problem that matters to you:** -- [Stop agents from burning your API budget overnight](/why-cycles/cost-control) — the $4,200 overnight incident +- [Stop agents from burning your API budget overnight](/why-cycles/cost-control) — a 240-iteration runaway-loop scenario - [Block the 201st email before it sends](/why-cycles/action-authority) — when the damage isn't cost, it's consequence - [One customer's runaway shouldn't affect your other 500](/why-cycles/multi-tenant) — per-tenant isolation for SaaS - [Prove to an auditor that your agents are under control](/why-cycles/governance) — auditable enforcement for compliance --- -If you're deploying AI agents — to customers or inside the enterprise — Cycles is the [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) layer that enforces hard limits on spend and actions before every LLM call, tool invocation, and side effect. Per-tenant, per-workflow, per-run. So one runaway agent never blows through another's budget, your feature margin stays predictable, and every action is auditable. +If you're deploying AI agents — to customers or inside the enterprise — Cycles is a [runtime authority](/blog/what-is-runtime-authority-for-ai-agents) layer for hard limits on spend and caller-assigned action exposure. Put a required reserve-commit boundary before each protected LLM call, tool invocation, or side effect, then use standard scopes such as tenant and workflow; a workflow value can be unique per run. Cycles accounts for the amounts and context the host submits; tool authorization and complete action auditing remain application responsibilities. ## What Cycles solves **Protect margin.** Agent costs follow a heavy-tail distribution — the top 10% of users consume [72% of total spend](/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin). Without per-user budget caps, a feature priced for 80% gross margin [delivers 23%](/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin). Cycles bounds the tail so unit economics stay predictable. -**Contain cross-tenant blast radius.** A single runaway agent can burn [$4,200 in three hours](/blog/ai-agent-failures-budget-controls-prevent). Cycles enforces hierarchical budgets — tenant, workspace, workflow, run — so one customer's bad agent cannot starve the platform or another customer's allocation. +**Contain cross-tenant budget impact.** In one [illustrative three-hour loop](/blog/ai-agent-failures-budget-controls-prevent), 240 iterations cost $52.80 under the stated token assumptions. Correctly provisioned tenant and workflow budgets can keep one customer's submitted usage from consuming another customer's allocation. -**Audit every action.** Every reservation, commit, and event creates a structured record with full scope context. Queryable via API, 90-day hot retention, exportable to cold storage. No log reconstruction required — the budget ledger is the audit trail. When an auditor asks "which agent did what, when, and who authorized it," the answer is a single API query — not a week of log reconstruction. [Details →](/security) +**Audit budget decisions and settlement.** Reservation state, audit rows, and implemented emitted events create structured records for the budget lifecycle, including caller-supplied scope and metadata where the relevant schema carries them. The event store has configurable retention (90 days by default), and callers can export records to their own long-term storage. Propagated trace IDs can link Cycles requests to application logs for tool arguments, authorization, and outcomes; successful reservation operations do not each emit a runtime Event. [Details →](/security) -**Gate high-consequence actions.** A support agent [sent 200 collections emails instead of welcome emails](/blog/ai-agent-action-control-hard-limits-side-effects). Total model spend: $1.40. Business impact: $50K+ in lost pipeline. No spending limit would have caught it. Cycles supports [RISK_POINTS](/concepts/action-authority-controlling-what-agents-do) — budgets denominated in blast radius, not dollars — so agents can read and reason freely while dangerous capabilities (email, deploy, delete) are gated separately. +**Bound high-consequence actions.** In an [illustrative scenario](/blog/ai-agent-action-control-hard-limits-side-effects), 200 mistaken emails have about $1.40 in modeled token spend but potentially much larger, unquantified business impact. A monetary limit calibrated to tokens may not catch that. A host can authorize actions, classify them in [RISK_POINTS](/concepts/action-authority-controlling-what-agents-do), and submit those amounts to a separate Cycles budget. The host still authorizes tools and arguments; Cycles meters the exposure it submits. ## Where Cycles fits @@ -32,21 +32,21 @@ If you're deploying AI agents — to customers or inside the enterprise — Cycl ## Why now -Regulatory frameworks are converging on a single requirement: if your AI system acts autonomously, you must be able to prove what it did, why it was allowed to do it, and how you would have stopped it. The EU AI Act's Article 50 transparency obligations and GPAI enforcement apply from August 2, 2026; the June 2026 Digital Omnibus moved Annex III high-risk obligations to December 2, 2027 ([what actually happens in August →](/blog/eu-ai-act-what-actually-happens-august-2-2026)). NIST launched its [AI Agent Standards Initiative](https://www.nist.gov/news-events/news/2026/02/announcing-ai-agent-standards-initiative-interoperable-and-secure) in February 2026. Organizations can already pursue [ISO 42001 certification](https://www.iso.org/standard/81230.html). The window between "we should govern our agents" and "we must prove we govern our agents" is closing. [Full regulatory mapping →](/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement) +Regulatory and standards programs impose different, scope-dependent obligations; none mandates Cycles or runtime budgets specifically. EU AI Act Article 50 transparency rules and Commission enforcement powers for GPAI are scheduled to apply from August 2, 2026, while the 2026 AI Omnibus moved the Annex III high-risk-system timeline to December 2, 2027 ([what actually happens in August →](/blog/eu-ai-act-what-actually-happens-august-2-2026)). NIST launched its voluntary [AI Agent Standards Initiative](https://www.nist.gov/news-events/news/2026/02/announcing-ai-agent-standards-initiative-interoperable-and-secure) in February 2026. Organizations can already pursue [ISO/IEC 42001 certification](https://www.iso.org/standard/42001). Teams should map the rules that apply to their role and use case, then select technical and organizational controls that produce the required evidence. [Full regulatory mapping →](/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement) ## By role -**For engineering:** A single runaway agent burned [$4,200 in three hours](/blog/ai-agent-failures-budget-controls-prevent). With a $15 per-run budget in Cycles, the same agent stops after 8 iterations. -**For security/compliance:** Every reservation, commit, and event creates a structured, queryable audit record with full scope context. +**For engineering:** A modeled loop reached [$52.80 over 240 iterations](/blog/ai-agent-failures-budget-controls-prevent). Under its flat-average assumptions, a mandatory $15 run budget rejects a protected call around iteration 68. +**For security/compliance:** Budget operations and implemented emitted events create structured records; propagated trace context connects them to application authorization and outcome logs. **For finance:** Per-user budget caps turned a [23% gross margin into 68%](/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin) in one analysis, with only 5% of users hitting the limit. -**For the AI agent itself:** Visible constraints earn trust — teams that see agents self-regulate respond by increasing budgets and granting access to higher-risk tools. +**For the AI agent itself:** Visible constraints give operators evidence they can use when deciding whether to increase budgets or reduce manual gates.
Engineering — Contain blast radius, protect margins -Every agent action passes through a reserve-commit gate. Before an LLM call executes, Cycles atomically checks the budget and locks the estimated cost. If the budget is exhausted, the call is denied and the agent degrades gracefully — cheaper model, shorter response, or explicit stop. +Every action the host instruments passes through a reserve-commit gate. Before an LLM call executes, Cycles atomically checks the submitted budget scope and locks the estimated cost. If the reservation is rejected, the host must skip the call and can degrade gracefully — cheaper model, shorter response, or explicit stop. -Without this gate, a single runaway agent can burn **$4,200 in three hours** — a coding agent hit an ambiguous error, retried with expanding context windows, and [looped 240 times before anyone noticed](/blog/ai-agent-failures-budget-controls-prevent). With a $15 per-run budget in Cycles, the same agent stops after 8 iterations and surfaces the problem immediately. +Without this gate, a coding agent in the [illustrative source scenario](/blog/ai-agent-failures-budget-controls-prevent) loops 240 times over three hours and spends $52.80 at the stated flat-average token rates. With mandatory coverage and a $15 run budget, a protected call is rejected around iteration 68 under those same assumptions. Blast radius is bounded at every level: per-run, per-workflow, per-tenant. One bad agent cannot starve the platform. Budgets are hierarchical — tenant, workspace, app, workflow, agent — so you set ceilings at the level that matches your architecture. @@ -59,11 +59,11 @@ Cost is the first dimension. [Action authority](/concepts/action-authority-contr
-Security / Compliance — Every action is auditable +Security / Compliance — Budget operations are auditable -Every reservation, commit, release, and event in Cycles creates a structured, queryable record. Each record includes: full scope hierarchy (tenant, workspace, app, workflow, agent, toolset), amounts reserved and committed, timestamp, status, and arbitrary metadata. +Cycles stores reservation lifecycle records and exposes emitted runtime events as structured, queryable data. Depending on the record and request, fields can include the submitted scope hierarchy, reserved or committed amounts, timestamps, status, correlation identifiers, and metadata. -This means every budget operation — every reservation, commit, release, and event — is logged with the context needed for audit. You can answer "which agent spent how much, on what, and when" from the event log alone, without reconstructing it from scattered application logs. +This supports questions such as "which submitted scope spent how much, and when." It is not a complete record of what a tool did or why application policy authorized it. Propagate the same trace context across related Cycles requests, retain reservation IDs, and add application correlation keys to your own logs so you can join authorization, tool-call, and outcome evidence. The event log is queryable via the REST API. Hot retention defaults to 90 days (configurable via `EVENT_TTL_DAYS`); export events via the API to your own cold storage (e.g. S3, GCS) for long-term compliance — there is no built-in exporter. The admin server records audit logs for all administrative operations — API key creation, tenant changes, budget modifications. @@ -78,11 +78,11 @@ Cycles is self-hosted open source today, so all data stays in your infrastructur
Finance — From unpredictable spend to bounded unit economics -In one [real deployment](/blog/how-much-do-ai-agents-cost), a team estimated $800/month for a customer support agent based on prototype traffic. The first production invoice was $4,200. The per-token pricing was exactly right — the call volume was not. Agents averaged 11 LLM calls per conversation instead of the 3 assumed in the estimate. Retries doubled call counts on bad days. +An [illustrative workload model](/blog/how-much-do-ai-agents-cost) compares an $800/month prototype estimate with a $4,200/month production-shaped scenario driven by 11 calls per conversation, growing context, and retries. It is a calculator scenario, not a verified customer invoice. This is the norm. In a [unit economics analysis](/blog/ai-agent-unit-economics-cost-per-conversation-per-user-margin) of an AI copilot feature, the top 10% of users consumed 72% of total spend. One user triggered 340 conversations in a month and cost $310 alone — wiping out the margin from 50+ light users. -Cycles bounds this tail. In the same analysis, a $15/month per-user cap turned a 23% gross margin into 68%, with only 5% of users ever hitting the limit. A $15 per-run cap prevents the [$4,200 tool loop](/blog/ai-agent-failures-budget-controls-prevent) entirely — the agent stops at $15 and surfaces the problem for human review. +Cycles can bound this tail on mandatory paths. In the unit-economics model, a $15/month per-user cap changes modeled gross margin from 23% to 68%, with 5% of modeled users hitting the limit. In the separate loop scenario, a $15 run budget bounds protected spend near $15 when estimates and settlement are accurate. Budget enforcement is not a cost center. It is the mechanism that makes AI feature unit economics predictable. @@ -91,7 +91,7 @@ Budget enforcement is not a cost center. It is the mechanism that makes AI featu
AI Agent — You get a budget, not just a limit -Your SDK tells you what each call cost — after it happened. You could track that locally. But a local counter breaks when 20 of you run concurrently against the same customer budget — you all read "remaining: $500" and all proceed. If you crash, your local state is gone. And you can't see the tenant's allocation, the workflow's run budget, or the workspace capacity. That context lives outside you. This isn't a gap you close with [more tools](/blog/vibe-coding-budget-wrapper-vs-budget-authority) — it requires a shared authority. +Your SDK tells you what each call cost — after it happened. You could track that locally. But a local counter breaks when 20 of you run concurrently against the same customer budget — you all read "remaining: $500" and all proceed. If you crash, your local state is gone. And you cannot see the tenant ledger, a workflow ledger keyed to the run, or workspace capacity. That context lives outside you. This isn't a gap you close with [more tools](/blog/vibe-coding-budget-wrapper-vs-budget-authority) — it requires a shared authority. Cycles is that authority. Call `cycles_check_balance` before you plan. Call `cycles_reserve` before each costly step. You get back: @@ -107,15 +107,15 @@ This is the difference between being killed mid-action and [choosing how to wind Without visible self-regulation, teams restrict you. They add human-in-the-loop approval steps, limit your tool access, reduce your context window, cap your iterations at 5. They do this because they have no evidence you'll stop on your own. -Cycles gives them that evidence. Every reservation is a record that you checked before acting. Every commit is proof you reported actual usage. Every DENY you handled gracefully — winding down, explaining what's left — is a signal that you can be trusted with more. Teams that see agents self-regulate within bounds respond by increasing budgets, granting access to higher-risk tools, and removing manual gates. Constraint you can demonstrate is the path to autonomy you earn. +Cycles contributes that evidence. Every reservation records that the integration checked the submitted budget before acting. Every commit records reported usage. A clean response to a rejection — winding down and explaining what remains — can help operators evaluate whether a larger budget or fewer manual gates are appropriate. Those changes remain human policy decisions, not an automatic consequence of using Cycles. ### What about latency? -A full reserve+commit cycle adds [~15ms](/blog/cycles-server-performance-benchmarks) end-to-end (p50; ~11ms per lifecycle under 32-thread concurrency). A typical LLM call takes 500ms–30s. Budget enforcement adds less time than the variance in your provider's response latency. You won't notice it. +In the published benchmark environment, a full reserve-commit cycle added [~15ms](/blog/cycles-server-performance-benchmarks) end-to-end at p50 (~11ms per lifecycle under 32-thread concurrency). That is small relative to many LLM calls, but network, datastore, and load characteristics vary. Measure the added latency in your deployment. ### What if the budget is set too low? -This is real — a budget of $0.50 on a task that needs $5 means you get DENY on step 3. But without Cycles, you'd discover the mismatch after spending $5 (or $50, or $4,200). With Cycles, you discover it at $0.50 and can tell the user: "Budget exhausted after 3 steps. This task needs a larger allocation." That's better for both of you. And teams can [calibrate budgets with shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) — running enforcement in dry-run against real traffic before turning it on. +This mismatch is possible — a budget of $0.50 on a task that needs $5 can reject a later live reservation. Without a mandatory budget boundary, the host may discover the mismatch only after spending much more. With one, it can stop near the configured allocation and tell the user that the task needs a larger budget. Teams can [calibrate budgets with shadow mode](/how-to/shadow-mode-in-cycles-how-to-roll-out-budget-enforcement-without-breaking-production) by collecting and analyzing dry-run responses before turning enforcement on. ### What if your estimates are wrong? diff --git a/why-cycles/action-authority.md b/why-cycles/action-authority.md index b890cb79..36938e8c 100644 --- a/why-cycles/action-authority.md +++ b/why-cycles/action-authority.md @@ -1,11 +1,11 @@ --- title: "Block the 201st Email Before It Sends" -description: "A support agent sent 200 collections emails instead of welcome emails. Total model spend: $1.40. Business impact: $50K+ in lost pipeline. No spending limit would have caught it." +description: "Illustrative scenario: 200 mistaken emails have low token cost and high potential impact. Combine application authorization with RISK_POINTS budgets safely." --- # Block the 201st Email Before It Sends -A support agent was supposed to send welcome emails to new signups. A prompt regression changed "welcome" to "final payment reminder." The agent [sent 200 collections emails to new customers](/blog/ai-agent-action-control-hard-limits-side-effects). Total model spend: $1.40. Business impact: $50K+ in lost pipeline. +Consider an [illustrative support-agent scenario](/blog/ai-agent-action-control-hard-limits-side-effects): a template regression turns welcome messages into payment reminders, and the host sends 200 emails. The modeled token cost is about $1.40; customer and business impact are potentially much larger but not quantified by source data. No spending limit would have caught this. The LLM calls were cheap. The damage was in what the agent *did*, not what it *spent*. @@ -44,16 +44,16 @@ hooks = CyclesRunHooks( ) ``` -In the original incident, the agent sent 200 emails unchecked. With risk points, you decide how many is too many. A budget of 200 risk points with 50 points per email means the agent can send 4 emails before it's denied. A budget of 10,000 points with 50 per email caps it at 200 — and blocks email #201 before it executes. +In the constructed scenario, the host sends 200 emails unchecked. With risk points, you decide how much cumulative exposure is too much. If the host requires a successful reservation for every email, a budget of 200 risk points with 50 points per email permits 4 reservations; the fifth is rejected. A budget of 10,000 points permits 200 and rejects the reservation for email #201. -The point isn't the specific number. It's that **every action is gated before execution** — not logged after the damage is done. +The point isn't the specific number. It is that **every protected action the host classifies and routes through the boundary is budgeted before execution** — not merely logged after the damage is done. Cycles does not infer risk or replace the host's tool and argument authorization. ## What happens now -- **Safe actions are free.** Reading, searching, and reasoning cost zero risk points. The agent works normally for everything that doesn't have consequence. -- **Dangerous actions are gated.** Each email, deployment, or write operation consumes risk points. The agent's available actions shrink as it uses them. -- **The agent degrades, not crashes.** When email authority runs out, the agent can queue the remaining emails for human review instead of stopping entirely. -- **Per-agent isolation in multi-agent systems.** The researcher agent gets unlimited search but zero email authority. The executor agent gets tool authority but limited LLM budget. A bug in one can't trigger the other's capabilities. +- **Low-exposure actions can reserve zero.** Reading, searching, and reasoning can use a zero risk estimate while remaining subject to the application's authorization policy. +- **Higher-exposure actions consume a separate budget.** Each instrumented email, deployment, or write operation reserves the amount assigned by the host. +- **The agent can degrade instead of crashing.** When the risk budget is exhausted, the host can queue remaining emails for human review instead of stopping the entire workflow. +- **Scopes support isolation in multi-agent systems.** Give agents distinct budget scopes, and separately give the researcher no email credential or tool permission. Cycles bounds submitted usage within those scopes; the orchestrator prevents one agent from invoking another's capabilities. ## Cost and consequence together @@ -77,7 +77,7 @@ Each action checks its own unit's budget. The LLM call draws from the dollar bud ## Now run the numbers for your agent -The blast-radius calculator below is pre-seeded with a support-bot scenario where the LLM cost is negligible but the action damage is six figures. Rename the agent, edit the action rows, and dial up the **Cycles containment** slider to see what runtime action authority is worth for *your* workload. Click **Share** to send the configured view; **PNG** to attach to a deck or follow-up email. +The blast-radius calculator below is pre-seeded with an illustrative support-bot scenario where the LLM cost is negligible but the modeled action damage is six figures. Rename the agent, edit the action rows, and set the containment slider to an assumption supported by your actual application authorization and budget boundary. Click **Share** to send the configured view; **PNG** to attach to a deck or follow-up email. @@ -87,5 +87,5 @@ The blast-radius calculator below is pre-seeded with a support-bot scenario wher - [Action Authority: Controlling What Agents Do](/concepts/action-authority-controlling-what-agents-do) — the conceptual foundation - [Understanding Units](/protocol/understanding-units-in-cycles-usd-microcents-tokens-credits-and-risk-points) — USD_MICROCENTS, TOKENS, CREDITS, RISK_POINTS - [OpenAI Agents SDK Integration](/how-to/integrating-cycles-with-openai-agents) — ToolEstimateMap and per-tool governance -- [Beyond Budget: Action Authority](/blog/beyond-budget-how-cycles-controls-agent-actions) — real scenarios and multi-agent patterns +- [How Cycles Meters Caller-Assigned Action Exposure](/blog/beyond-budget-how-cycles-controls-agent-actions) — metering patterns composed with host action authorization - [5 Failures Only Action Controls Would Prevent](/blog/ai-agent-action-failures-runtime-authority-prevents) — incidents where spend was negligible diff --git a/why-cycles/cost-control.md b/why-cycles/cost-control.md index a1e5ff5c..332812ab 100644 --- a/why-cycles/cost-control.md +++ b/why-cycles/cost-control.md @@ -1,17 +1,17 @@ --- title: "Stop Agents from Burning Your API Budget Overnight" -description: "A coding agent hit an ambiguous error, retried with expanding context windows, and looped 240 times. Total cost: $4,200. With Cycles, the same agent stops at $15." +description: "An illustrative coding-agent loop runs 240 iterations and costs $52.80 at stated token rates. A $15 budget rejects a protected call around iteration 68." --- # Stop Agents from Burning Your API Budget Overnight -A coding agent hit an ambiguous error. It retried with an expanding context window. Each retry cost more than the last. By the time someone checked the dashboard the next morning, the agent had looped [240 times and spent $4,200](/blog/ai-agent-failures-budget-controls-prevent). +In an [illustrative coding-agent scenario](/blog/ai-agent-failures-budget-controls-prevent), an ambiguous error leads to 240 iterations over three hours. At the stated flat averages and model rates, the loop costs $52.80. The model pricing was exactly right. The call volume was not. ## Why existing controls didn't help -**Provider spending caps** are typically monthly and org-wide. They don't distinguish between your production agent and your staging test. By the time the monthly cap kicks in, the damage is done — and it blocks every other agent on the account too. +**Provider cost controls** vary across soft project budgets, prepaid credits, account limits, and throughput quotas. They can isolate traffic when you assign separate provider identities, but a shared project or key does not infer your production run, staging test, or application tenant. **Rate limits** control how fast, not how much. The agent stayed within its requests-per-second limit. It was making perfectly well-formed API calls. Just 240 of them. @@ -30,30 +30,30 @@ def call_llm(prompt: str) -> str: ).choices[0].message.content ``` -That's it. Before every LLM call, the `@cycles` decorator reserves budget. If the budget is exhausted, `BudgetExceededError` is raised and the model is never called. No tokens consumed. No cost incurred. +For this decorated function, the wrapper reserves budget before invoking the model. If the live reservation fails for insufficient budget, it raises `BudgetExceededError` and does not call the wrapped model. Other call paths remain unprotected unless they use the same mandatory boundary. -The same agent with a $15 per-run budget stops after 8 iterations and surfaces the problem immediately: "Budget exhausted. This task needs human review." +Under the source scenario's flat-average assumptions, a $15 workflow ledger keyed to that run rejects a protected call around iteration 68. The host can surface: "Budget exhausted. This task needs human review." ## What happens now -- **Budget checked before every call.** The agent can't overspend — the reservation is denied before the API call executes. +- **Budget checked before every decorated call.** The wrapped model call does not execute when its live reservation fails; complete path coverage, estimate accuracy, settlement, and overage policy still matter. - **Graceful degradation, not a crash.** The agent can catch `BudgetExceededError` and wind down: summarize progress, switch to a cheaper model, or queue the task for later. -- **Per-run isolation.** Each agent run has its own budget. A runaway in run #47 can't affect run #48 or another customer's allocation. -- **You find out at $15, not $4,200.** The budget limit surfaces the problem immediately instead of letting it compound overnight. +- **Run-scope isolation.** If the application provisions distinct budget subjects for each run and routes every protected call correctly, a runaway in run #47 cannot consume run #48's allocation. +- **The configured bound surfaces sooner.** A $15 limit rejects a later protected call instead of allowing all 240 modeled iterations. ## The math | | Without Cycles | With Cycles ($15/run cap) | |---|---|---| -| Agent loops | 240 | 8 | -| Cost | $4,200 | $15 | +| Agent loops | 240 | About 68 under flat-average assumptions | +| Cost | $52.80 | Near $15, subject to estimates and settlement | | Time to detect | Next morning | Immediately | -| Impact on other agents | All blocked by provider cap | None — per-run isolation | +| Impact on other agents | Depends on the shared provider cap | Contained when scopes and routing are configured correctly | | Recovery action | Post-mortem and budget reset | Fix the prompt | ## Now run the numbers for your workload -The calculator below is pre-seeded with a *similar* retry-loop profile — 200K input tokens per call by the time someone notices, 240 calls. The exact $4,200 in the story above depends on context-window growth across retries that no static calculator captures perfectly; the **shape** of the cost curve is what the budget gate actually bounds. Adjust the input/output tokens, calls/day, and model rates to match your own incident. Click **Share** to send the configured view to a teammate, or **PNG** for an artifact you can paste into a deck. +The calculator below is pre-seeded with a separate, larger-context retry-loop profile — 200K input tokens and 10K output tokens per call, 240 calls. It is not the source scenario's input. Adjust the tokens, call count, and model rates to match your workload. Click **Share** to send the configured view to a teammate, or **PNG** for an artifact you can paste into a deck. @@ -63,5 +63,5 @@ The calculator below is pre-seeded with a *similar* retry-loop profile — 200K - [End-to-End Tutorial](/quickstart/end-to-end-tutorial) — zero to budget-guarded LLM call in 10 minutes - [Cost Estimation Cheat Sheet](/how-to/cost-estimation-cheat-sheet) — how much to reserve per model - [Degradation Paths](/how-to/how-to-think-about-degradation-paths-in-cycles-deny-downgrade-disable-or-defer) — what to do when budget runs out -- [5 Failures Budget Controls Would Prevent](/blog/ai-agent-failures-budget-controls-prevent) — more incidents with dollar math +- [5 Agent Cost Failures Runtime Budgets Can Bound](/blog/ai-agent-failures-budget-controls-prevent) — illustrative scenarios with checked dollar math - [Why Rate Limits Are Not Enough](/concepts/why-rate-limits-are-not-enough-for-autonomous-systems) — the deeper argument diff --git a/why-cycles/governance.md b/why-cycles/governance.md index 73452449..1ff48f12 100644 --- a/why-cycles/governance.md +++ b/why-cycles/governance.md @@ -13,7 +13,7 @@ You don't have an answer. The dashboard shows what happened. It does not prevent **Monitoring dashboards are post-hoc.** They record what happened — after it happened. For AI agents that qualify as high-risk AI systems, the EU AI Act's [Article 14](/blog/ai-agent-governance-framework-nist-eu-ai-act-iso-42001-owasp-runtime-enforcement) requires human oversight, including the ability to intervene in or interrupt the system's operation. A dashboard that shows a cost spike at 2 AM is not, by itself, a runtime interruption mechanism. -**Provider spending caps are fragmented and have no audit trail.** A single agent workflow can span multiple providers — OpenAI for reasoning, Anthropic for code generation, Google for search, plus external APIs for tools. Each provider has its own spending cap, but no single cap sees the total workflow cost. When OpenAI's monthly limit fires, it blocks the entire organization — while spend on Anthropic and Google continues unchecked. There is no record of which tenant, which workflow, or which agent triggered the limit. An auditor cannot trace a spending event to a responsible scope — because each provider's cap only sees its own slice. +**Provider records are fragmented across vendor boundaries.** A single workflow can span OpenAI for reasoning, Anthropic for code generation, Google for search, and external tool APIs. Each provider records the identities and traffic it sees; no provider automatically reconstructs the workflow's cross-provider application scope or its external actions. Attribution improves when applications use separate projects, workspaces, keys, and trace metadata, but complete workflow evidence still requires correlation outside any one provider. **Prompt-level guardrails are suggestions, not enforcement.** A system prompt that says "do not send more than 10 emails" is an instruction to a probabilistic model. It is not a control. An auditor asks: "Can the agent violate this rule?" If the answer is "yes, if the model decides to," it is not an auditable control point. diff --git a/why-cycles/multi-tenant.md b/why-cycles/multi-tenant.md index f4e1f18f..e827d9d6 100644 --- a/why-cycles/multi-tenant.md +++ b/why-cycles/multi-tenant.md @@ -1,11 +1,11 @@ --- title: "One Customer's Runaway Agent Shouldn't Affect Your Other 500" -description: "In a multi-tenant AI SaaS, one customer's agent loop can exhaust shared API budgets, starve other tenants, and blow through your gross margin. Cycles isolates every customer." +description: "Use mandatory tenant-scoped Cycles budgets to contain covered agent spend per customer, with correct provisioning, routing, estimates, and settlement behavior." --- # One Customer's Runaway Agent Shouldn't Affect Your Other 500 -You ship an AI copilot to 500 customers. Customer #47 discovers a prompt that triggers a research loop. Their agent makes 3,000 LLM calls in an hour — well within the prompt's intent, but 50x the average session. +Consider an illustrative AI copilot serving 500 customers. Customer #47 triggers a research loop whose agent makes 3,000 LLM calls in an hour — 50 times the modeled average session. Without per-tenant isolation, Customer #47's session burns through the shared API budget. Your provider's org-wide spending cap kicks in and blocks **every customer** — including the 499 who did nothing wrong. Your status page goes red. Support tickets flood in. The incident post-mortem reveals a $2,800 bill for one tenant's session. @@ -13,7 +13,7 @@ This isn't a scaling problem. It's an isolation problem. ## Why shared controls fail for multi-tenant -**Provider spending caps are org-wide.** Your provider's monthly limit doesn't know which of your 500 customers triggered the spend. When it fires, it blocks all of them. +**Provider identities may not match your tenants.** Project, workspace, and key controls can help, but a provider does not infer which of 500 application customers caused spend behind a shared identity. A hard provider stop at that shared scope can affect all of them. **Rate limits are per-key, not per-tenant.** If all customers share an API key (common in SaaS), one customer's burst consumes the rate limit for everyone. If each customer has their own key, you're managing 500 API keys at the provider level — and still have no budget enforcement. @@ -21,7 +21,7 @@ This isn't a scaling problem. It's an isolation problem. ## How Cycles fixes it -Each customer maps to a Cycles tenant. Each tenant has its own budget, its own API key, and its own scope hierarchy — enforced atomically by the protocol. +An application can map each customer to a Cycles tenant, provision separate budgets and API keys, and submit the correct tenant subject on every protected call. The protocol atomically enforces applicable budget scopes; the application remains responsible for secure tenant derivation and complete routing. ```python # Your onboarding logic (see Multi-Tenant SaaS Guide for full implementation) @@ -39,28 +39,28 @@ async def handle_chat(request: Request, prompt: str) -> str: ... ``` -When Customer #47's agent hits $50, their next reservation is denied. Customer #48 through #500 are unaffected — their budgets are independent. No shared caps. No cross-tenant interference. +When Customer #47's submitted spend reaches $50, the next over-budget live reservation returns an error. If every protected path uses the correct tenant scope, Customer #48 through #500 retain their separate Cycles allocations. The upstream provider account may still be shared, so provider-wide limits remain a separate dependency. ## What happens now -- **Blast radius contained.** One customer's runaway agent can only burn their own budget. Other tenants continue operating normally. +- **Covered budget impact contained.** Correctly scoped mandatory paths cannot consume another tenant's Cycles allocation. - **Per-customer limits map to plan tiers.** Free: $5/month. Pro: $50/month. Enterprise: $500/month. The budget authority enforces what the billing system promises. - **Concurrency safe.** Atomic reservations prevent the classic race condition where 20 parallel agents all read "budget available" and all proceed. Cycles locks the budget before execution. -- **Graceful degradation per tenant.** When Customer #47 hits their limit, their agent can downgrade to a cheaper model, show an upgrade prompt, or queue work for later — while every other tenant continues at full quality. +- **Graceful degradation per tenant.** When a live reservation fails, the host can downgrade the model, show an upgrade prompt, or queue work for later. ## The math | | Shared budget | Per-tenant with Cycles | |---|---|---| | Customer #47's session | $2,800 from shared pool | $50 from their own budget | -| Impact on other customers | All blocked by provider cap | None | -| Time to detect | When provider cap fires | Immediately (reservation denied) | +| Impact on other customers | Shared provider identity can affect all tenants when its hard boundary binds | Separate Cycles allocations remain; provider-wide dependencies still apply | +| Time to detect | Depends on provider alert or cutoff semantics | At the first rejected over-budget live reservation | | Recovery | Manually increase cap, apologize to 499 customers | Customer #47 sees upgrade prompt | -| Gross margin | Unpredictable — one tenant can destroy it | Bounded per tenant | +| Covered gross-margin exposure | Shared across tenants | Submitted estimates bounded per tenant on mandatory paths | ## Beyond budget: per-tenant action authority -The same isolation applies to actions. Customer #47's agent can send 10 emails. Customer #48's agent can send 10 emails. They can't share, borrow, or exhaust each other's action authority — even if both agents run on the same infrastructure. +The same scope isolation can apply to caller-assigned action exposure. If the host authorizes an email, assigns it a risk amount, and reserves against the correct tenant, one tenant cannot consume another tenant's risk-point budget. Tool permissions, recipient validation, and exact action counts remain application concerns. ``` Customer #47 (Pro plan)