1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//! JSON wrapper for the self-evolution governor (arXiv 2507.21046,
//! *A Survey of Self-Evolving Agents*).
//!
//! Stateless helper, binding-only — see
//! `docs/proposals/self-evolution-governor.md`. Decides the survey's *when +
//! what to evolve*: across every evolvable component (memory, skills, harness,
//! context, tools), which are due to evolve now, in what priority, under one
//! budget — evolving only under pressure and only with enough evidence.
use car_memgine::self_evolution::{plan_evolution, ComponentState, EvolutionPolicy};
#[derive(serde::Deserialize)]
struct EvolutionRequest {
#[serde(default)]
components: Vec<ComponentState>,
#[serde(default)]
policy: Option<EvolutionPolicy>,
}
/// Plan an evolution cycle. `request_json` is `{ components: [{ component:
/// "memory" | "skills" | "harness" | "context" | "tools", pressure?, evidence?,
/// min_evidence?, cost? }], policy?: { pressure_threshold?, budget? } }`. Returns
/// the `EvolutionPlan` JSON `{ decisions: [{ component, action: "evolve_now" |
/// "defer" | "skip", priority, defer_reason?, reason }], spent, evolve_now: [..] }`.
/// A component with pressure below threshold is skipped; one under pressure but
/// short on evidence defers (avoid overfitting); the rest are prioritized by
/// `pressure / cost` and packed into the budget.
pub fn plan(request_json: &str) -> Result<String, String> {
let req: EvolutionRequest = crate::from_json("request", request_json)?;
let policy = req.policy.unwrap_or_default();
let plan = plan_evolution(&req.components, &policy);
crate::to_json(&plan)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
#[test]
fn skips_low_pressure_evolves_high() {
let req = r#"{
"components": [
{"component":"memory","pressure":0.05,"evidence":100,"min_evidence":10,"cost":1.0},
{"component":"skills","pressure":0.9,"evidence":100,"min_evidence":10,"cost":2.0}
]
}"#;
let v: Value = serde_json::from_str(&plan(req).unwrap()).unwrap();
assert_eq!(v["evolve_now"], serde_json::json!(["skills"]));
}
#[test]
fn thin_evidence_defers() {
let req = r#"{"components":[{"component":"skills","pressure":0.9,"evidence":2,"min_evidence":20}]}"#;
let v: Value = serde_json::from_str(&plan(req).unwrap()).unwrap();
assert_eq!(v["decisions"][0]["action"], "defer");
assert_eq!(v["decisions"][0]["defer_reason"], "insufficient_evidence");
}
#[test]
fn bad_json_errors() {
assert!(plan("nope").is_err());
}
}