Skip to content

Architecture Overview: How Cycles Fits Together

Cycles is a budget authority for autonomous execution. It sits between your application and the actions that cost money or carry risk.

This page describes the components, how they interact, and where each piece runs.

System overview

┌──────────────────────────────────────┐
│         Your Application             │
│                                      │
│  ┌──────────────┐  ┌──────────────┐  │
│  │  @Cycles     │  │ CyclesClient │  │
│  │  annotation  │  │   (direct)   │  │
│  └──────┬───────┘  └─────┬────────┘  │
│         │                │           │
│         ▼                ▼           │
│  ┌──────────────────────────────┐    │
│  │ Java Spring, Other bindings  │    │
│  │     (Cycles Wire Protocol)   │    │
│  └──────────────┬───────────────┘    │
└─────────────────┼────────────────────┘
                  │ HTTP (JSON)
                  │ X-Cycles-API-Key

┌─────────────────────────────────────┐  ┌───────────────────────────────────┐
│      Cycles Server (port 7878)      │  │  Cycles Admin Server (port 7979)  │
│     (runtime budget enforcement)    │  │  (tenant, key, budget management) │
│                                     │  │                                   │
│  ┌────────────┐  ┌──────────────┐   │  │  ┌─────────────────────────────┐  │
│  │ Controllers│  │ Auth Filter  │   │  │  │ Tenant CRUD, API Key Mgmt,  │  │
│  │ (REST API) │  │ (API Key)    │   │  │  │ Budget Ledgers, Policies,   │  │
│  └─────┬──────┘  └──────────────┘   │  │  │ Audit Logs, Auth Validation │  │
│        │                            │  │  └──────────────┬──────────────┘  │
│        ▼                            │  │                 │                 │
│  ┌──────────────────────────────┐   │  └─────────────────┼─────────────────┘
│  │ RedisReservationRepository   │   │                    │
│  │ (Lua scripts for atomicity)  │   │                    │
│  └──────────────┬───────────────┘   │                    │
└─────────────────┼───────────────────┘                    │
                  │                                        │
                  └──────────────┬─────────────────────────┘

              ┌─────────────────────────────────────┐
              │            Redis 7+                 │
              │  (budget state, reservations,       │
              │   tenants, API keys, audit logs)    │
              └─────────────────────────────────────┘

Your application talks to the Cycles Server (port 7878) at runtime. The Cycles Admin Server (port 7979) is the management plane where you create tenants, generate API keys, and configure budget ledgers. Both servers share the same Redis instance.

Components

Cycles Protocol

The protocol specification defines the API contract. It is a language-agnostic OpenAPI 3.1 spec that any client or server can implement.

The protocol defines:

  • Nine HTTP endpoints for reservations, decisions, balances, and events
  • The Subject hierarchy (tenant, workspace, app, workflow, agent, toolset)
  • The reserve → execute → commit lifecycle
  • Error codes and their semantics
  • Idempotency guarantees
  • Scope derivation rules

The spec lives at cycles-protocol.

Cycles Server

The reference server implementation. It is a Spring Boot 3.5 application backed by Redis 7+.

What it does:

  • Accepts HTTP requests from clients
  • Validates API keys and enforces tenant isolation
  • Executes atomic budget operations via Redis Lua scripts
  • Maintains budget state (allocated, spent, reserved, debt)
  • Runs a background expiry sweep to clean up abandoned reservations

Modules:

ModulePurpose
cycles-protocol-service-apiREST controllers, security filters, exception handling
cycles-protocol-service-dataRedis repository, Lua scripts, scope derivation, expiry service
cycles-protocol-service-modelShared DTOs and enums

Why Redis and Lua:

Budget enforcement under concurrency requires atomicity. A reservation must check and update multiple scope counters in a single operation. Redis Lua scripts execute atomically on the server, ensuring no race conditions between concurrent reservations.

Six Lua scripts handle the core operations:

ScriptOperation
reserve.luaCheck budgets across all scopes, reserve atomically
commit.luaRecord actual spend, release remainder, handle overage
release.luaReturn reserved budget to pool
extend.luaExtend reservation TTL
event.luaRecord direct debit without reservation
expire.luaMark expired reservations and release their budget

Cycles Admin Server

The management plane for Cycles. It runs as a separate Spring Boot 3.5 service on port 7979 and shares the same Redis instance as the Cycles Server.

What it does:

  • Manages tenants (create, list, update, suspend, close)
  • Creates and revokes API keys with granular permissions
  • Creates budget ledgers and handles funding operations (credit, debit, reset, repay debt)
  • Defines policies (caps, rate limits, TTL overrides) matched by scope patterns
  • Validates API keys (used by the Cycles Server for authentication)
  • Maintains an audit log of all administrative operations

Modules:

ModulePurpose
cycles-admin-service-apiREST controllers, auth interceptor, Spring Boot app
cycles-admin-service-dataRedis repositories, key service
cycles-admin-service-modelShared domain models and DTOs

Authentication: The admin server uses two auth schemes:

