@@ -2561,6 +2561,10 @@ def _spawn_sub_epi(
25612561 This implements canonical THOL: "reorganizes external experience into
25622562 internal structure without external instruction".
25632563
2564+ ARCHITECTURAL: Sub-EPIs are created as independent NFR nodes to enable
2565+ operational fractality - recursive operator application, hierarchical metrics,
2566+ and multi-level bifurcation.
2567+
25642568 Parameters
25652569 ----------
25662570 G : TNFRGraph
@@ -2573,12 +2577,13 @@ def _spawn_sub_epi(
25732577 Bifurcation threshold that was exceeded
25742578 """
25752579 from ..alias import get_attr , set_attr
2576- from ..constants .aliases import ALIAS_EPI , ALIAS_VF
2580+ from ..constants .aliases import ALIAS_EPI , ALIAS_VF , ALIAS_THETA
25772581 from .metabolism import capture_network_signals , metabolize_signals_into_subepi
25782582
25792583 # Get current node state
25802584 parent_epi = float (get_attr (G .nodes [node ], ALIAS_EPI , 0.0 ))
25812585 parent_vf = float (get_attr (G .nodes [node ], ALIAS_VF , 1.0 ))
2586+ parent_theta = float (get_attr (G .nodes [node ], ALIAS_THETA , 0.0 ))
25822587
25832588 # Check if vibrational metabolism is enabled
25842589 metabolic_enabled = G .graph .get ("THOL_METABOLIC_ENABLED" , True )
@@ -2606,24 +2611,33 @@ def _spawn_sub_epi(
26062611 complexity_weight = complexity_weight ,
26072612 )
26082613
2609- # Store sub-EPI in node's sub_epis list
2610- sub_epis = G .nodes [node ].get ("sub_epis" , [])
2611-
26122614 # Get current timestamp from glyph history length
26132615 timestamp = len (G .nodes [node ].get ("glyph_history" , []))
26142616
2615- # Store sub-EPI with metabolic metadata
2617+ # ARCHITECTURAL: Create sub-EPI as independent NFR node
2618+ # This enables operational fractality - recursive operators, hierarchical metrics
2619+ sub_node_id = self ._create_sub_node (
2620+ G ,
2621+ parent_node = node ,
2622+ sub_epi = sub_epi_value ,
2623+ parent_vf = parent_vf ,
2624+ parent_theta = parent_theta ,
2625+ )
2626+
2627+ # Store sub-EPI metadata for telemetry and backward compatibility
26162628 sub_epi_record = {
26172629 "epi" : sub_epi_value ,
26182630 "vf" : parent_vf ,
26192631 "timestamp" : timestamp ,
26202632 "d2_epi" : d2_epi ,
26212633 "tau" : tau ,
2622- # NEW: Metabolic metadata
2634+ "node_id" : sub_node_id , # Reference to independent node
26232635 "metabolized" : network_signals is not None and metabolic_enabled ,
26242636 "network_signals" : network_signals ,
26252637 }
26262638
2639+ # Keep metadata list for telemetry/metrics backward compatibility
2640+ sub_epis = G .nodes [node ].get ("sub_epis" , [])
26272641 sub_epis .append (sub_epi_record )
26282642 G .nodes [node ]["sub_epis" ] = sub_epis
26292643
@@ -2649,6 +2663,82 @@ def _spawn_sub_epi(
26492663 }
26502664 )
26512665
2666+ def _create_sub_node (
2667+ self ,
2668+ G : TNFRGraph ,
2669+ parent_node : Any ,
2670+ sub_epi : float ,
2671+ parent_vf : float ,
2672+ parent_theta : float ,
2673+ ) -> str :
2674+ """Create sub-EPI as independent NFR node for operational fractality.
2675+
2676+ Sub-nodes are full TNFR nodes that can have operators applied, bifurcate
2677+ recursively, and contribute to hierarchical metrics.
2678+
2679+ Parameters
2680+ ----------
2681+ G : TNFRGraph
2682+ Graph containing the parent node
2683+ parent_node : Any
2684+ Parent node identifier
2685+ sub_epi : float
2686+ EPI value for the sub-node
2687+ parent_vf : float
2688+ Parent's structural frequency (inherited with damping)
2689+ parent_theta : float
2690+ Parent's phase (inherited)
2691+
2692+ Returns
2693+ -------
2694+ str
2695+ Identifier of the newly created sub-node
2696+ """
2697+ from ..constants import EPI_PRIMARY , VF_PRIMARY , THETA_PRIMARY , DNFR_PRIMARY
2698+ from ..dynamics import set_delta_nfr_hook
2699+
2700+ # Generate unique sub-node ID
2701+ sub_nodes_list = G .nodes [parent_node ].get ("sub_nodes" , [])
2702+ sub_index = len (sub_nodes_list )
2703+ sub_node_id = f"{ parent_node } _sub_{ sub_index } "
2704+
2705+ # Get parent hierarchy level
2706+ parent_hierarchy_level = G .nodes [parent_node ].get ("hierarchy_level" , 0 )
2707+
2708+ # Inherit parent's vf with slight damping (canonical: 95%)
2709+ sub_vf = parent_vf * 0.95
2710+
2711+ # Create the sub-node with full TNFR state
2712+ G .add_node (
2713+ sub_node_id ,
2714+ ** {
2715+ EPI_PRIMARY : float (sub_epi ),
2716+ VF_PRIMARY : float (sub_vf ),
2717+ THETA_PRIMARY : float (parent_theta ),
2718+ DNFR_PRIMARY : 0.0 ,
2719+ "parent_node" : parent_node ,
2720+ "hierarchy_level" : parent_hierarchy_level + 1 ,
2721+ "epi_history" : [float (sub_epi )], # Initialize history for future bifurcation
2722+ "glyph_history" : [],
2723+ },
2724+ )
2725+
2726+ # Ensure ΔNFR hook is set for the sub-node
2727+ # (inherits from graph-level hook, but ensure it's activated)
2728+ if hasattr (G , "graph" ) and "_delta_nfr_hook" in G .graph :
2729+ # Hook already set at graph level, will apply to sub-node automatically
2730+ pass
2731+
2732+ # Track sub-node in parent
2733+ sub_nodes_list .append (sub_node_id )
2734+ G .nodes [parent_node ]["sub_nodes" ] = sub_nodes_list
2735+
2736+ # Track hierarchy in graph metadata
2737+ hierarchy = G .graph .setdefault ("hierarchy" , {})
2738+ hierarchy .setdefault (parent_node , []).append (sub_node_id )
2739+
2740+ return sub_node_id
2741+
26522742 def _validate_preconditions (self , G : TNFRGraph , node : Any ) -> None :
26532743 """Validate THOL-specific preconditions."""
26542744 from .preconditions import validate_self_organization
0 commit comments