feat(topology): template builders + evolution/QD search (P5)
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:
co-authored by
Claude Opus 4.8
parent
bd982943b8
commit
b0c122b88d
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user