HeaderPurpose
X-Admin-API-KeySystem administration (tenant/key management, audit)
X-Cycles-API-KeyTenant-scoped operations (budgets, policies, reservations)

Why a separate server:

Separating the management plane from the runtime enforcement plane lets you:

  • Run the admin server in a restricted network (internal only) while the Cycles Server is accessible to applications
  • Scale the enforcement server independently from the admin server
  • Apply different access controls to management vs runtime operations

See the Cycles Admin Server README for the full API reference.

Cycles Spring Boot Starter

A client library that integrates Cycles into Spring Boot applications. It provides two usage modes:

  1. Declarative — The @Cycles annotation wraps methods in a reserve → execute → commit lifecycle automatically via Spring AOP
  2. Programmatic — The CyclesClient interface can be injected and used directly for fine-grained control

Key components:

ComponentPurpose
@Cycles annotationDeclarative budget enforcement on methods
CyclesAspectAOP interceptor that drives the lifecycle
CyclesLifecycleServiceOrchestrates reserve/execute/commit/release
CyclesClient / DefaultCyclesClientHTTP client using Spring WebClient
CyclesContextHolderThreadLocal access to reservation state mid-execution
CyclesExpressionEvaluatorSpEL evaluation for dynamic estimates and actuals
CyclesFieldResolverInterface for dynamic Subject field resolution
CommitRetryEngineRetry engine for transient commit failures
CyclesPropertiesSpring Boot configuration properties

Request flow

Here is what happens when an @Cycles-annotated method is called:

1. Estimate evaluation

The SpEL expression in the annotation is evaluated against method parameters to produce a numeric estimate.

2. Reservation request

The starter sends POST /v1/reservations to the Cycles server with the Subject, Action, estimate, TTL, and overage policy.

3. Atomic budget check (server side)

The server derives all affected scopes from the Subject, then executes reserve.lua. The Lua script:

  • Checks each scope has sufficient remaining budget (allocated - spent - reserved >= estimate)
  • Checks no scope has outstanding debt or is over-limit
  • If all checks pass, atomically increments the reserved counter on every scope
  • Stores the reservation record with its TTL

4. Decision returned

The server returns one of three decisions: ALLOW, ALLOW_WITH_CAPS, or DENY.

5. Method execution

If allowed, the starter runs the annotated method. During execution:

  • A heartbeat thread periodically extends the reservation TTL
  • The method can access CyclesContextHolder to read caps or set metrics

6. Commit

After the method returns, the starter evaluates the actual expression and sends POST /v1/reservations/{id}/commit. The server executes commit.lua to record actual spend and release the unused remainder.

7. Error path

If the method throws, the starter sends POST /v1/reservations/{id}/release to return all reserved budget to the pool.

Data model

All budget state lives in Redis. The key concepts:

Scopes

A scope is a budgeting boundary derived from the Subject hierarchy. A single reservation may affect multiple scopes. For example, a reservation with tenant=acme, workspace=prod, app=chatbot affects three scopes:

  • tenant:acme
  • tenant:acme/workspace:prod
  • tenant:acme/workspace:prod/app:chatbot

Balances

Each scope tracks:

FieldMeaning
allocatedTotal budget assigned to this scope
spentCommitted actual usage
reservedCurrently held by active reservations
remainingallocated - spent - reserved - debt
debtNegative balance from overdraft commits
overdraft_limitMaximum allowed debt
is_over_limitWhether debt > overdraft_limit

Reservations

Each reservation is stored with:

  • Unique ID
  • Subject and action metadata
  • Reserved amount and unit
  • Status (ACTIVE, COMMITTED, RELEASED, EXPIRED)
  • TTL and grace period timestamps
  • Idempotency key and payload hash

Authentication

The server authenticates every request via the X-Cycles-API-Key header. Each API key is associated with a tenant. The server enforces that subject.tenant matches the key's tenant — a key for tenant A cannot create reservations for tenant B.

Deployment topology

A typical deployment:

┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
│ Agent A  │  │ Agent B  │  │ Agent C  │  │ Agent D  │
│ (Spring) │  │ (Python) │  │ (Node.js)│  │  (HTTP)  │
└────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘
     │             │             │             │
     └──────┬──────┴─────────────┴─────────────┘


     ┌──────────────────────────────┐      ┌──────────────────────────┐
     │       Cycles Server          │      │   Cycles Admin Server    │
     │    (one or more instances)   │      │   (internal network)     │
     │         port 7878            │      │      port 7979           │
     └──────────────┬───────────────┘      └────────────┬─────────────┘
                    │                                   │
                    └─────────────┬──────────────────────┘

                  ┌──────────────────────────────┐
                  │         Redis 7+             │
                  │   (single instance or        │
                  │    Redis Cluster)            │
                  └──────────────────────────────┘

Multiple Cycles server instances can run behind a load balancer. All state is in Redis, so the server is stateless. The admin server is typically on an internal network, accessible only to operators and CI/CD pipelines.

Non-Spring clients (Python, TypeScript/Node.js, Go) can use the protocol directly via HTTP — the client libraries are convenience layers, not a requirement.

Next steps