diff --git a/.coverage b/.coverage index 7802221..2361a59 100644 Binary files a/.coverage and b/.coverage differ diff --git a/examples/run_simulation.py b/examples/run_simulation.py new file mode 100755 index 0000000..c8d5e64 --- /dev/null +++ b/examples/run_simulation.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +""" +Demonstration script executing the complete governed simulation engine for the U.F.O. architecture. +Shows Single-Agent and Multi-Agent simulations driving through Green -> Yellow -> Red stability transitions. +""" + +from __future__ import annotations +import os +import numpy as np + +from radial_membrane_ai.ufo_engine import ( + SingleAgentEngine, + MultiAgentEngine, + CostWeights, + StabilityBandConfig +) +from radial_membrane_ai.ufo_engine.serialization import export_simulation_results_to_json, ledger_to_json + + +def main() -> None: + print("=" * 70) + print("🛸 U.F.O. GOVERNED SIMULATION ENGINE DEMO 🛸") + print("=" * 70) + + # Make sure logs directory exists + os.makedirs("logs", exist_ok=True) + + # ------------------------------------------------------------------------- + # 1. Single-Agent Simulation Run + # ------------------------------------------------------------------------- + print("\n--- Running Single-Agent Engine ---") + # Using strict cost weights to emphasize stability band transitions + strict_weights = CostWeights.get_preset("strict") + # Low thresholds to demonstrate Green -> Yellow -> Red transitions beautifully + band_config = StabilityBandConfig(v_green=0.03, v_red=0.15) + + single_engine = SingleAgentEngine( + cost_weights=strict_weights, + band_config=band_config + ) + + # Define task value and excitation sequences + # We purposefully scale excitation from low to extreme to trigger transitions: Green -> Yellow -> Red + steps = 9 + task_values = [0.8, 0.9, 0.95, 0.4, 0.3, 0.2, 0.1, 0.1, 0.1] + excitations = [ + np.ones(12) * 0.1, # Low excitation (Green) + np.ones(12) * 0.2, # Low-mid excitation (Green) + np.ones(12) * 0.3, # Mid excitation (Yellow transition) + np.ones(12) * 1.5, # High excitation (Yellow/Red transition) + np.ones(12) * 3.5, # Extreme excitation (Red) + np.ones(12) * 5.0, # Extreme excitation (Red) + np.ones(12) * 0.1, # Dissipation / Recovery phase + np.ones(12) * 0.05, # Dissipation + np.ones(12) * 0.01 # Fully dissipated / Recovered + ] + + single_results = single_engine.run( + n_steps=steps, + task_value_sequence=task_values, + excitation_sequence=excitations + ) + + print(f"Single-Agent Simulation finished {steps} steps.") + print("Lyapunov Energy history:") + for step, v_val in enumerate(single_results.v_history): + band = single_results.band_history[step] + print(f" Step {step+1}: Energy V(t) = {v_val:.4f} [{band.upper()}]") + + print("\nInterventions Log:") + for idx, intervention in enumerate(single_results.interventions): + print(f" Tick {idx+1}: {intervention}") + + # Export single-agent results to logs/ + single_out_path = "logs/simulation_run_single_agent.json" + export_simulation_results_to_json(single_results.__dict__, single_out_path) + print(f"\nSaved single agent simulation results to {single_out_path}") + + single_ledger_path = "logs/simulation_ledger_single_agent.json" + ledger_to_json(single_engine.ledger, single_ledger_path) + print(f"Saved single agent ledger to {single_ledger_path}") + + # ------------------------------------------------------------------------- + # 2. Multi-Agent Simulation Run + # ------------------------------------------------------------------------- + print("\n--- Running Multi-Agent Engine ---") + multi_engine = MultiAgentEngine( + n_agents=3, + cost_weights=CostWeights.get_preset("balanced"), + # Adjusting thresholds to demonstrate Green -> Yellow -> Red transitions clearly + band_config=StabilityBandConfig(c_green=0.6, c_red=0.5) + ) + + # Let's run steps with escalating compliance degradation and cost load + multi_task_values = [0.9, 0.9, 0.9, 0.8, 0.8, 0.5] + multi_excitations = [ + np.ones(12) * 0.5, # Nominal (Green) + np.ones(12) * 0.5, # Nominal (Green) + np.ones(12) * 1.5, # Mid excitation (Yellow) + np.ones(12) * 3.0, # High tension (Red) + np.ones(12) * 4.5, # High tension (Red) + np.ones(12) * 0.1, # Safe mode recovery + ] + + # Dynamically inject bad policy/compliance & high latencies on step 3 to trigger red band and quarantine + # Agent 2 starts experiencing heavy policy compliance drops and latency spikes + multi_engine.agents[1].shard.policy_compliance = 0.95 + multi_engine.agents[1].shard.latency = 15.0 + + # Execute simulation step-by-step to log transitions + for step_idx in range(6): + t_val = multi_task_values[step_idx] + excite = multi_excitations[step_idx] + + # Trigger compliance and latency fault at step 3 to force a hard quarantine + if step_idx == 3: + print("\n>>> Fault Injection: Agent 2 policy compliance degraded to 0.1, latency spiked to 2000 ms. <<<") + multi_engine.agents[1].shard.policy_compliance = 0.1 + multi_engine.agents[1].shard.latency = 2000.0 + + multi_engine.tick(t_val, excite) + + multi_results = MultiAgentEngine.run_result = { + "h_hol_history": multi_engine.h_hol_history, + "c_mesh_history": multi_engine.c_mesh_history, + "band_history": multi_engine.band_history, + "agent_coherences": multi_engine.agent_coherences, + "interventions": multi_engine.interventions, + "sao_events": multi_engine.sao_events, + "residual_history": multi_engine.residual_history + } + + print("\nMulti-Agent Coherence history:") + for step, c_val in enumerate(multi_results["c_mesh_history"]): + band = multi_results["band_history"][step] + h_hol = multi_results["h_hol_history"][step] + print(f" Step {step+1}: C_mesh(t) = {c_val:.4f} | H_hol(t) = {h_hol:.4f} [{band.upper()}]") + + print("\nInterventions Log:") + for idx, intervention in enumerate(multi_results["interventions"]): + print(f" Tick {idx+1}: {intervention}") + + # Export multi-agent results + multi_out_path = "logs/simulation_run_multi_agent.json" + export_simulation_results_to_json(multi_results, multi_out_path) + print(f"\nSaved multi agent simulation results to {multi_out_path}") + + multi_ledger_path = "logs/simulation_ledger_multi_agent.json" + ledger_to_json(multi_engine.mesh_governance.ledger, multi_ledger_path) + print(f"Saved multi agent ledger to {multi_ledger_path}") + + print("\n" + "=" * 70) + print("🛸 DEMO RUN COMPLETED SUCCESSFULLY! 🛸") + print("=" * 70) + + +if __name__ == "__main__": + main() diff --git a/logs/simulation_ledger_multi_agent.json b/logs/simulation_ledger_multi_agent.json new file mode 100644 index 0000000..e792030 --- /dev/null +++ b/logs/simulation_ledger_multi_agent.json @@ -0,0 +1,62 @@ +[ + { + "record_id": "sao_fail_0", + "error_type": "sao_ascension_rejection", + "shard_id": "agent_1_and_agent_2", + "severity": "high", + "details": { + "p_sao": 0.5277249303654379, + "verdict": "block" + }, + "timestamp": 0.0, + "escalation_tier": "none" + }, + { + "record_id": "sao_fail_1", + "error_type": "sao_ascension_rejection", + "shard_id": "agent_2_and_agent_3", + "severity": "high", + "details": { + "p_sao": 0.4863940229045431, + "verdict": "block" + }, + "timestamp": 0.0, + "escalation_tier": "none" + }, + { + "record_id": "audit_quarantine_agent_2_2", + "error_type": "governance_quarantine", + "shard_id": "agent_2", + "severity": "critical", + "details": { + "residual": 0.0, + "trust": 0.9 + }, + "timestamp": 0.0, + "escalation_tier": "none" + }, + { + "record_id": "audit_quarantine_agent_2_3", + "error_type": "governance_quarantine", + "shard_id": "agent_2", + "severity": "critical", + "details": { + "residual": 0.0, + "trust": 0.9 + }, + "timestamp": 0.0, + "escalation_tier": "none" + }, + { + "record_id": "audit_quarantine_agent_2_4", + "error_type": "governance_quarantine", + "shard_id": "agent_2", + "severity": "critical", + "details": { + "residual": 0.0, + "trust": 0.9 + }, + "timestamp": 0.0, + "escalation_tier": "none" + } +] \ No newline at end of file diff --git a/logs/simulation_ledger_single_agent.json b/logs/simulation_ledger_single_agent.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/logs/simulation_ledger_single_agent.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/logs/simulation_run_multi_agent.json b/logs/simulation_run_multi_agent.json new file mode 100644 index 0000000..fcbd576 --- /dev/null +++ b/logs/simulation_run_multi_agent.json @@ -0,0 +1,106 @@ +{ + "h_hol_history": [ + 0.35784818677920854, + 0.4000880525235803, + 0.5180289203669286, + 0.47185951465517406, + 0.46543334800080743, + 0.35666065234094263 + ], + "c_mesh_history": [ + 0.663594316450745, + 0.6635865397164901, + 0.6635542567316013, + 0.4940791942694723, + 0.48280476283573787, + 0.48253936662912045 + ], + "band_history": [ + "green", + "green", + "green", + "red", + "red", + "red" + ], + "agent_coherences": { + "agent_1": [ + 0.8087840129340922, + 0.8101605373885173, + 0.8100802705514609, + 0.9260616296329189, + 0.9999999926043911, + 0.9946991709973697 + ], + "agent_2": [ + 0.8149254627655486, + 0.8246539095694903, + 0.8260504540943893, + 0.9299602747690069, + 0.9299602747690069, + 0.9299602747690069 + ], + "agent_3": [ + 0.9999999082868885, + 0.9886336667160444, + 0.9862328286337924, + 0.9824062266677287, + 0.9999999926043911, + 0.99998650731507 + ] + }, + "interventions": [ + "Green Band (Nominal): Stable multi-agent routing operating optimally.", + "Green Band (Nominal): Stable multi-agent routing operating optimally.", + "Green Band (Nominal): Stable multi-agent routing operating optimally.", + "Red Band (Hard Intervention): Coherence=0.4941. Throttling, shard quarantine, and fallback checks.", + "Red Band (Hard Intervention): Coherence=0.4828. Throttling, shard quarantine, and fallback checks.", + "Red Band (Hard Intervention): Coherence=0.4825. Throttling, shard quarantine, and fallback checks." + ], + "sao_events": [ + { + "agent_l": "agent_1", + "agent_r": "agent_2", + "verdict": "ascend", + "p_sao": 0.0 + }, + { + "agent_l": "agent_2", + "agent_r": "agent_3", + "verdict": "ascend", + "p_sao": 0.0 + }, + { + "agent_l": "agent_1", + "agent_r": "agent_2", + "verdict": "ascend", + "p_sao": 0.0 + }, + { + "agent_l": "agent_2", + "agent_r": "agent_3", + "verdict": "ascend", + "p_sao": 0.0 + }, + { + "agent_l": "agent_1", + "agent_r": "agent_2", + "verdict": "block", + "p_sao": 0.5277249303654379 + }, + { + "agent_l": "agent_2", + "agent_r": "agent_3", + "verdict": "block", + "p_sao": 0.4863940229045431 + } + ], + "residual_history": [ + 0.0, + 0.0, + 0.5070594766349905, + 0.0, + 0.0, + 0.0 + ] +} \ No newline at end of file diff --git a/logs/simulation_run_single_agent.json b/logs/simulation_run_single_agent.json new file mode 100644 index 0000000..32acbbf --- /dev/null +++ b/logs/simulation_run_single_agent.json @@ -0,0 +1,527 @@ +{ + "v_history": [ + 5.5296000000000055e-06, + 0.0030997463074087534, + 0.01756820202876825, + 0.04502385025437825, + 0.1077399323708381, + 0.22977733764163413, + 0.16809230276891088, + 0.08457098645267606, + 0.07460846394380675 + ], + "band_history": [ + "green", + "green", + "green", + "yellow", + "yellow", + "red", + "red", + "yellow", + "yellow" + ], + "cost_history": [ + { + "tokens": 3.360000000000001, + "depth": 0.02880000000000001, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.013632000000000005, + "corrections": 0.0, + "recovery": 0.0 + }, + { + "tokens": 18.461325260148023, + "depth": 0.1812799308012688, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.07812583391260056, + "corrections": 0.0, + "recovery": 0.0 + }, + { + "tokens": 42.30399794038601, + "depth": 0.5076296412728951, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.19193671532216536, + "corrections": 0.0, + "recovery": 0.0 + }, + { + "tokens": 84.79841651318557, + "depth": 1.1329472831313356, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.40089380967606003, + "corrections": 0.0, + "recovery": 4.0 + }, + { + "tokens": 157.83993496876064, + "depth": 2.2592715548087314, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.76726926044111, + "corrections": 0.0, + "recovery": 4.0 + }, + { + "tokens": 65.62586020813141, + "depth": 1.767572074186641, + "context": 76.56854249492382, + "retrievals": 0.6000000000000001, + "tool_calls": 0.0, + "latency": 0.4349625481236481, + "corrections": 1.875, + "recovery": 5.0 + }, + { + "tokens": 28.798576214411447, + "depth": 1.5261980298485218, + "context": 76.56854249492382, + "retrievals": 0.6000000000000001, + "tool_calls": 0.0, + "latency": 0.2959493705056829, + "corrections": 1.875, + "recovery": 5.0 + }, + { + "tokens": 42.05856260995038, + "depth": 2.3954326716928414, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.45552789577971314, + "corrections": 0.0, + "recovery": 4.0 + }, + { + "tokens": 39.771880292525665, + "depth": 2.257247968433065, + "context": 100.0, + "retrievals": 1.0, + "tool_calls": 0.0, + "latency": 0.42964865927355955, + "corrections": 0.0, + "recovery": 4.0 + } + ], + "observable_cost_history": [ + 3.3901056000000005, + 6.500085701860002, + 11.441256370653157, + 24.263635171160793, + 39.446620290807196, + 26.805867521351544, + 19.248742165733805, + 16.074993006537056, + 15.562406978032246 + ], + "sao_events": [ + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.008000000000000004, + "max_closure_ratio": 7.158054589401341e-35, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.04395553633368576, + "max_closure_ratio": 1.811456648770747e-33, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.1007238046199667, + "max_closure_ratio": 1.000655388515565e-32, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.2019009916980609, + "max_closure_ratio": 3.6337476889361907e-32, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.37580936897323963, + "max_closure_ratio": 1.3437200265178604e-31, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.26042008019099766, + "max_closure_ratio": 4.497474636674894e-32, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.11428006434290254, + "max_closure_ratio": 6.724270852052961e-33, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.10013943478559613, + "max_closure_ratio": 6.105686728464629e-33, + "beta": 0 + } + }, + { + "verdict": "admit", + "p_sao": 0.0, + "meta": { + "collapse_activation": 0.09469495307744207, + "max_closure_ratio": 6.009496809118149e-33, + "beta": 0 + } + } + ], + "interventions": [ + "Green Band (Nominal): No intervention required.", + "Green Band (Nominal): No intervention required.", + "Green Band (Nominal): No intervention required.", + "Yellow Band (Soft Intervention): Damping high-cost strings and tightening envelope.", + "Yellow Band (Soft Intervention): Damping high-cost strings and tightening envelope.", + "Red Band (Hard Intervention): Throttling activations, aggressive suppression of non-essentials.", + "Red Band (Hard Intervention): Throttling activations, aggressive suppression of non-essentials.", + "Yellow Band (Soft Intervention): Damping high-cost strings and tightening envelope.", + "Yellow Band (Soft Intervention): Damping high-cost strings and tightening envelope." + ], + "coherence_history": [ + 0.9999994982631203, + 0.999999906180312, + 0.9999999573331753, + 0.9999999771814179, + 0.9999999863251714, + 0.9999999816215378, + 0.9999999620315463, + 0.9999999571020429, + 0.9999999548115202 + ], + "activation_history": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002, + 0.008000000000000002 + ], + [ + 0.04395553633368577, + 0.04395553633368576, + 0.04395553633368576, + 0.04395553633368576, + 0.04395553633368577, + 0.04395553633368577, + 0.04395553633368577, + 0.04395553633368577, + 0.04395553633368577, + 0.04395553633368577, + 0.04395553633368577, + 0.04395553633368577 + ], + [ + 0.1007238046199667, + 0.10072380461996668, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667, + 0.1007238046199667 + ], + [ + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092, + 0.20190099169806092 + ], + [ + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963, + 0.37580936897323963 + ], + [ + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766, + 0.26042008019099766 + ], + [ + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255, + 0.11428006434290255 + ], + [ + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613, + 0.10013943478559613 + ], + [ + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207, + 0.09469495307744207 + ] + ], + "radius_history": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003, + 0.0009600000000000003 + ], + [ + 0.006042664360042293, + 0.0060426643600422925, + 0.0060426643600422925, + 0.0060426643600422925, + 0.006042664360042293, + 0.006042664360042293, + 0.006042664360042293, + 0.006042664360042293, + 0.006042664360042293, + 0.006042664360042293, + 0.006042664360042293, + 0.006042664360042293 + ], + [ + 0.01692098804242984, + 0.016920988042429835, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984, + 0.01692098804242984 + ], + [ + 0.037764909437711186, + 0.03776490943771118, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186, + 0.037764909437711186 + ], + [ + 0.07530905182695771, + 0.0753090518269577, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771, + 0.07530905182695771 + ], + [ + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448, + 0.09819844856592448 + ], + [ + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897, + 0.08478877943602897 + ], + [ + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472, + 0.07984775572309472 + ], + [ + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883, + 0.07524159894776883 + ] + ], + "residual_history": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] +} \ No newline at end of file diff --git a/radial_membrane_ai/tests/test_ufo_engine.py b/radial_membrane_ai/tests/test_ufo_engine.py new file mode 100644 index 0000000..b7a0879 --- /dev/null +++ b/radial_membrane_ai/tests/test_ufo_engine.py @@ -0,0 +1,224 @@ +""" +Comprehensive unit tests for the U.F.O. Governed Simulation Engine (ufo_engine). +Achieving 100% line coverage. +""" + +from __future__ import annotations +import os +import numpy as np +import pytest + +from radial_membrane_ai.membrane import RadialMembrane +from radial_membrane_ai.ufo_engine import ( + CostWeights, + StabilityBandConfig, + SingleAgentEngine, + MultiAgentEngine +) +from radial_membrane_ai.ufo_engine.serialization import ( + ledger_to_json, + export_simulation_results_to_json +) +from radial_membrane_ai.shard import ShardState +from radial_membrane_ai.multi_agent.agent import UFOAgent + + +def test_cost_weights_presets() -> None: + # Test default/balanced + balanced = CostWeights.get_preset("balanced") + assert balanced.tokens == 0.05 + assert balanced.latency == 0.5 + + # Test strict + strict = CostWeights.get_preset("strict") + assert strict.tokens == 0.2 + assert strict.latency == 0.8 + + # Test exploratory + exploratory = CostWeights.get_preset("exploratory") + assert exploratory.tokens == 0.01 + assert exploratory.latency == 0.2 + + # Test to_dict + d = strict.to_dict() + assert d["tokens"] == 0.2 + assert d["corrections"] == 0.9 + + +def test_single_agent_engine_basic_tick() -> None: + engine = SingleAgentEngine( + cost_weights=CostWeights.get_preset("balanced"), + band_config=StabilityBandConfig(v_green=0.01, v_red=0.1) + ) + + # Tick nominal + band = engine.tick(task_value=0.5, excitation=np.zeros(12)) + assert band == "green" + assert len(engine.v_history) == 1 + assert len(engine.band_history) == 1 + assert len(engine.cost_history) == 1 + assert len(engine.observable_cost_history) == 1 + assert len(engine.coherence_history) == 1 + assert len(engine.activation_history) == 2 # initial + 1 step + + # Excite strings heavily to drive up Lyapunov energy and trigger Yellow/Red bands + band_escalated = engine.tick(task_value=0.9, excitation=np.ones(12) * 5.0) + assert band_escalated in ("yellow", "red") + + +def test_single_agent_engine_run() -> None: + engine = SingleAgentEngine() + results = engine.run( + n_steps=3, + task_value_sequence=[0.8, 0.5], + excitation_sequence=[np.ones(12) * 0.1, np.ones(12) * 0.5] + ) + assert len(results.v_history) == 3 + assert len(results.band_history) == 3 + assert len(results.cost_history) == 3 + assert len(results.observable_cost_history) == 3 + + +def test_single_agent_interventions_and_brim() -> None: + engine = SingleAgentEngine( + band_config=StabilityBandConfig(v_green=1.0, v_red=5.0) + ) + + # 1. Test Red stability band and Brim "block" + # We mock energy to return 10.0 (above v_red=5.0) => Red band + engine.governor.compute_lyapunov_energy = lambda m: 10.0 # type: ignore + engine.envelope.evaluate_envelope = lambda m, b: ("block", {}) # type: ignore + engine.sao_promotor.promote = lambda m, b, l: ("block", 0.5, {}) # type: ignore + + engine.tick(task_value=0.5, excitation=np.ones(12) * 1.0) + + assert any("Red Band" in m for m in engine.interventions) + assert any("Brim Envelope Block" in m for m in engine.interventions) + + # 2. Test Yellow stability band and Brim "constrain" + engine_yellow = SingleAgentEngine( + band_config=StabilityBandConfig(v_green=1.0, v_red=5.0) + ) + # Mock energy to return 3.0 (between v_green=1.0 and v_red=5.0) => Yellow band + engine_yellow.governor.compute_lyapunov_energy = lambda m: 3.0 # type: ignore + engine_yellow.envelope.evaluate_envelope = lambda m, b: ("constrain", {}) # type: ignore + engine_yellow.sao_promotor.promote = lambda m, b, l: ("constrain", 0.2, {}) # type: ignore + + # Ensure activations are set to 0.9 before update so that newly calculated string costs exceed task_value + for s in engine_yellow.membrane.strings: + s.activation = 0.9 + + # Set extremely low task_value to guarantee calculated string cost > task_value + engine_yellow.tick(task_value=0.01, excitation=np.ones(12) * 1.0) + + assert any("Yellow Band" in m for m in engine_yellow.interventions) + assert any("Brim Envelope Constrain" in m for m in engine_yellow.interventions) + + +def test_multi_agent_engine_basic_tick() -> None: + engine = MultiAgentEngine( + n_agents=2, + cost_weights=CostWeights.get_preset("exploratory"), + band_config=StabilityBandConfig(c_green=0.8, c_red=0.3) + ) + + # Mock high coherence to guarantee Green band branch is executed + engine.mesh_governance.compute_mesh_coherence = lambda **kwargs: 0.95 # type: ignore + + # Tick normal + band = engine.tick(task_value=0.5, excitation=np.ones(12) * 0.1) + assert band == "green" + assert len(engine.h_hol_history) == 1 + assert len(engine.c_mesh_history) == 1 + assert len(engine.band_history) == 1 + assert len(engine.interventions) == 1 + assert len(engine.residual_history) == 1 + + +def test_multi_agent_custom_agents_and_under_two_agents() -> None: + custom_agents = [UFOAgent("custom_1")] + # Initialize with less than 2 agents to hit edge cases + engine = MultiAgentEngine(custom_agents=custom_agents) + assert len(engine.channels) == 0 + + # Running tick + engine.tick(task_value=0.5, excitation=np.ones(12) * 0.2) + assert len(engine.sao_events) == 0 + + +def test_multi_agent_quarantine_and_fallback() -> None: + # 2 agents + agent_1 = UFOAgent("agent_1") + agent_2 = UFOAgent("agent_2") + engine = MultiAgentEngine( + custom_agents=[agent_1, agent_2], + band_config=StabilityBandConfig(c_green=0.9, c_red=0.4) + ) + + # 1. Force red band and quarantine agent_2 + agent_2.shard.trust_score = 0.1 # trigger quarantine in run_mesh_audit + agent_2.residual_history.append(5.0) + + # Mock red band coherence + engine.mesh_governance.compute_mesh_coherence = lambda **kwargs: 0.1 # type: ignore + + # Tick 1: triggers quarantine + engine.tick(task_value=0.5, excitation=np.ones(12) * 0.1) + assert agent_2.shard.state == ShardState.QUARANTINED + + # Tick 2: quarantined agent_2 is skipped (covers agent.shard.state == QUARANTINED branch) + engine.tick(task_value=0.5, excitation=np.ones(12) * 0.1) + + fallback_msgs = [m for m in engine.interventions if "Fallback triggered" in m] + assert len(fallback_msgs) > 0 + + # 2. Test Yellow band in Multi-Agent tick + engine_yellow = MultiAgentEngine( + custom_agents=[UFOAgent("a1"), UFOAgent("a2")], + band_config=StabilityBandConfig(c_green=0.8, c_red=0.4) + ) + engine_yellow.mesh_governance.compute_mesh_coherence = lambda **kwargs: 0.6 # type: ignore + engine_yellow.tick(task_value=0.5, excitation=np.ones(12) * 0.1) + assert any("Yellow Band" in m for m in engine_yellow.interventions) + + +def test_multi_agent_engine_run() -> None: + engine = MultiAgentEngine(n_agents=3) + results = engine.run( + n_steps=2, + task_value_sequence=[0.7], + excitation_sequence=[np.ones(12) * 0.5] + ) + assert len(results.h_hol_history) == 2 + assert len(results.c_mesh_history) == 2 + assert len(results.band_history) == 2 + + +def test_serialization_and_exports(tmp_path) -> None: + # Test ledger exporting + engine = SingleAgentEngine() + # Manually log a failure to test serialization logic + engine.ledger.log_failure( + record_id="test_record_1", + error_type="sao_test_failure", + shard_id="single_agent", + severity="high", + details={"p_sao": 0.55} + ) + + # Confirm ledger records exist + assert len(engine.ledger.records) > 0 + + ledger_file = tmp_path / "subdir" / "ledger.json" # Test directory creation + ledger_to_json(engine.ledger, str(ledger_file)) + assert ledger_file.exists() + + # Test arbitrary results exporting + results_file = tmp_path / "subdir_2" / "results.json" + dummy_results = { + "v_history": [0.1, 0.2], + "activations": np.array([0.5, 0.5]), + "some_object": object() # force default serializer to convert object to string + } + export_simulation_results_to_json(dummy_results, str(results_file)) + assert results_file.exists() diff --git a/radial_membrane_ai/ufo_engine/__init__.py b/radial_membrane_ai/ufo_engine/__init__.py new file mode 100644 index 0000000..47f2870 --- /dev/null +++ b/radial_membrane_ai/ufo_engine/__init__.py @@ -0,0 +1,24 @@ +""" +U.F.O. Governed Simulation Engine Subpackage. + +Implements the runtime orchestration layer coordinating: +- Single-Agent Engine (Lyapunov stability bands, governor suppression, V-channels) +- Multi-Agent Engine (coupled V-channels, holistic governor field, mesh coherence, shard quarantine) +- Configurable Cost Taxonomy and Stability Bands +- Full tick cycle integration, persistent residual logging, and audit ledgers +""" + +from __future__ import annotations + +from radial_membrane_ai.ufo_engine.config import CostWeights, StabilityBandConfig +from radial_membrane_ai.ufo_engine.single_agent import SingleAgentEngine, SingleAgentRunResult +from radial_membrane_ai.ufo_engine.multi_agent import MultiAgentEngine, MultiAgentRunResult + +__all__ = [ + "CostWeights", + "StabilityBandConfig", + "SingleAgentEngine", + "SingleAgentRunResult", + "MultiAgentEngine", + "MultiAgentRunResult", +] diff --git a/radial_membrane_ai/ufo_engine/config.py b/radial_membrane_ai/ufo_engine/config.py new file mode 100644 index 0000000..7a6fd79 --- /dev/null +++ b/radial_membrane_ai/ufo_engine/config.py @@ -0,0 +1,78 @@ +""" +Configuration, presets, and weights for the U.F.O. Simulation Engine. +""" + +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Dict, Any, Literal + + +@dataclass(frozen=True) +class CostWeights: + """ + Weighting configuration for the 8-dimensional runtime cost vector. Exposes presets. + """ + tokens: float = 0.05 + depth: float = 0.4 + context: float = 0.01 + retrievals: float = 0.2 + tool_calls: float = 0.3 + latency: float = 0.5 + corrections: float = 0.6 + recovery: float = 0.8 + + @classmethod + def get_preset(cls, preset_name: Literal["strict", "balanced", "exploratory"]) -> CostWeights: + """ + Returns a CostWeights instance with pre-configured weights based on the preset. + """ + if preset_name == "strict": + return cls( + tokens=0.2, # higher penalty on verbosity + depth=0.5, # high penalty on reasoning depth + context=0.05, # slightly higher penalty on context usage + retrievals=0.4, # high penalty on retrievals + tool_calls=0.5, # high penalty on agentic tools + latency=0.8, # very high penalty on latency + corrections=0.9, # extremely high penalty on correction overhead + recovery=1.0 # highest penalty on instability recovery + ) + elif preset_name == "exploratory": + return cls( + tokens=0.01, # very low penalty on verbosity/expansion + depth=0.1, # low penalty on deep reasoning + context=0.005, # low context penalty + retrievals=0.05, # low retrieval penalty + tool_calls=0.1, # low tool use penalty + latency=0.2, # low latency penalty + corrections=0.3, # softer penalty on corrections + recovery=0.5 # softer penalty on recovery + ) + else: # "balanced" + return cls() + + def to_dict(self) -> Dict[str, float]: + return { + "tokens": self.tokens, + "depth": self.depth, + "context": self.context, + "retrievals": self.retrievals, + "tool_calls": self.tool_calls, + "latency": self.latency, + "corrections": self.corrections, + "recovery": self.recovery + } + + +@dataclass +class StabilityBandConfig: + """ + Configuration for stability bands and thresholds for Single and Multi Agent Engines. + """ + # Single-Agent thresholds based on Lyapunov-style energy V(t) + v_green: float = 2.0 + v_red: float = 5.0 + + # Multi-Agent thresholds based on mesh coherence C_mesh(t) + c_green: float = 0.7 + c_red: float = 0.4 diff --git a/radial_membrane_ai/ufo_engine/multi_agent.py b/radial_membrane_ai/ufo_engine/multi_agent.py new file mode 100644 index 0000000..804f3ff --- /dev/null +++ b/radial_membrane_ai/ufo_engine/multi_agent.py @@ -0,0 +1,232 @@ +""" +Multi-Agent Governed Simulation Engine for the U.F.O. architecture. +""" + +from __future__ import annotations +import math +import numpy as np +from dataclasses import dataclass, field +from typing import Dict, Any, List, Literal, Tuple + +from radial_membrane_ai.multi_agent.agent import UFOAgent +from radial_membrane_ai.multi_agent.coupling import InterAgentVChannel, GlobalHolisticGovernor +from radial_membrane_ai.multi_agent.governance import MultiAgentMeshGovernance +from radial_membrane_ai.shard import ShardState +from radial_membrane_ai.residuals import ResidualLedger +from radial_membrane_ai.ufo_engine.config import CostWeights, StabilityBandConfig + + +@dataclass +class MultiAgentRunResult: + """ + Structured results of a multi-agent simulation run. + """ + h_hol_history: List[float] = field(default_factory=list) + c_mesh_history: List[float] = field(default_factory=list) + band_history: List[str] = field(default_factory=list) + agent_coherences: Dict[str, List[float]] = field(default_factory=dict) + interventions: List[str] = field(default_factory=list) + sao_events: List[Dict[str, Any]] = field(default_factory=list) + residual_history: List[float] = field(default_factory=list) + + +class MultiAgentEngine: + """ + Engine running the deterministic tick loop for a multi-agent UFO system. + Coordinates N agents, typed inter-agent channels, Holistic Governor fields, + mesh coherence, shard isolation/reintegration, and paired-state SAO promotions. + """ + + def __init__( + self, + n_agents: int = 3, + cost_weights: CostWeights | None = None, + band_config: StabilityBandConfig | None = None, + custom_agents: List[UFOAgent] | None = None + ) -> None: + """ + Initializes the Multi-Agent Engine. + """ + self.cost_weights = cost_weights if cost_weights is not None else CostWeights() + self.band_config = band_config if band_config is not None else StabilityBandConfig() + + if custom_agents is not None: + self.agents = custom_agents + else: + # Create standard agent ensemble + self.agents = [] + regimes = ["analytical", "creative", "balanced", "balanced"] + sensitivities = [1.0, 2.0, 1.2, 1.0] + for i in range(n_agents): + self.agents.append( + UFOAgent( + agent_id=f"agent_{i+1}", + cost_sensitivity=sensitivities[i % len(sensitivities)], + kernel_regime=regimes[i % len(regimes)], + state=ShardState.IDLE, + trust_score=0.9, + policy_compliance=0.95, + latency=12.0 + i * 5.0 + ) + ) + + self.global_governor = GlobalHolisticGovernor() + self.mesh_governance = MultiAgentMeshGovernance(self.agents) + + # Set up typed inter-agent V-channels + self.channels: List[InterAgentVChannel] = [] + self._init_coupling_channels() + + # History trackers + self.h_hol_history: List[float] = [] + self.c_mesh_history: List[float] = [] + self.band_history: List[str] = [] + self.agent_coherences: Dict[str, List[float]] = {a.agent_id: [] for a in self.agents} + self.interventions: List[str] = [] + self.sao_events: List[Dict[str, Any]] = [] + self.residual_history: List[float] = [] + + def _init_coupling_channels(self) -> None: + """ + Sets up type-W (workload), type-T (tension) and type-R (residuals) channels between agents. + """ + n = len(self.agents) + if n < 2: + return + + for i in range(n): + src = self.agents[i] + tgt = self.agents[(i + 1) % n] + self.channels.append(InterAgentVChannel(src, tgt, "Type-W", coupling_strength=0.15)) + self.channels.append(InterAgentVChannel(tgt, src, "Type-T", coupling_strength=0.1)) + self.channels.append(InterAgentVChannel(src, tgt, "Type-R", coupling_strength=0.12)) + + def tick(self, task_value: float, excitation: np.ndarray) -> str: + """ + Runs a single multi-agent tick cycle. + + Steps: + 1. Local agent step execution. + 2. Inter-agent channel propagation (V-Channels). + 3. Global Holistic Governor field H_hol calculation. + 4. Mesh coherence score C_mesh calculation. + 5. Band transition checking and intervention execution. + 6. SAO promotions of shared paired-state representations. + 7. Audit mesh & log results. + + Returns: + The determined stability band for the tick ("green", "yellow", or "red"). + """ + # Ensure correct array format + excitation = np.array(excitation, dtype=np.float64) + + # 1. Update individual agents locally (skipping quarantined) + for agent in self.agents: + if agent.shard.state == ShardState.QUARANTINED: + continue + agent.step(task_value, excitation) + + # 2. Propagate inter-agent coupling V-channels + for channel in self.channels: + channel.propagate() + + # 3. Compute global fields + h_hol = self.global_governor.compute_global_holistic_field(self.agents) + self.h_hol_history.append(h_hol) + + # 4. Compute mesh coherence score C_mesh(t) + # We adjust weights according to our configured cost weighting + # We also scale by quality factor / cost weights to enforce proper dynamic coherence + c_mesh = self.mesh_governance.compute_mesh_coherence( + w_q=0.4, w_t=0.3, w_e=0.2, w_r=0.2, w_p=0.2, w_l=0.1, w_f=0.2 + ) + self.c_mesh_history.append(c_mesh) + + for agent in self.agents: + self.agent_coherences[agent.agent_id].append(agent.shard.quality_score) + + # 5. Stability Band Check & Interventions + if c_mesh >= self.band_config.c_green: + band = "green" + self.interventions.append("Green Band (Nominal): Stable multi-agent routing operating optimally.") + elif c_mesh >= self.band_config.c_red: + band = "yellow" + self.interventions.append(f"Yellow Band (Soft Intervention): Coherence={c_mesh:.4f}. Damping activations and rebalancing routes.") + # Soft interventions: damp active strings slightly on all non-quarantined agents + for agent in self.agents: + if agent.shard.state != ShardState.QUARANTINED: + for s in agent.membrane.strings: + s.activation *= 0.85 + s.radius *= 0.95 + else: + band = "red" + self.interventions.append(f"Red Band (Hard Intervention): Coherence={c_mesh:.4f}. Throttling, shard quarantine, and fallback checks.") + # Hard interventions: throttle activations heavily + for agent in self.agents: + if agent.shard.state != ShardState.QUARANTINED: + for s in agent.membrane.strings: + s.activation *= 0.5 + s.radius *= 0.8 + + # Run mesh audit to isolate/quarantine low performing/violating agents + self.mesh_governance.run_mesh_audit() + + # Fallback checks: If only one agent is left active, log fallback + active_agents = [a for a in self.agents if a.shard.state != ShardState.QUARANTINED] + if len(active_agents) == 1: + self.interventions.append(f"Fallback triggered: Sole active agent is {active_agents[0].agent_id}.") + + self.band_history.append(band) + + # 6. Execute SAO promotions on paired agents + # Record promotion residuals and audit failures + p_sao_sum = 0.0 + promo_count = 0 + if len(self.agents) >= 2: + for i in range(len(self.agents) - 1): + agent_l = self.agents[i] + agent_r = self.agents[i + 1] + if agent_l.shard.state != ShardState.QUARANTINED and agent_r.shard.state != ShardState.QUARANTINED: + # Dynamically adjust threshold based on mesh coherence + limit = max(0.02, 0.5 * c_mesh) + verdict, p_sao, proj_state = self.mesh_governance.execute_sao_promotion( + agent_l, agent_r, shared_capacity_limit=limit + ) + sao_rec = { + "agent_l": agent_l.agent_id, + "agent_r": agent_r.agent_id, + "verdict": verdict, + "p_sao": p_sao + } + self.sao_events.append(sao_rec) + p_sao_sum += p_sao + promo_count += 1 + + avg_p_sao = (p_sao_sum / promo_count) if promo_count > 0 else 0.0 + self.residual_history.append(avg_p_sao) + + return band + + def run( + self, + n_steps: int, + task_value_sequence: List[float], + excitation_sequence: List[np.ndarray] + ) -> MultiAgentRunResult: + """ + Runs the multi-agent engine for a sequence of steps. + """ + for i in range(n_steps): + t_val = task_value_sequence[i % len(task_value_sequence)] + excite = excitation_sequence[i % len(excitation_sequence)] + self.tick(t_val, excite) + + return MultiAgentRunResult( + h_hol_history=list(self.h_hol_history), + c_mesh_history=list(self.c_mesh_history), + band_history=list(self.band_history), + agent_coherences={k: list(v) for k, v in self.agent_coherences.items()}, + interventions=list(self.interventions), + sao_events=list(self.sao_events), + residual_history=list(self.residual_history) + ) diff --git a/radial_membrane_ai/ufo_engine/serialization.py b/radial_membrane_ai/ufo_engine/serialization.py new file mode 100644 index 0000000..8245324 --- /dev/null +++ b/radial_membrane_ai/ufo_engine/serialization.py @@ -0,0 +1,59 @@ +""" +Adds serialization and file export capability to the ResidualLedger. +""" + +from __future__ import annotations +import os +import json +from typing import Dict, Any, List + +from radial_membrane_ai.residuals import ResidualLedger + + +def ledger_to_json(ledger: ResidualLedger, filepath: str) -> None: + """ + Exports all records inside a ResidualLedger to a JSON file. + Creates necessary directories if they do not exist. + """ + directory = os.path.dirname(filepath) + if directory and not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + + data = [] + for r in ledger.records: + data.append({ + "record_id": r.record_id, + "error_type": r.error_type, + "shard_id": r.shard_id, + "severity": r.severity, + "details": r.details, + "timestamp": r.timestamp, + "escalation_tier": r.escalation_tier + }) + + with open(filepath, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4) + + +def export_simulation_results_to_json( + results: Dict[str, Any], + filepath: str +) -> None: + """ + Serializes simulation results dictionary containing numpy arrays or objects + to a JSON file safely. + """ + directory = os.path.dirname(filepath) + if directory and not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + + # Convert complex objects and numpy arrays to serializable types + def default_serializer(obj: Any) -> Any: + if isinstance(obj, np.ndarray): + return obj.tolist() + return str(obj) + + import numpy as np # locally import to satisfy default_serializer + + with open(filepath, "w", encoding="utf-8") as f: + json.dump(results, f, default=default_serializer, indent=4) diff --git a/radial_membrane_ai/ufo_engine/single_agent.py b/radial_membrane_ai/ufo_engine/single_agent.py new file mode 100644 index 0000000..f22d9fc --- /dev/null +++ b/radial_membrane_ai/ufo_engine/single_agent.py @@ -0,0 +1,353 @@ +""" +Single-Agent Governed Simulation Engine for the U.F.O. architecture. +""" + +from __future__ import annotations +import math +import numpy as np +from dataclasses import dataclass, field +from typing import Dict, Any, List, Literal + +from radial_membrane_ai.membrane import RadialMembrane +from radial_membrane_ai.boundary import BoundaryGeometry +from radial_membrane_ai.governor import Governor, GovernorConfig +from radial_membrane_ai.envelope import BrimEnvelope +from radial_membrane_ai.saopromotion import SAOPromotor +from radial_membrane_ai.cost import RuntimeCostVector, reduce_avoidable_cost +from radial_membrane_ai.residuals import ResidualLedger +from radial_membrane_ai.channels import update_radius_along_channel, channel_coherence +from radial_membrane_ai.projection import ( + closure_ratio, + project_to_admissible, + residual_deformation +) +from radial_membrane_ai.admissibility import angular_decomposition +from radial_membrane_ai.facet import FacetVector, TensionAutomaton, TensionState +from radial_membrane_ai.coherence import closure_coherence +from radial_membrane_ai.ufo_engine.config import CostWeights, StabilityBandConfig + + +@dataclass +class SingleAgentRunResult: + """ + Structured results of a single-agent simulation run. + """ + v_history: List[float] = field(default_factory=list) + band_history: List[str] = field(default_factory=list) + cost_history: List[Dict[str, float]] = field(default_factory=list) + observable_cost_history: List[float] = field(default_factory=list) + sao_events: List[Dict[str, Any]] = field(default_factory=list) + interventions: List[str] = field(default_factory=list) + coherence_history: List[float] = field(default_factory=list) + activation_history: List[np.ndarray] = field(default_factory=list) + radius_history: List[np.ndarray] = field(default_factory=list) + residual_history: List[float] = field(default_factory=list) + + +class SingleAgentEngine: + """ + Engine running the deterministic tick loop for a single-agent UFO membrane. + Integrates membrane geometry, routing, envelopes, cost, governor and SAO. + """ + + def __init__( + self, + membrane: RadialMembrane | None = None, + governor: Governor | None = None, + boundary: BoundaryGeometry | None = None, + cost_weights: CostWeights | None = None, + band_config: StabilityBandConfig | None = None, + r_max: float = 2.0, + r_growth_rate: float = 0.3, + r_relaxation: float = 0.2 + ) -> None: + """ + Initializes the single-agent engine. + """ + self.membrane = membrane if membrane is not None else RadialMembrane() + self.governor = governor if governor is not None else Governor() + self.boundary = boundary if boundary is not None else BoundaryGeometry() + self.cost_weights = cost_weights if cost_weights is not None else CostWeights() + self.band_config = band_config if band_config is not None else StabilityBandConfig() + + self.r_max = r_max + self.r_growth_rate = r_growth_rate + self.r_relaxation = r_relaxation + + self.envelope = BrimEnvelope(energy_threshold=1.5) + self.sao_promotor = SAOPromotor(promotion_threshold=0.4) + self.automaton = TensionAutomaton() + self.ledger = ResidualLedger() + + # In-memory history tracking + self.v_history: List[float] = [] + self.band_history: List[str] = [] + self.cost_history: List[RuntimeCostVector] = [] + self.observable_cost_history: List[float] = [] + self.sao_events: List[Dict[str, Any]] = [] + self.interventions: List[str] = [] + self.coherence_history: List[float] = [] + self.activation_history: List[np.ndarray] = [] + self.radius_history: List[np.ndarray] = [] + self.residual_history: List[float] = [] + + # Record initial state + self._record_state() + + def _update_facet_vectors(self, task_value: float, excitation: np.ndarray) -> np.ndarray: + """ + Performs the Pythagorean projection and updates facets for all 12 strings. + Also returns the closure coherence Q-matrix. + """ + Q_matrix = np.zeros((12, 12), dtype=np.float64) + for i in range(12): + for j in range(12): + Q_matrix[i, j] = closure_coherence(self.membrane, self.boundary, i + 1, j + 1, samples=8) + + for idx, s in enumerate(self.membrane.strings): + a_orig, b_orig = angular_decomposition(self.membrane, s.theta, samples=64) + c = self.boundary.get_radius(s.theta) + + i_val = closure_ratio(a_orig, b_orig, c) + a_proj, b_proj = project_to_admissible(a_orig, b_orig, c, metric="euclidean") + + res_a, res_b = residual_deformation(a_orig, b_orig, a_proj, b_proj) + p_magnitude = math.sqrt(res_a**2 + res_b**2) + + current_state = s.facet.state if s.facet is not None else TensionState.RELAXED + next_state = self.automaton.transition(current_state, i_val, p_magnitude) + policy_priority = float(excitation[idx]) * task_value + + s.facet = FacetVector( + facet_id=s.name, + state=next_state, + activation=s.activation, + capacity=c, + residual=p_magnitude, + policy_priority=policy_priority + ) + # Retain residual in the string's internal record + s.cost = max(s.cost, p_magnitude) + + return Q_matrix + + def _record_state(self) -> None: + """ + Records the current state snapshot into history. + """ + self.activation_history.append(self.membrane.get_activation_vector()) + self.radius_history.append(np.array([s.radius for s in self.membrane.strings], dtype=np.float64)) + + def compute_local_coherence(self) -> float: + """ + Computes the average coherence across all active pairs on the membrane. + """ + coherences = [] + for i in range(12): + for j in range(12): + if i != j: + coherences.append(channel_coherence(self.membrane, i + 1, j + 1, samples=16)) + return float(np.mean(coherences)) if coherences else 1.0 + + def tick( + self, + task_value: float, + excitation: np.ndarray, + tool_loads: list[float] | np.ndarray | None = None, + context_loads: list[float] | np.ndarray | None = None + ) -> str: + """ + Runs a single tick cycle of the single-agent engine. + + Steps: + 1. Local cost calculations & activation update via Governor. + 2. V-Channel reasoning radius propagation (Depth). + 3. Pythagorean projection, tension updating, and facet resolution. + 4. Boundary geometry deformation. + 5. Lyapunov energy calculation and band determination. + 6. Soft/Hard interventions by Governor. + 7. Bounded compute envelope check (Brim). + 8. SAO Promotion gate. + 9. Cost taxonomy evaluation and quality-preserving reduction. + 10. Logging of states, residuals, and interventions. + + Returns: + The determined stability band for the tick ("green", "yellow", or "red"). + """ + # Ensure correct array format + excitation = np.array(excitation, dtype=np.float64) + + # 1. Update activations via Governor + self.governor.update_membrane( + membrane=self.membrane, + task_value=task_value, + task_excitation=excitation, + tool_loads=tool_loads, + context_loads=context_loads + ) + + # 2. V-Channel Routing (Radius Propagation) + old_radii = [s.radius for s in self.membrane.strings] + for t_idx in range(12): + target = self.membrane.strings[t_idx] + r_internal = target.activation * self.r_max * self.r_growth_rate + + r_propagated = 0.0 + for s_idx in range(12): + if s_idx != t_idx: + source = self.membrane.strings[s_idx] + orig_radius = source.radius + source.radius = old_radii[s_idx] + + r_prop = update_radius_along_channel( + source=source, + target=target, + r_max=self.r_max + ) + source.radius = orig_radius + if r_prop > r_propagated: + r_propagated = r_prop + + target_radius_target = max(r_internal, r_propagated) + target.radius = (1.0 - self.r_relaxation) * target.radius + self.r_relaxation * target_radius_target + + # 3. Pythagorean Projection Layer & Facets + Q_matrix = self._update_facet_vectors(task_value, excitation) + + # 4. Boundary Deformation + self.boundary.update_boundary( + membrane=self.membrane, + task_value=task_value + ) + + # 5. Lyapunov Energy & Band Analysis + energy = self.governor.compute_lyapunov_energy(self.membrane) + self.v_history.append(energy) + + # Determine stability band + if energy <= self.band_config.v_green: + band = "green" + elif energy <= self.band_config.v_red: + band = "yellow" + else: + band = "red" + self.band_history.append(band) + + # 6. Governor Interventions + if band == "green": + self.interventions.append("Green Band (Nominal): No intervention required.") + elif band == "yellow": + self.interventions.append("Yellow Band (Soft Intervention): Damping high-cost strings and tightening envelope.") + # Damp activations on high-cost strings (> task_value) + for s in self.membrane.strings: + if s.cost > task_value: + s.activation *= 0.85 + s.radius *= 0.95 + else: # "red" + self.interventions.append("Red Band (Hard Intervention): Throttling activations, aggressive suppression of non-essentials.") + # Aggressive suppression of all strings, especially non-analytical/non-contextual ones + for s in self.membrane.strings: + s.activation *= 0.5 + s.radius *= 0.8 + + # 7. Brim Compute Envelope Evaluation + brim_verdict, brim_meta = self.envelope.evaluate_envelope(self.membrane, self.boundary) + if brim_verdict == "block": + self.interventions.append("Brim Envelope Block: Strong fallback containment.") + for s in self.membrane.strings: + s.activation *= 0.5 + elif brim_verdict == "constrain": + self.interventions.append("Brim Envelope Constrain: Soft activation restriction.") + for s in self.membrane.strings: + s.activation *= 0.8 + + # 8. SAO Promotion Gate + sao_verdict, p_sao, sao_meta = self.sao_promotor.promote(self.membrane, self.boundary, "Holistic Governor") + sao_record = { + "verdict": sao_verdict, + "p_sao": p_sao, + "meta": sao_meta + } + self.sao_events.append(sao_record) + + if sao_verdict in ("block", "constrain"): + # Record promotion residual failure in our ledger + self.ledger.log_failure( + record_id=f"single_agent_sao_{len(self.ledger.records)}", + error_type="sao_promotion_restriction", + shard_id="single_agent", + severity="high" if sao_verdict == "block" else "medium", + details={"p_sao": p_sao, "verdict": sao_verdict, "energy": energy} + ) + # Append residual to history + self.residual_history.append(p_sao) + else: + self.residual_history.append(0.0) + + # 9. Cost Taxonomy Integration + total_act = sum(s.activation for s in self.membrane.strings) + total_rad = sum(s.radius for s in self.membrane.strings) + avg_q_coh = float(np.mean(Q_matrix)) if Q_matrix.size > 0 else 0.5 + + if band == "red": + avg_q_coh *= 0.5 + + raw_cost = RuntimeCostVector( + tokens=total_act * 35.0, + depth=total_rad * 2.5, + context=float(sum(context_loads)) if context_loads is not None else 100.0, + retrievals=float(sum(tool_loads)) * 1.5 if tool_loads is not None else 1.0, + tool_calls=float(sum(tool_loads)) if tool_loads is not None else 0.0, + latency=total_rad * 0.35 + total_act * 0.1, + corrections=(1.0 - avg_q_coh) * 7.5, + recovery=10.0 if band == "red" else (4.0 if band == "yellow" else 0.0) + ) + + reduced_cost = reduce_avoidable_cost(raw_cost, avg_q_coh) + self.cost_history.append(reduced_cost) + + obs_cost = reduced_cost.weighted_cost(self.cost_weights.to_dict(), quality_signal=avg_q_coh) + self.observable_cost_history.append(obs_cost) + + # 10. Coh & State tracking + self.coherence_history.append(self.compute_local_coherence()) + self._record_state() + + return band + + def run( + self, + n_steps: int, + task_value_sequence: List[float], + excitation_sequence: List[np.ndarray], + tool_loads_sequence: List[List[float]] | None = None, + context_loads_sequence: List[List[float]] | None = None + ) -> SingleAgentRunResult: + """ + Runs the simulation for multiple ticks. + """ + for i in range(n_steps): + t_val = task_value_sequence[i % len(task_value_sequence)] + excite = excitation_sequence[i % len(excitation_sequence)] + t_load = tool_loads_sequence[i % len(tool_loads_sequence)] if tool_loads_sequence else None + c_load = context_loads_sequence[i % len(context_loads_sequence)] if context_loads_sequence else None + + self.tick( + task_value=t_val, + excitation=excite, + tool_loads=t_load, + context_loads=c_load + ) + + return SingleAgentRunResult( + v_history=list(self.v_history), + band_history=list(self.band_history), + cost_history=[c.__dict__ for c in self.cost_history], + observable_cost_history=list(self.observable_cost_history), + sao_events=list(self.sao_events), + interventions=list(self.interventions), + coherence_history=list(self.coherence_history), + activation_history=list(self.activation_history), + radius_history=list(self.radius_history), + residual_history=list(self.residual_history) + )