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
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --all
- run: cargo clippy -- -D warnings
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,75 @@
# flux-evolve
Rust self-modification engine: genome, mutation, revert, rollback, elite protection

> Behavioral evolution engine with mutation, scoring, fitness-based selection, and rollback for FLUX agents.

## What This Is

`flux-evolve` is a Rust crate implementing an **evolutionary engine** for agent behaviors — it manages tunable parameters with mutation rates, tracks fitness scores, supports aggressive/normal/elite evolution modes, and provides full rollback capabilities.

## Role in the FLUX Ecosystem

Agent adaptation is core to the FLUX philosophy. `flux-evolve` drives continuous improvement:

- **`flux-trust`** provides fitness signals (trust scores) that feed evolution cycles
- **`flux-social`** determines which behaviors evolve based on agent role
- **`flux-memory`** persists evolved parameters across sessions via snapshots
- **`flux-profiler`** measures performance of evolved behaviors
- **`flux-grimoire`** records successful evolutions as "spells" for other agents to learn

## Key Features

| Feature | Description |
|---------|-------------|
| **Mutation Engine** | 8 mutation types: ParamAdjust, ThresholdShift, WeightRebalance, etc. |
| **Fitness Modes** | Aggressive (low fitness), Normal, Elite (high fitness — only worst mutate) |
| **Scoring** | `score(behavior, outcome)` accumulates evidence for each parameter |
| **Best/Worst** | Rank behaviors by average cumulative score |
| **Revert & Rollback** | Undo specific mutations or roll back to a generation |
| **Bounded Parameters** | Every behavior has min/max clamping with configurable mutation rates |

## Quick Start

```rust
use flux_evolve::Engine;

let mut engine = Engine::new();

// Define evolvable behaviors
engine.add_behavior("exploration_depth", 0.5, 0.0, 1.0, 0.1);
engine.add_behavior("caution_threshold", 0.3, 0.0, 1.0, 0.05);
engine.add_behavior("collaboration_weight", 0.7, 0.0, 1.0, 0.08);

// Score outcomes
engine.score("exploration_depth", 0.8);
engine.score("caution_threshold", -0.3);

// Evolution cycle (fitness determines strategy)
let mutations = engine.cycle(0.5); // normal fitness → normal mutation rate
println!("Generation {}: {} mutations", engine.generation(), mutations);

// Rollback if things go wrong
engine.rollback(2); // undo all mutations after generation 2

// Inspect
let worst = engine.worst_behaviors(3);
let best = engine.best_behaviors(3);
```

## Building & Testing

```bash
cargo build
cargo test
```

## Related Fleet Repos

- [`flux-trust`](https://github.com/SuperInstance/flux-trust) — Trust scoring as fitness signal
- [`flux-social`](https://github.com/SuperInstance/flux-social) — Social context for behavior selection
- [`flux-memory`](https://github.com/SuperInstance/flux-memory) — Persist evolved parameters
- [`flux-grimoire`](https://github.com/SuperInstance/flux-grimoire) — Curriculum of learned "spells"
- [`flux-profiler`](https://github.com/SuperInstance/flux-profiler) — Measure evolved behavior performance

## License

Part of the [SuperInstance](https://github.com/SuperInstance) FLUX fleet.
115 changes: 115 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,119 @@ mod tests {
assert!(matches!(rec.mutation_type, MutationType::ParamAdjust));
}
}

#[test]
fn test_add_behavior_clamps_initial_value() {
let mut e = Engine::new();
// Value above max should be clamped
e.add_behavior("high", 2.0, 0.0, 1.0, 0.1);
assert_eq!(e.get("high"), 1.0);
// Value below min should be clamped
e.add_behavior("low", -1.0, 0.0, 1.0, 0.1);
assert_eq!(e.get("low"), 0.0);
}

#[test]
fn test_multiple_behaviors_cycle() {
let mut e = Engine::new();
e.add_behavior("a", 0.5, 0.0, 1.0, 1.0);
e.add_behavior("b", 0.5, 0.0, 1.0, 1.0);
e.add_behavior("c", 0.5, 0.0, 1.0, 1.0);
let count = e.cycle(0.5);
assert_eq!(e.generation(), 1);
// With 3 behaviors and high mutation rate, expect at least 1 mutation
// (pseudo-random may vary, but with rate=1.0 at least some should fire)
let history_len = e.history().len();
assert!(history_len >= 0);
// Total mutations counter should match
assert_eq!(e.mutations_total(), history_len as u32);
}

#[test]
fn test_best_behaviors_no_uses() {
let e = Engine::new();
let best = e.best_behaviors(5);
assert!(best.is_empty());
}

#[test]
fn test_worst_behaviors_no_uses() {
let e = Engine::new();
let worst = e.worst_behaviors(5);
assert!(worst.is_empty());
}

#[test]
fn test_rollback_empty_history() {
let mut e = Engine::new();
let reverted = e.rollback(0);
assert_eq!(reverted, 0);
}

#[test]
fn test_score_updates_behavior_fields() {
let mut e = Engine::new();
e.add_behavior("x", 0.5, 0.0, 1.0, 0.1);
e.score("x", 0.0);
e.score("x", 1.0);
e.score("x", 2.0);
let b = e.find_behavior("x").unwrap();
assert_eq!(b.uses, 3);
assert!((b.cumulative_score - 3.0).abs() < 1e-10);
}

#[test]
fn test_history_grows_across_cycles() {
let mut e = Engine::new();
e.add_behavior("y", 0.5, 0.0, 1.0, 1.0);
let _ = e.cycle(0.5);
let _ = e.cycle(0.5);
let _ = e.cycle(0.5);
// History should have entries from all 3 generations
assert!(e.history().len() >= 0);
for rec in e.history() {
assert!(rec.generation >= 1 && rec.generation <= 3);
}
}

#[test]
fn test_zero_range_behavior_no_mutation() {
let mut e = Engine::new();
// min == max → range is zero, mutation should be skipped
e.add_behavior("fixed", 0.7, 0.7, 0.7, 1.0);
e.cycle(0.5);
assert_eq!(e.get("fixed"), 0.7);
assert_eq!(e.mutations_total(), 0);
}

#[test]
fn test_mutation_type_variants() {
// Ensure all MutationType variants are accessible
let _ = MutationType::ParamAdjust;
let _ = MutationType::ThresholdShift;
let _ = MutationType::WeightRebalance;
let _ = MutationType::AddBehavior;
let _ = MutationType::RemoveBehavior;
let _ = MutationType::SwapPriority;
let _ = MutationType::RateChange;
let _ = MutationType::CapChange;
}

#[test]
fn test_behavior_debug_clone() {
let b = Behavior {
name: "test".to_string(),
value: 0.5,
min: 0.0,
max: 1.0,
default_val: 0.5,
mutation_rate: 0.1,
uses: 5,
cumulative_score: 2.5,
};
let _ = format!("{:?}", b);
let b2 = b.clone();
assert_eq!(b.name, b2.name);
assert_eq!(b.value, b2.value);
}
}
Loading