Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default defineConfig({
logo: '/runcycles-logo-64.png',
nav: [
{ text: 'Home', link: '/' },
{ text: 'Quickstart', link: '/quickstart/deploying-the-full-cycles-stack' },
{ text: 'Quickstart', link: '/quickstart/getting-started-with-the-python-client' },
{ text: 'API Reference', link: '/api/' },
{ text: 'Protocol', link: 'https://github.com/runcycles/cycles-protocol' },
{ text: 'GitHub', link: 'https://github.com/runcycles' }
Expand All @@ -38,13 +38,14 @@ export default defineConfig({
{
text: 'Quickstart',
items: [
{ text: 'Deploy the Full Stack', link: '/quickstart/deploying-the-full-cycles-stack' },
{ text: 'Architecture Overview', link: '/quickstart/architecture-overview-how-cycles-fits-together' },
{ text: 'Self-Hosting the Server', link: '/quickstart/self-hosting-the-cycles-server' },
{ text: 'Spring Boot Starter', link: '/quickstart/getting-started-with-the-cycles-spring-boot-starter' },
{ text: 'Python Client', link: '/quickstart/getting-started-with-the-python-client' },
{ text: 'Add to a Python App', link: '/quickstart/getting-started-with-the-python-client' },
{ text: 'Add to a TypeScript App', link: '/quickstart/getting-started-with-the-typescript-client' },
{ text: 'Add to a Spring Boot App', link: '/quickstart/getting-started-with-the-cycles-spring-boot-starter' },
{ text: 'Budget Limits with Spring AI', link: '/quickstart/how-to-add-hard-budget-limits-to-spring-ai-with-cycles' },
{ text: 'Choose a First Rollout', link: '/quickstart/how-to-choose-a-first-cycles-rollout-tenant-budgets-run-budgets-or-model-call-guardrails' },
{ text: 'Architecture Overview', link: '/quickstart/architecture-overview-how-cycles-fits-together' },
{ text: 'Deploy the Full Stack', link: '/quickstart/deploying-the-full-cycles-stack' },
{ text: 'Self-Hosting the Server', link: '/quickstart/self-hosting-the-cycles-server' },
]
},
{
Expand Down Expand Up @@ -99,6 +100,7 @@ export default defineConfig({
text: 'Configuration Reference',
items: [
{ text: 'Python Client Configuration', link: '/configuration/python-client-configuration-reference' },
{ text: 'TypeScript Client Configuration', link: '/configuration/typescript-client-configuration-reference' },
{ text: 'Spring Client Configuration', link: '/configuration/client-configuration-reference-for-cycles-spring-boot-starter' },
{ text: 'Server Configuration', link: '/configuration/server-configuration-reference-for-cycles' },
{ text: 'SpEL Expression Reference', link: '/configuration/spel-expression-reference-for-cycles' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,5 +428,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -372,5 +372,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -375,5 +375,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,6 @@ To learn more:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage tenants and budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
180 changes: 180 additions & 0 deletions configuration/typescript-client-configuration-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# TypeScript Client Configuration Reference

This is the complete reference for all configuration options available in the `runcycles` TypeScript client.

## CyclesConfig

All configuration is provided through the `CyclesConfig` constructor.

### Required fields

| Field | Type | Description |
|---|---|---|
| `baseUrl` | `string` | Base URL of the Cycles server (e.g., `http://localhost:7878`) |
| `apiKey` | `string` | API key for authentication |

### Subject defaults

These fields set default values for the Subject used in `withCycles` calls. They apply to all guarded functions unless overridden at the HOF level.

| Field | Type | Default | Description |
|---|---|---|---|
| `tenant` | `string \| undefined` | `undefined` | Default tenant |
| `workspace` | `string \| undefined` | `undefined` | Default workspace |
| `app` | `string \| undefined` | `undefined` | Default application name |
| `workflow` | `string \| undefined` | `undefined` | Default workflow |
| `agent` | `string \| undefined` | `undefined` | Default agent |
| `toolset` | `string \| undefined` | `undefined` | Default toolset |

### HTTP timeouts

| Field | Type | Default | Description |
|---|---|---|---|
| `connectTimeout` | `number` | `2000` | Connection timeout in milliseconds |
| `readTimeout` | `number` | `5000` | Read timeout in milliseconds |

> **Note:** Node's built-in `fetch` does not distinguish connection timeout from read timeout. `connectTimeout` and `readTimeout` are summed into a single `AbortSignal.timeout()` value (default: 7000ms total) that caps the entire request duration.

### Retry configuration

Controls the commit retry engine for transient failures.

| Field | Type | Default | Description |
|---|---|---|---|
| `retryEnabled` | `boolean` | `true` | Enable automatic commit retries |
| `retryMaxAttempts` | `number` | `5` | Maximum number of retry attempts |
| `retryInitialDelay` | `number` | `500` | Delay before the first retry (milliseconds) |
| `retryMultiplier` | `number` | `2.0` | Backoff multiplier between retries |
| `retryMaxDelay` | `number` | `30000` | Maximum delay between retries (milliseconds) |

#### How retry works

When a commit fails with a transport error or 5xx response, the retry engine schedules a retry using exponential backoff:

```
Attempt 1: wait 500ms
Attempt 2: wait 1000ms
Attempt 3: wait 2000ms
Attempt 4: wait 4000ms
Attempt 5: wait 8000ms (capped at retryMaxDelay)
```

Non-retryable errors (4xx responses) are not retried. Retries are fire-and-forget — the guarded function returns immediately while the commit is retried in the background.

## Programmatic configuration

```typescript
import { CyclesConfig } from "runcycles";

const config = new CyclesConfig({
// Required
baseUrl: "http://localhost:7878",
apiKey: "cyc_live_...",

// Subject defaults
tenant: "acme",
workspace: "production",
app: "support-bot",

// HTTP settings (milliseconds)
connectTimeout: 2000,
readTimeout: 5000,

// Commit retry
retryEnabled: true,
retryMaxAttempts: 5,
retryInitialDelay: 500,
retryMultiplier: 2.0,
retryMaxDelay: 30000,
});
```

## Environment variable configuration

Use `CyclesConfig.fromEnv()` to load configuration from environment variables. The default prefix is `CYCLES_`:

```typescript
const config = CyclesConfig.fromEnv();
```

| Environment variable | Maps to | Required |
|---|---|---|
| `CYCLES_BASE_URL` | `baseUrl` | Yes |
| `CYCLES_API_KEY` | `apiKey` | Yes |
| `CYCLES_TENANT` | `tenant` | No |
| `CYCLES_WORKSPACE` | `workspace` | No |
| `CYCLES_APP` | `app` | No |
| `CYCLES_WORKFLOW` | `workflow` | No |
| `CYCLES_AGENT` | `agent` | No |
| `CYCLES_TOOLSET` | `toolset` | No |
| `CYCLES_CONNECT_TIMEOUT` | `connectTimeout` | No |
| `CYCLES_READ_TIMEOUT` | `readTimeout` | No |
| `CYCLES_RETRY_ENABLED` | `retryEnabled` | No |
| `CYCLES_RETRY_MAX_ATTEMPTS` | `retryMaxAttempts` | No |
| `CYCLES_RETRY_INITIAL_DELAY` | `retryInitialDelay` | No |
| `CYCLES_RETRY_MULTIPLIER` | `retryMultiplier` | No |
| `CYCLES_RETRY_MAX_DELAY` | `retryMaxDelay` | No |

A custom prefix can be passed: `CyclesConfig.fromEnv("MY_PREFIX_")` reads `MY_PREFIX_BASE_URL`, `MY_PREFIX_API_KEY`, etc.

## Setting a default client

Instead of passing `client` to every `withCycles` call, set a module-level default:

```typescript
import { CyclesClient, CyclesConfig, setDefaultClient, setDefaultConfig } from "runcycles";

// Option 1: Set a config (client created lazily on first invocation)
setDefaultConfig(new CyclesConfig({
baseUrl: "http://localhost:7878",
apiKey: "cyc_live_...",
tenant: "acme",
}));

// Option 2: Set an explicit client
setDefaultClient(new CyclesClient(new CyclesConfig({
baseUrl: "http://localhost:7878",
apiKey: "cyc_live_...",
})));
```

Client resolution is deferred to the first invocation and then cached — the wrapper binds permanently to the resolved client after its first call. A later `setDefaultClient()` call will not affect already-invoked wrappers.

## Resolution order

For each Subject field, the HOF resolves the value using this priority:

1. **HOF parameter** — if set in the `withCycles` options, it wins
2. **Config default** — if set on the `CyclesConfig` instance

If neither provides a value, the field is omitted from the request.

## Disabling retry

```typescript
const config = new CyclesConfig({
baseUrl: "http://localhost:7878",
apiKey: "cyc_live_...",
retryEnabled: false,
});
```

## Aggressive retry for critical commits

```typescript
const config = new CyclesConfig({
baseUrl: "http://localhost:7878",
apiKey: "cyc_live_...",
retryMaxAttempts: 10,
retryInitialDelay: 200,
retryMultiplier: 1.5,
retryMaxDelay: 60000,
});
```

## Next steps

- [Getting Started with the TypeScript Client](/quickstart/getting-started-with-the-typescript-client) — quick start guide
- [Error Handling Patterns](/how-to/error-handling-patterns-in-cycles-client-code) — error handling patterns
- [Using the Client Programmatically](/how-to/using-the-cycles-client-programmatically) — direct client usage
- [Server Configuration Reference](/configuration/server-configuration-reference-for-cycles) — server-side properties
Original file line number Diff line number Diff line change
Expand Up @@ -471,5 +471,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -397,5 +397,6 @@ To explore how Cycles models these boundaries:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -422,5 +422,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -371,5 +371,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,29 @@ to:

That is a different operating model.

## See it in action: the runaway agent demo

The [cycles-runaway-demo](https://github.com/runcycles/cycles-runaway-demo) repository demonstrates exactly this failure mode with a runnable example.

The scenario: a customer support bot drafts a response, evaluates its quality, and refines it in a loop until the quality score exceeds 8.0. The bug is that the quality evaluator never returns above 6.9. Without a budget boundary, the agent loops indefinitely.

The demo runs the same agent twice:

1. **Without Cycles** — the agent runs for 30 seconds, making ~600 calls and spending ~$6.00 before being auto-terminated. In production, there would be no auto-termination.
2. **With Cycles (budget: $1.00)** — the agent hits the budget ceiling after ~100 calls. The Cycles server returns `409 BUDGET_EXCEEDED`, the `@cycles` decorator raises `BudgetExceededError`, and the agent stops cleanly.

The entire integration diff between the unguarded and guarded versions is three `@cycles` decorators and one `except BudgetExceededError` block.

To run it locally:

```bash
git clone https://github.com/runcycles/cycles-runaway-demo
cd cycles-runaway-demo
python3 -m venv .venv && source .venv/bin/activate
pip install -r agent/requirements.txt
./demo.sh
```

## Why this matters now

As AI systems become more autonomous, incidents are shifting.
Expand Down Expand Up @@ -388,8 +411,10 @@ to:

To explore the Cycles stack:

- Try the [Runaway Agent Demo](https://github.com/runcycles/cycles-runaway-demo) — see the failure mode and the fix in action
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
4 changes: 2 additions & 2 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ hero:
actions:
- theme: brand
text: "Get Started"
link: "/quickstart/deploying-the-full-cycles-stack"
link: "/quickstart/getting-started-with-the-python-client"
- theme: alt
text: "View on GitHub"
link: "https://github.com/runcycles"
Expand All @@ -20,7 +20,7 @@ features:
- title: "Protocol Reference"
details: "Reserve-commit lifecycle, authentication, scope derivation, units, caps, overage policies, TTL, decide, dry run, events, debt, balances, metrics, and error handling."
- title: "How-To Guides"
details: "Python and Spring AI integration, tenant budgets, shadow mode rollout, reservation strategies, and degradation paths."
details: "Python, TypeScript, and Spring AI integration, tenant budgets, shadow mode rollout, reservation strategies, and degradation paths."
- title: "Incident Patterns"
details: "Real failure modes — runaway agents, tool loops, retries, and budget overruns."
---
3 changes: 2 additions & 1 deletion protocol/authentication-tenancy-and-api-keys-in-cycles.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
3 changes: 2 additions & 1 deletion protocol/caps-and-the-three-way-decision-model-in-cycles.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,6 @@ To explore the Cycles stack:
- Read the [Cycles Protocol](https://github.com/runcycles/cycles-protocol)
- Run the [Cycles Server](https://github.com/runcycles/cycles-server)
- Manage budgets with [Cycles Admin](https://github.com/runcycles/cycles-server-admin)
- Integrate with Python using the [Python Client](https://github.com/runcycles/cycles-client-python)
- 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)
- Integrate with Spring AI using the [Spring Client](https://github.com/runcycles/cycles-spring-boot-starter)
Loading