//! The curated taxonomy of execution-meaningful topologies (v1). use serde::{Deserialize, Serialize}; /// An organizational topology pattern. The v1 set covers shapes that differ /// *in how a task executes*; governance/novel forms are deferred. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum TopologyKind { /// Tree: an orchestrator delegates down; results bubble up. Hierarchical, /// Autonomous peers with minimal coordination. Flat, /// Linear stages: output of stage n feeds stage n+1. Pipeline, /// Parallel attempts with consensus/aggregation. Swarm, /// Dense peer-to-peer exchange until convergence. Mesh, /// A central hub routes to spokes and aggregates. HubSpoke, /// A cycle: sequential round-trips, refining each lap. Ring, /// Router + experts (mixture-of-experts). StarMoe, /// Tasks auctioned to agents by fit/cost. Market, /// Agents read/write a shared workspace. Blackboard, /// Adversarial proposer vs critic rounds, then a judge. Debate, /// Self-organizing circles assign within roles. Holacratic, } /// How a topology kind actually executes. /// /// The twelve kinds above describe twelve distinct *intents*, but the /// orchestrator implements five execution patterns and maps the kinds onto /// them. So `Market` never auctions, `StarMoe` never routes to experts, `Ring` /// never cycles and `Holacratic` never self-organizes — each runs as whichever /// pattern it collapses to. Naming that here keeps the gap honest, lets the /// catalog API report it, and makes the collapse a single source of truth that /// `cm-orchestrator::plan_steps` matches on rather than duplicating. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ExecutionPattern { /// Coordinator plans, members work, coordinator aggregates. Hierarchical, /// Each node in sequence, output feeding the next. Pipeline, /// All nodes in parallel, then one aggregates. Swarm, /// Two exchange rounds, then node 0 aggregates. Mesh, /// Proposer, critic, judge. Debate, } impl ExecutionPattern { pub fn as_str(&self) -> &'static str { match self { ExecutionPattern::Hierarchical => "hierarchical", ExecutionPattern::Pipeline => "pipeline", ExecutionPattern::Swarm => "swarm", ExecutionPattern::Mesh => "mesh", ExecutionPattern::Debate => "debate", } } } impl TopologyKind { /// The execution pattern this kind actually runs as. pub fn execution_pattern(&self) -> ExecutionPattern { match self { TopologyKind::Hierarchical | TopologyKind::HubSpoke | TopologyKind::StarMoe | TopologyKind::Market => ExecutionPattern::Hierarchical, TopologyKind::Pipeline | TopologyKind::Ring => ExecutionPattern::Pipeline, TopologyKind::Swarm | TopologyKind::Flat | TopologyKind::Holacratic => { ExecutionPattern::Swarm } TopologyKind::Mesh | TopologyKind::Blackboard => ExecutionPattern::Mesh, TopologyKind::Debate => ExecutionPattern::Debate, } } /// Whether this kind's own semantics are realized at execution, or whether /// it is an alias for another kind's pattern. `false` means the label is /// currently aspirational — useful for a UI that shouldn't promise /// behaviour the engine doesn't implement. pub fn is_distinct_at_execution(&self) -> bool { matches!( self, TopologyKind::Hierarchical | TopologyKind::Pipeline | TopologyKind::Swarm | TopologyKind::Mesh | TopologyKind::Debate ) } /// Every supported kind, for iteration in tests/UIs/benchmarks. pub const ALL: [TopologyKind; 12] = [ TopologyKind::Hierarchical, TopologyKind::Flat, TopologyKind::Pipeline, TopologyKind::Swarm, TopologyKind::Mesh, TopologyKind::HubSpoke, TopologyKind::Ring, TopologyKind::StarMoe, TopologyKind::Market, TopologyKind::Blackboard, TopologyKind::Debate, TopologyKind::Holacratic, ]; /// The snake_case wire name (matches the serde representation). pub fn as_str(&self) -> &'static str { match self { TopologyKind::Hierarchical => "hierarchical", TopologyKind::Flat => "flat", TopologyKind::Pipeline => "pipeline", TopologyKind::Swarm => "swarm", TopologyKind::Mesh => "mesh", TopologyKind::HubSpoke => "hub_spoke", TopologyKind::Ring => "ring", TopologyKind::StarMoe => "star_moe", TopologyKind::Market => "market", TopologyKind::Blackboard => "blackboard", TopologyKind::Debate => "debate", TopologyKind::Holacratic => "holacratic", } } /// One-line human description. pub fn description(&self) -> &'static str { match self { TopologyKind::Hierarchical => "orchestrator delegates down; results bubble up", TopologyKind::Flat => "autonomous peers with minimal coordination", TopologyKind::Pipeline => "linear stages; each feeds the next", TopologyKind::Swarm => "parallel attempts with consensus/aggregation", TopologyKind::Mesh => "dense peer-to-peer exchange until convergence", TopologyKind::HubSpoke => "a central hub routes to spokes and aggregates", TopologyKind::Ring => "a cycle refining the result each lap", TopologyKind::StarMoe => "a router dispatches subtasks to experts", TopologyKind::Market => "tasks auctioned to agents by fit/cost", TopologyKind::Blackboard => "agents collaborate via a shared workspace", TopologyKind::Debate => "proposer vs critic rounds, then a judge", TopologyKind::Holacratic => "self-organizing circles assign within roles", } } } #[cfg(test)] mod tests { use super::*; #[test] fn as_str_matches_serde() { for k in TopologyKind::ALL { let json = serde_json::to_string(&k).unwrap(); assert_eq!(json, format!("\"{}\"", k.as_str())); let back: TopologyKind = serde_json::from_str(&json).unwrap(); assert_eq!(back, k); } } #[test] fn all_is_complete_and_unique() { let mut seen = std::collections::HashSet::new(); for k in TopologyKind::ALL { assert!(seen.insert(k.as_str()), "duplicate in ALL: {}", k.as_str()); assert!(!k.description().is_empty()); } assert_eq!(seen.len(), 12); } }