feat(topology): template builders + evolution/QD search (P5)
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

cm-topology::build(kind, roles) instantiates a canonical graph for any of the 12
kinds from a role list (the template catalog + the search generator).

cm-orchestrator::evolve searches a (kinds × team-size) grid using the comparison
machinery as fitness: build a candidate per cell, run the task, score it, and
keep a MAP-Elites-style archive of per-cell elites + a quality/cost Pareto front
and the global best. evolve_all() covers every kind at full size. This is the
bridge toward Autonomous Organizational Evolution on a safe substrate — every
candidate still executes via safe turns (§15 invariant holds).

Demoed in topology_bench (auto-picks the best topology + Pareto kinds).
cm-topology 20 tests; cm-orchestrator 17 (--features provider); clippy clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-15 21:36:01 -07:00
co-authored by Claude Opus 4.8
parent bd982943b8
commit b0c122b88d
5 changed files with 320 additions and 1 deletions
+111
View File
@@ -0,0 +1,111 @@
//! Canonical topology builders: instantiate a [`TopologyGraph`] of any
//! [`TopologyKind`] from a list of roles.
//!
//! This is the "template catalog" — it turns a kind + roles into a runnable
//! graph, and it is the candidate generator for the evolution/search layer.
//! Node ids are `n0..nk`; `roles[i]` is the role of `ni`.
use crate::graph::{Edge, EdgeKind, Node, TopologyGraph};
use crate::kind::TopologyKind;
use crate::TopologyError;
fn id(i: usize) -> String {
format!("n{i}")
}
fn star(n: usize, kind: EdgeKind) -> Vec<Edge> {
(1..n)
.map(|i| Edge { from: id(0), to: id(i), kind })
.collect()
}
fn chain(n: usize, close: bool) -> Vec<Edge> {
let mut edges: Vec<Edge> = (0..n.saturating_sub(1))
.map(|i| Edge { from: id(i), to: id(i + 1), kind: EdgeKind::PipesTo })
.collect();
if close && n > 1 {
edges.push(Edge { from: id(n - 1), to: id(0), kind: EdgeKind::PipesTo });
}
edges
}
fn complete(n: usize, kind: EdgeKind) -> Vec<Edge> {
let mut edges = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
edges.push(Edge { from: id(i), to: id(j), kind });
}
}
edges
}
/// Build a canonical graph of `kind` over `roles` (node `ni` plays `roles[i]`).
pub fn build(kind: TopologyKind, roles: &[&str]) -> Result<TopologyGraph, TopologyError> {
if roles.is_empty() {
return Err(TopologyError::Empty);
}
let n = roles.len();
let nodes: Vec<Node> = roles
.iter()
.enumerate()
.map(|(i, r)| Node::new(id(i), *r))
.collect();
use TopologyKind::*;
let edges = match kind {
Hierarchical => star(n, EdgeKind::DelegatesTo),
HubSpoke | StarMoe => star(n, EdgeKind::RoutesTo),
Market => star(n, EdgeKind::BidsTo),
Pipeline => chain(n, false),
Ring => chain(n, true),
Mesh => complete(n, EdgeKind::PeersWith),
Blackboard => complete(n, EdgeKind::ReadsWrites),
// Peer/parallel kinds wire structure at run time, not via edges.
Flat | Holacratic | Swarm | Debate => Vec::new(),
};
TopologyGraph::new(kind, nodes, edges)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::classify;
#[test]
fn builds_every_kind() {
let roles = ["coordinator", "researcher", "writer"];
for kind in TopologyKind::ALL {
let g = build(kind, &roles).expect("build");
assert_eq!(g.order(), 3);
assert_eq!(g.kind, kind);
g.validate().expect("valid");
}
}
#[test]
fn canonical_shapes_classify_back() {
let roles = ["a", "b", "c", "d"];
assert_eq!(
classify(&build(TopologyKind::Hierarchical, &roles).unwrap()).primary,
TopologyKind::HubSpoke // a 1→(n-1) star reads as hub/star structurally
);
assert_eq!(
classify(&build(TopologyKind::Pipeline, &roles).unwrap()).primary,
TopologyKind::Pipeline
);
assert_eq!(
classify(&build(TopologyKind::Ring, &roles).unwrap()).primary,
TopologyKind::Ring
);
assert_eq!(
classify(&build(TopologyKind::Mesh, &roles).unwrap()).primary,
TopologyKind::Mesh
);
}
#[test]
fn empty_roles_error() {
assert_eq!(build(TopologyKind::Flat, &[]), Err(TopologyError::Empty));
}
}
+2
View File
@@ -15,12 +15,14 @@
//! - [`heuristics`]— per-kind role distributions / optimization weights.
mod adapter;
mod builders;
mod classifier;
mod graph;
mod heuristics;
mod kind;
pub use adapter::{from_json, to_json};
pub use builders::build;
pub use classifier::{classify, Classification, GraphMetrics};
pub use graph::{Edge, EdgeKind, Node, TopologyGraph};
pub use heuristics::{heuristics, Heuristics};