Resource Pools & Rotation
Distribute work across multiple resources — API keys, email accounts, proxy servers, or any rate-limited service. Built-in rotation strategies, daily caps, and warmup ramps ensure even distribution without overloading any single resource.
Why Resource Pools
Many workflows interact with rate-limited external services. Without rotation, a single API key or mail server becomes a bottleneck:
- ●Email deliverability — rotate across SMTP accounts to avoid sender reputation damage
- ●API rate limits — distribute calls across multiple keys to stay under per-key quotas
- ●Proxy rotation — cycle through IPs to avoid blocks during scraping
- ●Load balancing — spread traffic across servers with weight-based distribution
- ●New resource onboarding — warmup ramps prevent new accounts from hitting limits on day one
Core Concepts
| Concept | Description |
|---|---|
| Pool | A named collection of resources with a rotation strategy. Tenant-scoped. |
| Resource | A single unit in a pool (e.g., one API key, one mail server). Has a unique resource_key. |
| Strategy | How the next resource is selected: round_robin, weighted, or random. |
| Daily Cap | Maximum assignments per day per resource. Resets at midnight. 0 = unlimited. |
| Warmup | Gradual daily cap increase over N days for newly added resources. |
| Assignment | Result of requesting a resource: Assigned(key), Exhausted, or Empty. |
Rotation Strategies
round_robin — Default
Assigns resources in order, cycling through all enabled resources with capacity. Tracks position via round_robin_index. Skips resources that have hit their daily cap.
weighted
Assigns proportional to each resource's weight. A resource with weight 3 receives ~3x more assignments than weight 1. Respects daily caps — exhausted resources are excluded from the weighted selection.
random
Random uniform selection among enabled resources with remaining capacity. Useful when distribution doesn't need to be perfectly even.
Assignment results: When all resources hit their daily cap, assignment returns Exhausted. If no resources are enabled, it returns Empty. Workflows should handle these cases (retry later or fail).
Warmup Ramps
New email accounts or freshly provisioned API keys often have lower limits initially. Warmup ramps gradually increase the daily cap from a starting value to the full cap over a configurable number of days.
How It Works
The effective daily cap is linearly interpolated:
effective_cap = warmup_start_cap + (daily_cap - warmup_start_cap) * days_active / warmup_days
Example: warmup_start_cap=10, daily_cap=100, warmup_days=10
Day 0: cap = 10
Day 3: cap = 10 + (90 * 3 / 10) = 37
Day 5: cap = 10 + (90 * 5 / 10) = 55
Day 10: cap = 100 (warmup complete)
Day 15: cap = 100 (stays at full)Configuration
| Field | Type | Description |
|---|---|---|
| warmup_start | date (YYYY-MM-DD) | When the warmup period begins |
| warmup_days | u32 | Number of days to ramp from start cap to full cap. 0 = no warmup. |
| warmup_start_cap | u32 | Starting daily cap on day 0 of warmup |
Pool API
Create Pool
POST /pools
{
"tenant_id": "tenant-1",
"name": "Email Senders",
"strategy": "round_robin"
}
Response (201):
{
"id": "550e8400-...",
"tenant_id": "tenant-1",
"name": "Email Senders",
"strategy": "round_robin",
"round_robin_index": 0,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}List Pools
GET /pools?tenant_id=tenant-1Add Resource
POST /pools/{pool_id}/resources
{
"resource_key": "smtp-account-1",
"name": "Primary SMTP",
"weight": 2,
"daily_cap": 500,
"warmup_start": "2024-01-15",
"warmup_days": 14,
"warmup_start_cap": 50
}
Response (201):
{
"id": "...",
"pool_id": "...",
"resource_key": "smtp-account-1",
"name": "Primary SMTP",
"weight": 2,
"enabled": true,
"daily_cap": 500,
"daily_usage": 0,
"daily_usage_date": null,
"warmup_start": "2024-01-15",
"warmup_days": 14,
"warmup_start_cap": 50,
"created_at": "2024-01-15T10:00:00Z"
}Update Resource
PUT /pools/{pool_id}/resources/{resource_id}
{
"enabled": false,
"daily_cap": 1000
}Delete Resource
DELETE /pools/{pool_id}/resources/{resource_id}
Response: 204 No ContentList Resources
GET /pools/{pool_id}/resources
Response: Array of PoolResource objects with current daily_usageValidation: resource_key andname must be 1-255 characters. weight must be at least 1.
Using Pools in Workflows
Workflows request a resource from a pool before executing rate-limited steps. The assigned resource_key is used to look up the corresponding credential or configuration.
Example: Email Rotation
[
{
"id": "get-sender",
"type": "pool_assign",
"params": {
"pool_id": "email-senders-pool-id"
}
},
{
"id": "send-email",
"type": "http_request",
"params": {
"url": "https://api.sendgrid.com/v3/mail/send",
"headers": {
"Authorization": "Bearer credentials://{{steps.get-sender.output.resource_key}}"
},
"body": {
"from": "{{steps.get-sender.output.resource_key}}@example.com",
"to": "{{context.recipient}}",
"subject": "Hello"
}
}
}
]Handling Exhaustion
{
"id": "get-api-key",
"type": "pool_assign",
"params": { "pool_id": "openai-keys-pool" },
"retry": {
"max_attempts": 3,
"backoff": "exponential",
"initial_delay_ms": 60000
}
}When all resources are exhausted, the step fails with a transient error. A retry policy with delay gives resources time to reset at midnight.
Production Patterns
Multi-Provider LLM Pool
Create a pool with weighted resources: OpenAI (weight: 3, cap: 10000), Anthropic (weight: 2, cap: 5000), Groq (weight: 1, cap: 2000). The engine distributes calls proportionally while respecting per-provider rate limits.
SMTP Warmup
Add new mail accounts with warmup_start_cap: 20,daily_cap: 500, warmup_days: 30. Over a month, sending volume gradually increases, building sender reputation without triggering spam filters.
Graceful Degradation
Disable a resource via PATCH enabled: false when it's experiencing issues. The pool automatically routes to remaining resources. Re-enable when resolved — no workflow changes needed.
Capacity Monitoring
Poll GET /pools/{id}/resources to checkdaily_usage vs daily_cap across resources. Alert when total remaining capacity drops below threshold.