Nine Lives 🐱
Tower-native fractal supervision for async Rust — autonomous, self-healing Services via composable policy algebra.
Resilience patterns for Rust with algebraic composition.
Nine Lives provides battle-tested resilience patterns (retry, circuit breaker, bulkhead, timeout) as composable tower layers with a unique algebraic composition system.
Features
- 🔁 Retry policies with exponential/linear/constant backoff and jitter
- ⚡ Circuit breakers with half-open state recovery
- 🚧 Bulkheads for concurrency limiting and resource isolation
- ⏱️ Timeout policies integrated with tokio
- 🧮 Algebraic composition via intuitive operators (
+,|,&) - 🏎️ Fork-join for concurrent racing (Happy Eyeballs pattern)
- 🔒 Lock-free implementations using atomics
- 🏗️ Tower-native - works with any tower
Service - 🌐 Companion sinks (OTLP, NATS, Kafka, Elastic, etcd, Prometheus, JSONL) via optional crates
Quick Start
Add to your Cargo.toml:
[]
= "0.2"
= "0.5"
= { = "1", = ["full"] }
Basic Usage
use *;
use Duration;
use ;
async
Algebraic Composition - The Nine Lives Advantage
Compose resilience strategies using intuitive operators:
Policy(A) + Policy(B)- Sequential composition:AwrapsBPolicy(A) | Policy(B)- Fallback: tryA, fall back toBon errorPolicy(A) & Policy(B)- Fork-join: try both concurrently, return first success
Precedence: & > + > | (like * > + > bitwise-or in math)
Example: Fallback Strategy
Try an aggressive timeout first, fall back to a longer timeout on failure:
use *;
use Duration;
use ;
let fast = Policy;
let slow = Policy;
let policy = fast | slow;
let svc = new
.layer
.service_fn;
Example: Fork-Join (Happy Eyeballs)
Race two strategies concurrently and return the first success:
use *;
use Duration;
// Create two timeout policies with different durations
let ipv4 = Policy;
let ipv6 = Policy;
// Race them concurrently - first success wins
let policy = ipv4 & ipv6;
let svc = new
.layer
.service_fn;
Example: Multi-Tier Resilience
Combine multiple strategies with automatic precedence:
use *;
use Duration;
// Aggressive: just a fast timeout
let aggressive = Policy;
// Defensive: nested timeouts for retries
let defensive = Policy
+ Policy;
// Try aggressive first, fall back to defensive
let policy = aggressive | defensive;
// Parsed as: Policy(Timeout50ms) | (Policy(Timeout10s) + Policy(Timeout5s))
Example: Circuit Breaker with Retry
use *;
use Duration;
// Build a retry policy with exponential backoff
let retry = builder
.max_attempts
.backoff
.with_jitter
.build?;
// Configure circuit breaker
let circuit_breaker = new?;
// Compose: circuit breaker wraps retry
let policy = Policy + Policy;
Telemetry Sink Ladder
- Baby mode:
MemorySink::with_capacity(1_000)for local inspection. - Intermediate:
NonBlockingSink(LogSink)to keep request paths non-blocking while logging. - Advanced:
NonBlockingSink(OtlpSink)+StreamingSinkfan-out for in-cluster consumers. - GOD MODE:
StreamingSink→ NATS/Kafka/Elastic via companion crates, with Observer + Sentinel auto-tuning when drop/evict metrics spike.
See recipes in src/cookbook.rs and companion cookbooks:
ninelives-otlp/README.mdninelives-nats/README.mdninelives-kafka/README.mdninelives-elastic/README.mdninelives-etcd/README.mdninelives-prometheus/README.mdninelives-jsonl/README.md
Cookbook (pick your recipe)
- Simple retry:
retry_fast— 3 attempts, 50ms exp backoff + jitter. - Latency guard:
timeout_p95— 300ms budget. - Bulkhead:
bulkhead_isolate(max)— protect shared deps. - API guardrail (intermediate):
api_guardrail— timeout + breaker + bulkhead. - Reliable read (advanced):
reliable_read— fast path then fallback stack. - Hedged read (tricky):
hedged_read— fork-join two differently-tuned stacks. - Hedge + fallback (god tier):
hedged_then_fallback— race two fast paths, then fall back to a sturdy stack. - Sensible defaults:
sensible_defaults— timeout + retry + bulkhead starter pack.
All live in src/cookbook.rs.
Moved to the ninelives-cookbook crate (see its README/examples).
Tower Integration
Nine Lives layers work seamlessly with tower's ServiceBuilder:
use *;
use ServiceBuilder;
use Duration;
let service = new
.layer
.layer
.layer
.service;
Or use the algebraic syntax:
let policy = Policy
+ Policy
+ Policy;
let service = new
.layer
.service;
Available Layers
TimeoutLayer
Enforces time limits on operations:
use *;
use Duration;
let timeout = new?;
RetryLayer
Retries failed operations with configurable backoff and jitter:
use *;
use Duration;
let retry = builder
.max_attempts
.backoff
.with_jitter
.build?
.into_layer;
Backoff strategies:
Backoff::constant(duration)- Fixed delayBackoff::linear(base)- Linear increase:base * attemptBackoff::exponential(base)- Exponential:base * 2^attempt
Jitter strategies:
Jitter::none()- No jitterJitter::full()- Random [0, delay]Jitter::equal()- delay/2 + random [0, delay/2]Jitter::decorrelated()- AWS-style stateful jitter
CircuitBreakerLayer
Prevents cascading failures with three-state management (Closed/Open/HalfOpen):
use *;
use Duration;
let circuit_breaker = new?;
BulkheadLayer
Limits concurrent requests for resource isolation:
use *;
let bulkhead = new?; // Max 10 concurrent requests
Error Handling
All resilience errors are unified under ResilienceError<E>:
use ResilienceError;
match service.call.await
Operator Precedence
When combining operators, understand the precedence rules:
// & binds tighter than +, and + binds tighter than |
A | B + C & D // Parsed as: A | (B + (C & D))
// Use parentheses for explicit control
+ C // C wraps the fallback between A and B
Examples:
// Try fast, fallback to slow with retry
let policy = fast | retry + slow;
// Equivalent to: fast | (retry + slow)
// Retry wraps a fallback
let policy = retry + ;
// Happy Eyeballs: race IPv4 and IPv6
let policy = ipv4 & ipv6;
// Both called concurrently, first success wins
// Complex composition
let policy = aggressive | defensive + ;
// Try aggressive, fallback to defensive wrapping parallel attempts
Testability
Nine Lives is designed for testing with dependency injection:
use *;
use Duration;
// Use InstantSleeper for tests (no actual delays)
let retry = builder
.max_attempts
.backoff
.with_sleeper
.build?;
// TrackingSleeper records sleep durations for assertions
let tracker = new;
let retry = builder
.max_attempts
.with_sleeper
.build?;
// ... exercise retry ...
let sleeps = tracker.get_sleeps;
assert_eq!; // Slept twice before success
Roadmap (snapshot)
Nine Lives is marching toward autonomous, fractal resilience. Current focus:
- ✅ Phase 0–1: Tower-native algebra + telemetry sinks (done)
- 🚧 Phase 2: Control plane & adaptive configs (in progress)
- 🧭 Phase 3: Observer for aggregated state (planned)
- 🔮 Phase 5: Sentinel meta-policies + shadow eval (planned)
Full detail and milestones live in ROADMAP.md.
Performance
Nine Lives is built for production:
- Lock-free circuit breaker state transitions using atomics
- Zero-allocation backoff/jitter calculations with overflow protection
- Minimal overhead - resilience layers add < 1% latency in common cases
Benchmarks coming soon.
Comparison to Other Libraries
| Feature | Nine Lives | Resilience4j (Java) | Polly (C#) | tower |
|---|---|---|---|---|
| Uniform Service Abstraction | ✅ | ❌ | ❌ | ✅ |
Algebraic Composition (+, |, &) |
✅ | ❌ | ❌ | ❌ |
| Fork-Join (Happy Eyeballs) | ✅ | ❌ | ❌ | ❌ |
| Tower Integration | ✅ Native | N/A | N/A | ✅ Native |
| Lock-Free Implementations | ✅ | Partial | Partial | Varies |
| Retry with Backoff/Jitter | ✅ | ✅ | ✅ | ❌ |
| Circuit Breaker | ✅ | ✅ | ✅ | ❌ |
| Bulkhead | ✅ | ✅ | ✅ | ❌ |
| Timeout | ✅ | ✅ | ✅ | ✅ |
Nine Lives' unique advantage: Algebraic composition with fork-join support lets you express complex resilience strategies declaratively, including concurrent racing patterns like Happy Eyeballs, without nested builders or imperative code.
Examples
See the ninelives-cookbook/examples directory for runnable examples:
retry_only.rs- Focused retry with backoff, jitter, andshould_retrybulkhead_concurrency.rs- Non-blocking bulkhead behavior under contentiontimeout_fallback.rs- Timeout with fallback policydecorrelated_jitter.rs- AWS-style decorrelated jitteralgebra_composition.rs- Algebraic composition patternstelemetry_basic.rs/telemetry_composition.rs- Attaching sinks and composing telemetry
Run with:
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.
License
Apache License, Version 2.0 (LICENSE or http://www.apache.org/licenses/LICENSE-2.0)
@ 2025 • James Ross • 📧 • 🔗 FLYING•ROBOTS