git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
38 KiB
Feature 25: Self-Organizing Graph Transformers
Overview
Problem Statement
Current graph transformers operate on fixed, manually designed topologies. The graph structure is either given as input (e.g., molecule graphs, social networks) or constructed once via nearest-neighbor heuristics (e.g., HNSW). In either case, the topology is static during inference and training: it does not grow, differentiate, or reorganize in response to the data distribution. This rigidity creates three fundamental bottlenecks:
- Topology-data mismatch: A graph constructed for one data distribution becomes suboptimal as the distribution shifts.
- No specialization: Every node and edge in the graph plays the same generic role -- there is no mechanism for nodes to develop distinct functional identities.
- No self-repair: When parts of the graph become corrupted or irrelevant, there is no process for replacing or regenerating damaged regions.
Biology solved these problems billions of years ago. Morphogenesis builds complex structures from simple rules. Embryonic development differentiates a single cell into hundreds of specialized types. Autopoiesis maintains living systems by continuously rebuilding their own components. These principles have been largely ignored in graph neural network design.
Proposed Solution
Self-Organizing Graph Transformers (SOGTs) are graph attention networks that grow, differentiate, and maintain their own topology through biologically-inspired developmental programs. The approach has three pillars:
- Morphogenetic Graph Networks: Turing pattern formation on graphs drives reaction-diffusion attention, creating spatially structured activation patterns that guide message passing and edge formation.
- Developmental Graph Programs: Graph grammars encode growth rules as L-system productions. Generic seed nodes differentiate into specialized types (hub nodes, boundary nodes, relay nodes) through a developmental program conditioned on local graph statistics.
- Autopoietic Graph Transformers: The network continuously rebuilds its own topology -- pruning dead edges, spawning new nodes, and adjusting attention weights -- to maintain a target coherence level, analogous to homeostasis in living systems.
Expected Benefits
- Adaptive Topology: 30-50% improvement in retrieval quality on distribution-shifting workloads
- Self-Specialization: Nodes develop distinct roles (hub, boundary, relay) reducing routing overhead by 40-60%
- Self-Repair: Automatic recovery from node/edge corruption with <5% transient degradation
- Architecture Search: Morphogenetic NAS discovers attention patterns 10x faster than random search
- Emergent Computation: Local attention rules give rise to global computational patterns (sorting, clustering, routing)
Novelty Claim
Unique Contribution: First graph transformer architecture that grows its own topology through morphogenetic, developmental, and autopoietic processes. Unlike neural architecture search (which optimizes a fixed search space), SOGTs develop continuously through biologically-grounded growth rules that operate at runtime.
Differentiators:
- Reaction-diffusion attention creates Turing patterns on graphs for structured activation
- L-system graph grammars encode developmental programs for node specialization
- Autopoietic maintenance loop continuously rebuilds topology to maintain coherence
- Cellular automata attention rules produce emergent global computation from local rules
- Morphogenetic NAS discovers novel attention architectures through growth processes
Biological Foundations
Morphogenesis and Turing Patterns
Alan Turing's 1952 paper "The Chemical Basis of Morphogenesis" demonstrated that two diffusing chemicals (an activator and an inhibitor) with different diffusion rates can spontaneously form stable spatial patterns: spots, stripes, and spirals. These reaction-diffusion systems explain leopard spots, zebrafish stripes, and fingerprint ridges.
On a graph, the Turing instability generalizes naturally. Each node holds concentrations of an activator a and inhibitor h. The dynamics follow the graph Laplacian:
da/dt = f(a, h) + D_a * L * a
dh/dt = g(a, h) + D_h * L * h
where L is the graph Laplacian, D_h >> D_a (inhibitor diffuses faster), and f, g encode local reaction kinetics. The key insight is that Turing patterns on graphs create natural attention masks: regions of high activator concentration attend to each other, while inhibitor barriers create boundaries between attention clusters.
Embryonic Development and Differentiation
A single fertilized cell becomes a human body with 200+ cell types through a developmental program. Key principles:
- Positional information: Cells read chemical gradients to determine their position and fate.
- Inductive signaling: Cells signal neighbors to change type.
- Competence windows: Cells can only respond to certain signals during specific developmental stages.
- Canalization: Development is robust to perturbations -- the same endpoint is reached from varied starting conditions.
For graph transformers, these principles translate to: nodes read local graph statistics (degree, centrality, neighborhood composition) to determine their functional role; they signal neighbors through message passing to coordinate specialization; and developmental stages gate which transformations are available at each growth step.
Autopoiesis and Self-Maintenance
Autopoiesis (Maturana and Varela, 1972) describes systems that continuously produce and replace their own components. A living cell is autopoietic: it synthesizes the membrane that bounds it, the enzymes that catalyze reactions, and the DNA that encodes those enzymes. The system maintains itself through circular causality.
For graph transformers, autopoiesis means: the attention mechanism produces the topology that shapes the attention mechanism. Dead edges are pruned. Overloaded nodes are split. Missing connections are grown. The graph maintains a target coherence level (measurable via ruvector-coherence) through continuous self-modification.
Technical Design
Architecture Diagram
Data Distribution
|
+--------v--------+
| Seed Graph |
| (initial K |
| nodes) |
+--------+--------+
|
+--------------+--------------+
| | |
+--------v-------+ +---v----+ +-------v--------+
| Morphogenetic | | Devel- | | Autopoietic |
| Field Engine | | opment | | Maintenance |
| | | Program| | Loop |
| Turing pattern | | L-sys | | Coherence- |
| on graph | | grammar| | gated rebuild |
+--------+-------+ +---+----+ +-------+--------+
| | |
+------+-------+------+-------+
| |
+------v------+ +----v-------+
| Topology | | Node Type |
| Growth | | Specialize |
| (new edges/ | | (hub/relay/|
| nodes) | | boundary) |
+------+------+ +----+-------+
| |
+------+-------+
|
+--------v--------+
| Self-Organizing |
| Graph Attention |
| Layer |
+--------+--------+
|
+--------v--------+
| Query / Embed |
| / Route |
+-----------------+
Morphogenetic Field Detail:
Node Activator (a) Node Inhibitor (h)
+---+---+---+---+ +---+---+---+---+
|0.9|0.1|0.8|0.2| |0.1|0.8|0.2|0.9|
+---+---+---+---+ +---+---+---+---+
|0.2|0.7|0.1|0.9| |0.7|0.2|0.8|0.1|
+---+---+---+---+ +---+---+---+---+
Attention Mask = sigma(a - threshold)
High-a nodes form attention clusters
High-h boundaries separate clusters
Core Data Structures
/// Configuration for Self-Organizing Graph Transformer
#[derive(Debug, Clone)]
pub struct SelfOrganizingConfig {
/// Initial seed graph size
pub seed_nodes: usize,
/// Maximum graph size (growth limit)
pub max_nodes: usize,
/// Embedding dimension
pub embed_dim: usize,
/// Morphogenetic field parameters
pub morpho: MorphogeneticConfig,
/// Developmental program parameters
pub development: DevelopmentalConfig,
/// Autopoietic maintenance parameters
pub autopoiesis: AutopoieticConfig,
/// Growth phase schedule
pub phases: Vec<GrowthPhase>,
}
/// Morphogenetic field configuration (Turing patterns on graphs)
#[derive(Debug, Clone)]
pub struct MorphogeneticConfig {
/// Activator diffusion rate
pub d_activator: f32,
/// Inhibitor diffusion rate (must be > d_activator)
pub d_inhibitor: f32,
/// Reaction kinetics: activator self-enhancement rate
pub rho_a: f32,
/// Reaction kinetics: inhibitor production rate
pub rho_h: f32,
/// Activator decay rate
pub mu_a: f32,
/// Inhibitor decay rate
pub mu_h: f32,
/// Number of reaction-diffusion steps per forward pass
pub rd_steps: usize,
/// Threshold for activator-based attention gating
pub attention_threshold: f32,
}
impl Default for MorphogeneticConfig {
fn default() -> Self {
Self {
d_activator: 0.01,
d_inhibitor: 0.1, // 10x faster diffusion
rho_a: 0.08,
rho_h: 0.12,
mu_a: 0.03,
mu_h: 0.06,
rd_steps: 10,
attention_threshold: 0.5,
}
}
}
/// Node functional types arising from developmental specialization
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NodeType {
/// Undifferentiated seed node
Stem,
/// High-degree hub node (routes between clusters)
Hub,
/// Cluster boundary node (separates attention groups)
Boundary,
/// Internal relay node (local message passing)
Relay,
/// Sensory node (interfaces with external data)
Sensory,
/// Memory node (long-term information storage)
Memory,
}
/// Developmental program configuration
#[derive(Debug, Clone)]
pub struct DevelopmentalConfig {
/// L-system axiom (initial production)
pub axiom: Vec<NodeType>,
/// Production rules: (predecessor, condition, successor pattern)
pub rules: Vec<ProductionRule>,
/// Maximum developmental steps
pub max_steps: usize,
/// Competence window: (min_step, max_step) per rule
pub competence_windows: Vec<(usize, usize)>,
}
/// A production rule in the developmental graph grammar
#[derive(Debug, Clone)]
pub struct ProductionRule {
/// Node type that this rule applies to
pub predecessor: NodeType,
/// Condition: local graph statistic threshold
pub condition: DevelopmentalCondition,
/// Successor: what the node becomes + new nodes spawned
pub successor: Vec<NodeType>,
/// Edge pattern for newly created nodes
pub edge_pattern: EdgePattern,
}
/// Conditions for developmental rule activation
#[derive(Debug, Clone)]
pub enum DevelopmentalCondition {
/// Node degree exceeds threshold
DegreeAbove(usize),
/// Node betweenness centrality exceeds threshold
CentralityAbove(f32),
/// Activator concentration exceeds threshold
ActivatorAbove(f32),
/// Inhibitor concentration exceeds threshold
InhibitorAbove(f32),
/// Neighbor composition: fraction of type T exceeds threshold
NeighborFraction(NodeType, f32),
/// Always applies
Always,
}
/// Edge creation patterns for developmental rules
#[derive(Debug, Clone)]
pub enum EdgePattern {
/// Connect to parent only
ParentOnly,
/// Connect to parent and all parent neighbors
InheritNeighborhood,
/// Connect to k nearest nodes by embedding distance
KNearest(usize),
/// Connect to nodes with matching activator pattern
MorphogeneticAffinity,
}
/// Autopoietic maintenance configuration
#[derive(Debug, Clone)]
pub struct AutopoieticConfig {
/// Target coherence level (from ruvector-coherence)
pub target_coherence: f32,
/// Coherence tolerance band (maintain within +/- tolerance)
pub coherence_tolerance: f32,
/// Edge pruning threshold: remove edges with attention < threshold
pub prune_threshold: f32,
/// Node splitting threshold: split nodes with degree > threshold
pub split_degree_threshold: usize,
/// Edge growth rate: max new edges per maintenance cycle
pub max_new_edges_per_cycle: usize,
/// Maintenance cycle interval (every N forward passes)
pub cycle_interval: usize,
}
/// Growth phase in the developmental schedule
#[derive(Debug, Clone)]
pub struct GrowthPhase {
/// Phase name
pub name: String,
/// Duration in forward passes
pub duration: usize,
/// Which subsystems are active
pub morpho_active: bool,
pub development_active: bool,
pub autopoiesis_active: bool,
/// Growth rate multiplier
pub growth_rate: f32,
}
Key Algorithms
1. Morphogenetic Field Update (Reaction-Diffusion on Graph)
/// Morphogenetic field state for the graph
pub struct MorphogeneticField {
/// Activator concentration per node
activator: Vec<f32>,
/// Inhibitor concentration per node
inhibitor: Vec<f32>,
/// Graph Laplacian (sparse)
laplacian: Vec<(usize, usize, f32)>,
/// Configuration
config: MorphogeneticConfig,
}
impl MorphogeneticField {
/// Run one step of reaction-diffusion on the graph.
///
/// Uses the Gierer-Meinhardt model:
/// da/dt = rho_a * (a^2 / h) - mu_a * a + D_a * L * a
/// dh/dt = rho_h * a^2 - mu_h * h + D_h * L * h
fn step(&mut self, dt: f32) {
let n = self.activator.len();
let mut da = vec![0.0f32; n];
let mut dh = vec![0.0f32; n];
// Reaction kinetics (Gierer-Meinhardt)
for i in 0..n {
let a = self.activator[i];
let h = self.inhibitor[i].max(1e-6); // avoid division by zero
da[i] += self.config.rho_a * (a * a / h) - self.config.mu_a * a;
dh[i] += self.config.rho_h * a * a - self.config.mu_h * h;
}
// Diffusion via graph Laplacian
for &(src, dst, weight) in &self.laplacian {
let diff_a = self.activator[dst] - self.activator[src];
let diff_h = self.inhibitor[dst] - self.inhibitor[src];
da[src] += self.config.d_activator * weight * diff_a;
dh[src] += self.config.d_inhibitor * weight * diff_h;
}
// Euler integration
for i in 0..n {
self.activator[i] = (self.activator[i] + dt * da[i]).max(0.0);
self.inhibitor[i] = (self.inhibitor[i] + dt * dh[i]).max(0.0);
}
}
/// Compute attention mask from activator field.
/// Nodes with activator above threshold attend to each other.
fn attention_mask(&self) -> Vec<bool> {
self.activator.iter()
.map(|&a| a > self.config.attention_threshold)
.collect()
}
/// Compute morphogenetic affinity between two nodes.
/// Nodes with similar activator/inhibitor ratios have high affinity.
fn affinity(&self, i: usize, j: usize) -> f32 {
let ratio_i = self.activator[i] / self.inhibitor[i].max(1e-6);
let ratio_j = self.activator[j] / self.inhibitor[j].max(1e-6);
let diff = (ratio_i - ratio_j).abs();
(-diff * diff).exp() // Gaussian affinity
}
}
2. Developmental Program (L-System Graph Grammar)
/// Developmental program executor
pub struct DevelopmentalProgram {
/// Current developmental step
step: usize,
/// Production rules
rules: Vec<ProductionRule>,
/// Competence windows per rule
competence: Vec<(usize, usize)>,
/// Node type assignments
node_types: Vec<NodeType>,
/// Graph adjacency (mutable during development)
adjacency: Vec<Vec<usize>>,
/// Node embeddings
embeddings: Vec<Vec<f32>>,
}
impl DevelopmentalProgram {
/// Execute one developmental step.
///
/// For each node, check if any production rule applies:
/// 1. The node type matches the rule predecessor
/// 2. The condition is satisfied
/// 3. The current step is within the competence window
///
/// If so, apply the rule: change node type and/or spawn new nodes.
fn develop_step(
&mut self,
field: &MorphogeneticField,
max_nodes: usize,
) -> Vec<DevelopmentalEvent> {
let mut events = Vec::new();
let current_n = self.node_types.len();
// Collect applicable rules (avoid borrow conflicts)
let mut applications: Vec<(usize, usize)> = Vec::new(); // (node_idx, rule_idx)
for node_idx in 0..current_n {
for (rule_idx, rule) in self.rules.iter().enumerate() {
// Check competence window
let (min_step, max_step) = self.competence[rule_idx];
if self.step < min_step || self.step > max_step {
continue;
}
// Check predecessor type
if self.node_types[node_idx] != rule.predecessor {
continue;
}
// Check condition
if self.check_condition(node_idx, &rule.condition, field) {
applications.push((node_idx, rule_idx));
break; // one rule per node per step
}
}
}
// Apply rules
for (node_idx, rule_idx) in applications {
if self.node_types.len() >= max_nodes {
break;
}
let rule = &self.rules[rule_idx];
// First element of successor replaces the node's type
if let Some(&new_type) = rule.successor.first() {
let old_type = self.node_types[node_idx];
self.node_types[node_idx] = new_type;
events.push(DevelopmentalEvent::Differentiate {
node: node_idx,
from: old_type,
to: new_type,
});
}
// Remaining elements spawn new nodes
for &spawn_type in rule.successor.iter().skip(1) {
let new_idx = self.node_types.len();
if new_idx >= max_nodes { break; }
self.node_types.push(spawn_type);
// Create embedding as perturbation of parent
let parent_emb = self.embeddings[node_idx].clone();
let new_emb = perturb_embedding(&parent_emb, 0.01);
self.embeddings.push(new_emb);
// Create edges based on pattern
let new_edges = match &rule.edge_pattern {
EdgePattern::ParentOnly => vec![node_idx],
EdgePattern::InheritNeighborhood => {
let mut edges = vec![node_idx];
edges.extend_from_slice(&self.adjacency[node_idx]);
edges
}
EdgePattern::KNearest(k) => {
self.k_nearest(new_idx, *k)
}
EdgePattern::MorphogeneticAffinity => {
self.morpho_nearest(new_idx, field, 4)
}
};
self.adjacency.push(new_edges.clone());
for &neighbor in &new_edges {
if neighbor < self.adjacency.len() {
self.adjacency[neighbor].push(new_idx);
}
}
events.push(DevelopmentalEvent::Spawn {
parent: node_idx,
child: new_idx,
child_type: spawn_type,
});
}
}
self.step += 1;
events
}
/// Check whether a developmental condition is satisfied for a node.
fn check_condition(
&self,
node_idx: usize,
condition: &DevelopmentalCondition,
field: &MorphogeneticField,
) -> bool {
match condition {
DevelopmentalCondition::DegreeAbove(threshold) => {
self.adjacency[node_idx].len() > *threshold
}
DevelopmentalCondition::ActivatorAbove(threshold) => {
field.activator[node_idx] > *threshold
}
DevelopmentalCondition::InhibitorAbove(threshold) => {
field.inhibitor[node_idx] > *threshold
}
DevelopmentalCondition::NeighborFraction(target_type, threshold) => {
let neighbors = &self.adjacency[node_idx];
if neighbors.is_empty() { return false; }
let count = neighbors.iter()
.filter(|&&n| self.node_types[n] == *target_type)
.count();
(count as f32 / neighbors.len() as f32) > *threshold
}
DevelopmentalCondition::CentralityAbove(_threshold) => {
// Approximated via degree centrality for efficiency
let degree = self.adjacency[node_idx].len() as f32;
let max_degree = self.adjacency.iter()
.map(|adj| adj.len())
.max()
.unwrap_or(1) as f32;
(degree / max_degree) > 0.5
}
DevelopmentalCondition::Always => true,
}
}
}
/// Events produced by the developmental program
#[derive(Debug, Clone)]
pub enum DevelopmentalEvent {
/// A node changed its functional type
Differentiate { node: usize, from: NodeType, to: NodeType },
/// A new node was spawned
Spawn { parent: usize, child: usize, child_type: NodeType },
/// An edge was pruned
Prune { src: usize, dst: usize },
}
/// Perturb an embedding with small Gaussian noise
fn perturb_embedding(emb: &[f32], scale: f32) -> Vec<f32> {
emb.iter().enumerate()
.map(|(i, &v)| {
// Deterministic pseudo-noise based on index
let noise = ((i as f32 * 0.618033988) % 1.0 - 0.5) * 2.0 * scale;
v + noise
})
.collect()
}
3. Autopoietic Maintenance Loop
/// Autopoietic maintenance system
pub struct AutopoieticMaintainer {
config: AutopoieticConfig,
/// Forward pass counter
pass_count: usize,
/// Running coherence history
coherence_history: Vec<f32>,
}
impl AutopoieticMaintainer {
/// Execute one maintenance cycle if due.
///
/// Measures current coherence (via ruvector-coherence metrics),
/// then adjusts topology to stay within the target band.
fn maybe_maintain(
&mut self,
adjacency: &mut Vec<Vec<usize>>,
node_types: &mut Vec<NodeType>,
attention_weights: &[Vec<(usize, f32)>],
embeddings: &[Vec<f32>],
) -> Vec<MaintenanceAction> {
self.pass_count += 1;
if self.pass_count % self.config.cycle_interval != 0 {
return Vec::new();
}
let mut actions = Vec::new();
let coherence = self.measure_coherence(attention_weights);
self.coherence_history.push(coherence);
let target = self.config.target_coherence;
let tol = self.config.coherence_tolerance;
if coherence < target - tol {
// Coherence too low: grow edges to increase connectivity
let new_edges = self.grow_edges(adjacency, embeddings);
actions.extend(new_edges);
} else if coherence > target + tol {
// Coherence too high: prune weak edges
let pruned = self.prune_edges(adjacency, attention_weights);
actions.extend(pruned);
}
// Always check for overloaded nodes
let splits = self.split_overloaded(adjacency, node_types, embeddings);
actions.extend(splits);
actions
}
/// Measure coherence as mean attention weight across active edges.
fn measure_coherence(&self, attention_weights: &[Vec<(usize, f32)>]) -> f32 {
let mut total_weight = 0.0f32;
let mut edge_count = 0usize;
for node_weights in attention_weights {
for &(_neighbor, weight) in node_weights {
total_weight += weight;
edge_count += 1;
}
}
if edge_count == 0 { return 0.0; }
total_weight / edge_count as f32
}
/// Prune edges with attention weight below threshold.
fn prune_edges(
&self,
adjacency: &mut Vec<Vec<usize>>,
attention_weights: &[Vec<(usize, f32)>],
) -> Vec<MaintenanceAction> {
let mut actions = Vec::new();
for (src, node_weights) in attention_weights.iter().enumerate() {
let to_prune: Vec<usize> = node_weights.iter()
.filter(|&&(_, w)| w < self.config.prune_threshold)
.map(|&(dst, _)| dst)
.collect();
for dst in to_prune {
adjacency[src].retain(|&n| n != dst);
actions.push(MaintenanceAction::PruneEdge { src, dst });
}
}
actions
}
/// Split nodes whose degree exceeds the threshold.
fn split_overloaded(
&self,
adjacency: &mut Vec<Vec<usize>>,
node_types: &mut Vec<NodeType>,
embeddings: &[Vec<f32>],
) -> Vec<MaintenanceAction> {
let mut actions = Vec::new();
let n = adjacency.len();
for i in 0..n {
if adjacency[i].len() > self.config.split_degree_threshold {
// Split: new node takes half the edges
let mid = adjacency[i].len() / 2;
let split_edges: Vec<usize> = adjacency[i].drain(mid..).collect();
let new_idx = adjacency.len();
adjacency.push(split_edges.clone());
node_types.push(node_types[i]);
// Reconnect transferred edges
for &neighbor in &split_edges {
if neighbor < adjacency.len() {
// Replace old -> new in neighbor lists
if let Some(pos) = adjacency[neighbor].iter().position(|&n| n == i) {
adjacency[neighbor][pos] = new_idx;
}
}
}
// Connect the two halves
adjacency[i].push(new_idx);
adjacency[new_idx].push(i);
actions.push(MaintenanceAction::SplitNode {
original: i,
new_node: new_idx,
edges_transferred: split_edges.len(),
});
}
}
actions
}
/// Grow new edges to increase coherence.
fn grow_edges(
&self,
adjacency: &mut Vec<Vec<usize>>,
embeddings: &[Vec<f32>],
) -> Vec<MaintenanceAction> {
let mut actions = Vec::new();
let mut added = 0;
// Find pairs with high embedding similarity but no edge
for i in 0..adjacency.len() {
if added >= self.config.max_new_edges_per_cycle { break; }
for j in (i + 1)..adjacency.len() {
if added >= self.config.max_new_edges_per_cycle { break; }
if adjacency[i].contains(&j) { continue; }
let sim = cosine_similarity(&embeddings[i], &embeddings[j]);
if sim > 0.8 {
adjacency[i].push(j);
adjacency[j].push(i);
added += 1;
actions.push(MaintenanceAction::GrowEdge { src: i, dst: j, similarity: sim });
}
}
}
actions
}
}
/// Actions taken by the autopoietic maintainer
#[derive(Debug, Clone)]
pub enum MaintenanceAction {
PruneEdge { src: usize, dst: usize },
GrowEdge { src: usize, dst: usize, similarity: f32 },
SplitNode { original: usize, new_node: usize, edges_transferred: usize },
}
fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a < 1e-8 || norm_b < 1e-8 { return 0.0; }
dot / (norm_a * norm_b)
}
4. Cellular Automata Attention Rules
/// Cellular automaton rule for graph attention updates.
///
/// Each node updates its attention state based on the attention states
/// of its neighbors, analogous to Conway's Game of Life on a graph.
pub struct CellularAttentionRule {
/// Birth threshold: node activates if >= birth neighbors are active
pub birth_threshold: usize,
/// Survival range: node stays active if neighbors in [lo, hi]
pub survival_lo: usize,
pub survival_hi: usize,
/// Refractory period: steps before reactivation after deactivation
pub refractory: usize,
}
impl CellularAttentionRule {
/// Update attention states for all nodes.
fn update(
&self,
states: &mut Vec<CellState>,
adjacency: &[Vec<usize>],
) {
let n = states.len();
let old_states: Vec<CellState> = states.clone();
for i in 0..n {
let active_neighbors = adjacency[i].iter()
.filter(|&&j| old_states[j].active)
.count();
match &mut states[i] {
s if s.active => {
// Survival check
if active_neighbors < self.survival_lo
|| active_neighbors > self.survival_hi
{
s.active = false;
s.refractory_remaining = self.refractory;
}
}
s if s.refractory_remaining > 0 => {
s.refractory_remaining -= 1;
}
s => {
// Birth check
if active_neighbors >= self.birth_threshold {
s.active = true;
}
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct CellState {
pub active: bool,
pub refractory_remaining: usize,
}
RuVector Integration Points
Affected Crates/Modules
-
ruvector-domain-expansion: TheDomainExpansionEnginealready implements cross-domain transfer withMetaThompsonEngine. Morphogenetic fields extend this with spatial structure over the domain graph -- each domain node carries activator/inhibitor concentrations that influence the transfer policy selection. ThePolicyKernelpopulation search can be guided by developmental programs that specialize kernels into domain-specific roles. -
ruvector-attention: The existing 18+ attention mechanisms (morphological, topology, sheaf, PDE, transport, curvature, sparse, flash, hyperbolic, MoE) serve as the building blocks that the self-organizing system selects and composes. Thetopology/module's gated attention maps directly to morphogenetic field gating. Thesheaf/module's restriction maps provide the mathematical framework for boundary-creating attention between differentiated node types. -
ruvector-coherence: The coherence engine (spectral.rs,quality.rs,metrics.rs) provides the feedback signal for the autopoietic loop. The target coherence fromAutopoieticConfigcorresponds directly to the spectral coherence thresholds used in the mincut-gated-transformer. Coherence measurements drive the grow/prune/split decisions. -
ruvector-mincut: Topology optimization via mincut provides the theoretical foundation for the pruning phase of autopoiesis. The mincut-gated-transformer'sGateController(energy gates, early exit) directly corresponds to morphogenetic field gating -- both decide which computation paths are active based on a learned signal. -
ruvector-nervous-system: The dendritic coincidence detection (Dendrite,DendriticTree,PlateauPotential) maps directly to the developmental differentiation model. Neurons differentiate based on their dendritic input patterns, just as graph nodes differentiate based on local topology. Theplasticity/epropmodule's e-prop learning rule can guide morphogenetic field parameter adaptation. TheGlobalWorkspaceandOscillatoryRouterprovide the coordination substrate for cellular automata attention. -
ruvector-gnn: The core GNN layer (layer.rs), training loop (training.rs), and elastic weight consolidation (ewc.rs) provide the foundation. EWC is essential for developmental programs: when a node differentiates, the weights associated with its old type must be protected via Fisher-information-weighted regularization, preventing catastrophic forgetting of learned representations.
New Modules to Create
ruvector-gnn/src/self_organizing/
mod.rs
morphogenetic.rs # Reaction-diffusion field on graph
developmental.rs # L-system graph grammar executor
autopoietic.rs # Self-maintenance loop
cellular_automata.rs # CA-based attention rules
growth_phase.rs # Phase scheduling
metrics.rs # Growth statistics and visualization
Future Roadmap
2030: Self-Growing Graph Architectures
By 2030, the developmental program becomes a learned object rather than a hand-designed grammar. The production rules themselves are parameterized by neural networks trained via reinforcement learning on downstream task performance. Key milestones:
- Learned Growth Rules: A meta-network predicts which production rule to apply at each developmental step, conditioned on global graph statistics and task loss gradients.
- Topology-Aware Data Distribution Matching: The morphogenetic field parameters are optimized so that the resulting attention cluster structure matches the data distribution's intrinsic geometry (e.g., manifold structure, cluster hierarchy).
- Federated Self-Organization: Multiple SOGT instances running on different data partitions exchange developmental signals (activator/inhibitor concentrations) to coordinate topology across distributed deployments.
- Morphogenetic Architecture Search: Instead of NAS over a fixed search space, the search space itself grows through morphogenetic processes. Novel attention mechanisms emerge as stable Turing patterns on the architecture search graph.
2036: Autonomous Graph Systems
By 2036, the self-organizing graph transformer becomes a fully autonomous system that evolves new attention mechanisms through its developmental program:
- Open-Ended Evolution: The graph system exhibits open-ended evolution -- it continuously produces novel structures that are not repetitions of previous states. New node types, edge types, and attention mechanisms emerge without human intervention.
- Developmental Canalization: The system develops robust developmental trajectories that reliably produce high-performing topologies despite environmental perturbation, analogous to biological canalization.
- Morphogenetic Memory: Growth histories are stored as compressed developmental programs (analogous to DNA) that can be replayed, mutated, and recombined for evolutionary search over architectures.
- Autopoietic Resilience at Scale: Production graph systems with millions of nodes self-repair within milliseconds of node failure, maintaining 99.999% coherence through continuous autopoietic maintenance without human intervention.
Implementation Phases
Phase 1: Morphogenetic Fields (3 weeks)
- Implement reaction-diffusion on graph using graph Laplacian
- Integrate Turing pattern attention masking with existing ruvector-attention
- Validate pattern formation on synthetic graphs
- Unit tests for stability and convergence
Phase 2: Developmental Programs (4 weeks)
- Implement L-system graph grammar with production rules
- Add competence windows and node differentiation
- Integrate with morphogenetic fields for condition checking
- Test developmental trajectories on benchmark graphs
Phase 3: Autopoietic Maintenance (3 weeks)
- Implement coherence-gated topology maintenance using ruvector-coherence
- Add edge pruning, node splitting, and edge growth
- Integrate with existing HNSW index maintenance
- Stress tests for self-repair under node deletion
Phase 4: Integration and Evaluation (2 weeks)
- Combine all three subsystems into unified SOGT layer
- Benchmark against static graph transformers on distribution-shifting workloads
- Measure self-repair latency and coherence maintenance
- Document growth phase scheduling heuristics
Success Metrics
| Metric | Target |
|---|---|
| Topology Adaptation Speed | <100ms to respond to distribution shift |
| Node Specialization Accuracy | >85% correct functional type assignment |
| Self-Repair Recovery Time | <50ms to recover from 10% node deletion |
| Coherence Maintenance | Within +/-5% of target coherence |
| Retrieval Quality (shifting workload) | 30-50% improvement over static topology |
| Growth Overhead | <15% additional computation per forward pass |
| Morphogenetic Pattern Stability | Converge within 50 reaction-diffusion steps |
Risks and Mitigations
-
Risk: Uncontrolled Growth
- Mitigation: Hard
max_nodescap, growth rate limits per phase, energy-based cost for node creation
- Mitigation: Hard
-
Risk: Developmental Instability
- Mitigation: Canalization through competence windows, EWC-protected weight consolidation during differentiation
-
Risk: Morphogenetic Pattern Collapse
- Mitigation: Validated Turing parameter regimes (D_h/D_a > 5), stochastic perturbation to break symmetry
-
Risk: Autopoietic Oscillation
- Mitigation: Hysteresis in coherence thresholds (different thresholds for grow vs. prune), exponential moving average smoothing
-
Risk: Performance Overhead
- Mitigation: Amortize maintenance over many forward passes, sparse Laplacian operations, early-exit from growth phases when targets are met