camdog920 commited on
Commit
e34db8d
·
verified ·
1 Parent(s): a8a5298

Add AETHER v0.2.0 autonomous — fully self-evolving with automated oversight

Browse files
Files changed (1) hide show
  1. aether_autonomous.py +1445 -0
aether_autonomous.py ADDED
@@ -0,0 +1,1445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AETHER: Autonomous Self-Evolving Neuro-Symbolic Architecture
3
+ ===============================================================
4
+ Fully automated — zero human-in-the-loop. All oversight is performed by
5
+ automated regression gating, risk scoring, and stability validation.
6
+
7
+ Architecture:
8
+ 1. Neuro-Symbolic Fusion Gate — learned attention over symbolic/neural split
9
+ 2. Four-Agent Orchestration — Researcher, Engineer, Analyzer, Integrator
10
+ 3. MAP-Elites Quality-Diversity — behavioral archive for evolutionary search
11
+ 4. CoALA 4-Tier Memory — Working, Episodic, Semantic, Procedural
12
+ 5. Temporal Memory with Attention — long-horizon context retention
13
+ 6. Knowledge Graph Engine — RGCN + ComplEx + symbolic inference
14
+ 7. AutoOversight System — regression suites, risk scoring, auto-rollback
15
+ 8. Recursive Evolution Loop — generate → evaluate → select → mutate → validate → integrate
16
+
17
+ Run: python aether_autonomous.py
18
+ Dependencies: torch, numpy, networkx
19
+ """
20
+
21
+ import torch
22
+ import torch.nn as nn
23
+ import torch.nn.functional as F
24
+ import numpy as np
25
+ import networkx as nx
26
+ import copy, json, hashlib, time, random, logging, warnings
27
+ from dataclasses import dataclass, field, asdict
28
+ from typing import Dict, List, Any, Optional, Tuple, Callable
29
+ from collections import deque
30
+ from contextlib import contextmanager
31
+
32
+ warnings.filterwarnings("ignore")
33
+ logging.basicConfig(
34
+ level=logging.INFO,
35
+ format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
36
+ )
37
+ logger = logging.getLogger("AETHER")
38
+
39
+
40
+ # ============================================================================
41
+ # 0. CONFIGURATION
42
+ # ============================================================================
43
+
44
+ @dataclass
45
+ class AetherConfig:
46
+ population_size: int = 8
47
+ generations: int = 10
48
+ mutation_rate: float = 0.15
49
+ crossover_rate: float = 0.30
50
+
51
+ macro_policy_dim: int = 256
52
+ micro_policy_dim: int = 128
53
+ num_agents: int = 4
54
+
55
+ working_memory_capacity: int = 16
56
+ episodic_buffer_size: int = 1000
57
+
58
+ kg_embedding_dim: int = 128
59
+ kg_num_relations: int = 20
60
+
61
+ learning_rate: float = 2e-5
62
+ batch_size: int = 4
63
+
64
+ enable_self_modification: bool = True
65
+ enable_parallel_agents: bool = True
66
+
67
+ # Auto-oversight thresholds (fully automated)
68
+ max_mutation_rate: float = 0.50
69
+ max_agents: int = 16
70
+ max_memory_mb: float = 8192.0
71
+ rollback_fitness_drop: float = 0.15
72
+ stability_window: int = 3
73
+ risk_threshold: float = 0.70
74
+
75
+ # MAP-Elites
76
+ archive_dims: Tuple[int, int] = (10, 10)
77
+
78
+ def to_vector(self) -> np.ndarray:
79
+ return np.array([
80
+ self.population_size,
81
+ self.mutation_rate,
82
+ self.learning_rate * 1e5,
83
+ self.macro_policy_dim,
84
+ self.micro_policy_dim,
85
+ self.num_agents,
86
+ self.kg_embedding_dim,
87
+ ], dtype=np.float32)
88
+
89
+ @classmethod
90
+ def from_vector(cls, vec: np.ndarray) -> "AetherConfig":
91
+ return cls(
92
+ population_size=int(np.clip(vec[0], 2, 64)),
93
+ mutation_rate=float(np.clip(vec[1], 0.01, 0.5)),
94
+ learning_rate=float(np.clip(vec[2] / 1e5, 1e-6, 1e-3)),
95
+ macro_policy_dim=int(np.clip(vec[3], 64, 512)),
96
+ micro_policy_dim=int(np.clip(vec[4], 32, 256)),
97
+ num_agents=int(np.clip(vec[5], 1, 16)),
98
+ kg_embedding_dim=int(np.clip(vec[6], 32, 512)),
99
+ )
100
+
101
+
102
+ # ============================================================================
103
+ # 1. AUTO-OVERSIGHT (replaces human-in-the-loop)
104
+ # ============================================================================
105
+
106
+ class AutoOversight:
107
+ """
108
+ Fully automated oversight. No human approval.
109
+ Components:
110
+ - Risk scorer: estimates danger of proposed mutation
111
+ - Regression suite: quick benchmarks that must not degrade
112
+ - Stability validator: checks config bounds, memory, consistency
113
+ - Auto-rollback: reverts to last known good if fitness drops
114
+ """
115
+
116
+ def __init__(self, config: AetherConfig):
117
+ self.config = config
118
+ self.audit_log: List[Dict] = []
119
+ self.modification_history: List[Dict] = []
120
+ self.baseline_fitness: float = 0.0
121
+ self.fitness_history: deque = deque(maxlen=config.stability_window * 2)
122
+ self.last_good_config: Optional[AetherConfig] = None
123
+ self.last_good_fitness: float = -float("inf")
124
+ self.consecutive_rejections: int = 0
125
+
126
+ def risk_score(self, candidate: AetherConfig) -> float:
127
+ """Return 0..1 risk. >threshold = reject."""
128
+ risks = []
129
+
130
+ # Mutation rate risk
131
+ risks.append(min(1.0, candidate.mutation_rate / self.config.max_mutation_rate))
132
+
133
+ # Agent count risk
134
+ risks.append(min(1.0, candidate.num_agents / self.config.max_agents))
135
+
136
+ # Memory estimate risk
137
+ est_mem = (candidate.macro_policy_dim * candidate.micro_policy_dim *
138
+ candidate.num_agents * 4) / 1e6
139
+ risks.append(min(1.0, est_mem / self.config.max_memory_mb))
140
+
141
+ # Dimension consistency risk
142
+ if candidate.micro_policy_dim > candidate.macro_policy_dim:
143
+ risks.append(1.0)
144
+ else:
145
+ risks.append(0.0)
146
+
147
+ return float(np.mean(risks))
148
+
149
+ def validate_stability(self, candidate: AetherConfig) -> Tuple[bool, str]:
150
+ checks = {
151
+ "population_size": (2, 64),
152
+ "mutation_rate": (0.0, self.config.max_mutation_rate),
153
+ "learning_rate": (1e-6, 1e-3),
154
+ "num_agents": (1, self.config.max_agents),
155
+ "macro_policy_dim": (32, 512),
156
+ "micro_policy_dim": (16, 256),
157
+ }
158
+ violations = []
159
+ for field_name, (lo, hi) in checks.items():
160
+ val = getattr(candidate, field_name, None)
161
+ if val is not None and not (lo <= val <= hi):
162
+ violations.append(f"{field_name}={val} not in [{lo},{hi}]")
163
+
164
+ if candidate.micro_policy_dim > candidate.macro_policy_dim:
165
+ violations.append("micro > macro")
166
+
167
+ if violations:
168
+ return False, "; ".join(violations)
169
+ return True, "ok"
170
+
171
+ def regression_suite(self, candidate: AetherConfig,
172
+ core: "AetherCore") -> Tuple[bool, float]:
173
+ """
174
+ Quick synthetic benchmarks. Returns (pass, composite_score).
175
+ Higher = better.
176
+ """
177
+ scores = []
178
+
179
+ # Benchmark 1: memory throughput
180
+ try:
181
+ wm = WorkingMemory(capacity=candidate.working_memory_capacity)
182
+ for i in range(100):
183
+ wm.store({"idx": i, "data": torch.randn(16)})
184
+ retrieved = wm.retrieve("idx", top_k=5)
185
+ scores.append(len(retrieved) / 5.0)
186
+ except Exception as e:
187
+ scores.append(0.0)
188
+
189
+ # Benchmark 2: knowledge graph query speed
190
+ try:
191
+ kg = KnowledgeGraphEngine(
192
+ embedding_dim=candidate.kg_embedding_dim,
193
+ num_relations=candidate.kg_num_relations,
194
+ )
195
+ for i in range(20):
196
+ kg.add_fact(f"Node{i}", "relates_to", f"Node{i+1}")
197
+ q = kg.query("Node0 relates_to", top_k=3)
198
+ scores.append(min(1.0, len(q["results"]) / 3.0))
199
+ except Exception:
200
+ scores.append(0.0)
201
+
202
+ # Benchmark 3: agent orchestration latency
203
+ try:
204
+ orch = AetherAgentOrchestrator(candidate)
205
+ task_embed = torch.randn(1, candidate.macro_policy_dim)
206
+ blueprint = orch.hierarchical.generate_blueprint(task_embed)
207
+ scores.append(min(1.0, len(blueprint) / 3.0))
208
+ except Exception:
209
+ scores.append(0.0)
210
+
211
+ composite = float(np.mean(scores))
212
+ # Must beat baseline by at least rollback threshold, or be first run
213
+ if self.baseline_fitness > 0 and composite < self.baseline_fitness * (1 - self.config.rollback_fitness_drop):
214
+ return False, composite
215
+ return True, composite
216
+
217
+ def should_rollback(self, current_fitness: float) -> bool:
218
+ """Auto-rollback if fitness drops significantly."""
219
+ if self.last_good_fitness == -float("inf"):
220
+ return False
221
+ drop = (self.last_good_fitness - current_fitness) / (abs(self.last_good_fitness) + 1e-8)
222
+ return drop > self.config.rollback_fitness_drop
223
+
224
+ def decide(self, candidate: AetherConfig, core: "AetherCore") -> Tuple[bool, float, str]:
225
+ """
226
+ Automated decision gate. Returns (approved, score, reason).
227
+ No human involved.
228
+ """
229
+ risk = self.risk_score(candidate)
230
+ if risk > self.config.risk_threshold:
231
+ self._log(candidate, False, f"risk={risk:.2f} > threshold")
232
+ self.consecutive_rejections += 1
233
+ return False, risk, "auto-rejected: high risk"
234
+
235
+ stable, stability_reason = self.validate_stability(candidate)
236
+ if not stable:
237
+ self._log(candidate, False, stability_reason)
238
+ self.consecutive_rejections += 1
239
+ return False, risk, f"auto-rejected: unstable ({stability_reason})"
240
+
241
+ reg_pass, reg_score = self.regression_suite(candidate, core)
242
+ if not reg_pass:
243
+ self._log(candidate, False, f"regression fail score={reg_score:.3f}")
244
+ self.consecutive_rejections += 1
245
+ return False, reg_score, "auto-rejected: regression failure"
246
+
247
+ self._log(candidate, True, f"risk={risk:.2f} reg={reg_score:.3f}")
248
+ self.consecutive_rejections = 0
249
+ self.baseline_fitness = max(self.baseline_fitness, reg_score)
250
+ return True, reg_score, "auto-approved"
251
+
252
+ def _log(self, candidate: AetherConfig, approved: bool, reason: str):
253
+ entry = {
254
+ "timestamp": time.time(),
255
+ "approved": approved,
256
+ "config_hash": hashlib.sha256(
257
+ json.dumps(asdict(candidate), sort_keys=True).encode()
258
+ ).hexdigest()[:16],
259
+ "reason": reason,
260
+ }
261
+ self.modification_history.append(entry)
262
+ self.audit_log.append(entry)
263
+
264
+ def update_good_checkpoint(self, config: AetherConfig, fitness: float):
265
+ self.last_good_config = copy.deepcopy(config)
266
+ self.last_good_fitness = fitness
267
+
268
+ def summary(self) -> Dict[str, Any]:
269
+ total = len(self.modification_history)
270
+ approved = sum(1 for m in self.modification_history if m["approved"])
271
+ return {
272
+ "total_attempted": total,
273
+ "approved": approved,
274
+ "rejected": total - approved,
275
+ "consecutive_rejections": self.consecutive_rejections,
276
+ "baseline_fitness": self.baseline_fitness,
277
+ "last_good_fitness": self.last_good_fitness,
278
+ }
279
+
280
+
281
+ # ============================================================================
282
+ # 2. MEMORY SYSTEM (CoALA 4-tier + Temporal)
283
+ # ============================================================================
284
+
285
+ class WorkingMemory:
286
+ def __init__(self, capacity: int = 16):
287
+ self.capacity = capacity
288
+ self.buffer: deque = deque(maxlen=capacity)
289
+ self.attention = nn.Parameter(torch.ones(capacity))
290
+
291
+ def store(self, item: Dict[str, Any]):
292
+ item["_t"] = time.time()
293
+ self.buffer.append(item)
294
+
295
+ def retrieve(self, query: str, top_k: int = 3) -> List[Dict]:
296
+ if not self.buffer:
297
+ return []
298
+ scores = []
299
+ buf = list(self.buffer)
300
+ for i, item in enumerate(buf):
301
+ text = json.dumps(item)
302
+ score = sum(1 for w in query.lower().split() if w in text.lower())
303
+ # attention weighting (learned)
304
+ attn = torch.sigmoid(self.attention[i % self.capacity]).item()
305
+ scores.append(score * attn)
306
+ indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k]
307
+ return [buf[i] for i in indices]
308
+
309
+ def export(self) -> List[Dict]:
310
+ return list(self.buffer)
311
+
312
+
313
+ class EpisodicMemory:
314
+ def __init__(self, buffer_size: int = 1000):
315
+ self.buffer: deque = deque(maxlen=buffer_size)
316
+
317
+ def store(self, episode: Dict[str, Any]):
318
+ episode["_t"] = time.time()
319
+ self.buffer.append(episode)
320
+
321
+ def retrieve_similar(self, query: str, top_k: int = 5) -> List[Dict]:
322
+ if not self.buffer:
323
+ return []
324
+ buf = list(self.buffer)
325
+ scores = []
326
+ for item in buf:
327
+ text = json.dumps(item)
328
+ scores.append(sum(1 for w in query.lower().split() if w in text.lower()))
329
+ indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k]
330
+ return [buf[i] for i in indices]
331
+
332
+ def get_recent(self, n: int = 10) -> List[Dict]:
333
+ return list(self.buffer)[-n:]
334
+
335
+ def export(self) -> List[Dict]:
336
+ return list(self.buffer)
337
+
338
+
339
+ class SemanticMemory:
340
+ def __init__(self):
341
+ self.facts: Dict[str, Any] = {}
342
+
343
+ def store_fact(self, key: str, value: Any, confidence: float = 1.0):
344
+ self.facts[key] = {"value": value, "confidence": confidence, "t": time.time()}
345
+
346
+ def retrieve(self, key: str) -> Optional[Dict]:
347
+ return self.facts.get(key)
348
+
349
+ def query(self, query: str) -> List[Dict]:
350
+ return [v for k, v in self.facts.items() if query.lower() in k.lower()]
351
+
352
+ def export(self) -> Dict:
353
+ return self.facts
354
+
355
+
356
+ class ProceduralMemory:
357
+ def __init__(self):
358
+ self.tools: Dict[str, Dict] = {}
359
+ self.usage: Dict[str, int] = {}
360
+
361
+ def register_tool(self, name: str, code: str, description: str, tags: List[str] = None):
362
+ self.tools[name] = {
363
+ "code": code, "description": description,
364
+ "tags": tags or [], "registered_at": time.time(), "version": 1,
365
+ }
366
+ self.usage[name] = 0
367
+
368
+ def get_tool(self, name: str) -> Optional[Dict]:
369
+ if name in self.tools:
370
+ self.usage[name] += 1
371
+ return self.tools[name]
372
+ return None
373
+
374
+ def search_tools(self, query: str) -> List[Dict]:
375
+ out = []
376
+ for name, tool in self.tools.items():
377
+ text = f"{name} {tool['description']} {' '.join(tool['tags'])}"
378
+ if query.lower() in text.lower():
379
+ out.append({"name": name, **tool})
380
+ return out
381
+
382
+ def merge_tools(self, cluster: List[str]) -> Optional[str]:
383
+ if len(cluster) < 2:
384
+ return None
385
+ canonical = max(cluster, key=lambda t: self.usage.get(t, 0))
386
+ merged_desc = " | ".join(self.tools[t]["description"] for t in cluster if t in self.tools)
387
+ self.tools[canonical]["description"] = merged_desc
388
+ self.tools[canonical]["version"] += 1
389
+ for t in cluster:
390
+ if t != canonical and t in self.tools:
391
+ del self.tools[t]
392
+ return canonical
393
+
394
+ def export(self) -> Dict:
395
+ return {"tools": self.tools, "usage": self.usage}
396
+
397
+
398
+ class CoALAMemory:
399
+ def __init__(self, capacity: int = 16):
400
+ self.working = WorkingMemory(capacity=capacity)
401
+ self.episodic = EpisodicMemory(buffer_size=1000)
402
+ self.semantic = SemanticMemory()
403
+ self.procedural = ProceduralMemory()
404
+
405
+ def store(self, item: Dict[str, Any], memory_type: str = "working"):
406
+ if memory_type == "working":
407
+ self.working.store(item)
408
+ elif memory_type == "episodic":
409
+ self.episodic.store(item)
410
+ elif memory_type == "semantic":
411
+ for k, v in item.items():
412
+ self.semantic.store_fact(k, v)
413
+ elif memory_type == "procedural":
414
+ if "name" in item and "code" in item:
415
+ self.procedural.register_tool(
416
+ item["name"], item["code"],
417
+ item.get("description", ""), item.get("tags", [])
418
+ )
419
+
420
+ def retrieve(self, query: str, memory_type: str = "all", top_k: int = 5) -> List[Dict]:
421
+ if memory_type == "all":
422
+ out = []
423
+ out.extend(self.working.retrieve(query, top_k=top_k // 2))
424
+ out.extend(self.episodic.retrieve_similar(query, top_k=top_k))
425
+ out.extend(self.semantic.query(query)[:top_k])
426
+ return out[:top_k]
427
+ elif memory_type == "working":
428
+ return self.working.retrieve(query, top_k)
429
+ elif memory_type == "episodic":
430
+ return self.episodic.retrieve_similar(query, top_k)
431
+ elif memory_type == "semantic":
432
+ return self.semantic.query(query)[:top_k]
433
+ elif memory_type == "procedural":
434
+ return self.procedural.search_tools(query)
435
+ return []
436
+
437
+ @property
438
+ def buffer(self):
439
+ return self.working.buffer
440
+
441
+ def export(self) -> Dict[str, Any]:
442
+ return {
443
+ "working": self.working.export(),
444
+ "episodic": self.episodic.export(),
445
+ "semantic": self.semantic.export(),
446
+ "procedural": self.procedural.export(),
447
+ }
448
+
449
+
450
+ class TemporalMemory(nn.Module):
451
+ def __init__(self, buffer_size: int = 1000, hidden_dim: int = 64):
452
+ super().__init__()
453
+ self.buffer_size = buffer_size
454
+ self.hidden_dim = hidden_dim
455
+ self.buffer: deque = deque(maxlen=buffer_size)
456
+ self.temporal_gate = nn.Sequential(
457
+ nn.Linear(2, hidden_dim), nn.ReLU(),
458
+ nn.Linear(hidden_dim, 1), nn.Sigmoid(),
459
+ )
460
+
461
+ def store(self, event: Dict[str, Any]):
462
+ event["_t"] = time.time()
463
+ self.buffer.append(event)
464
+
465
+ def retrieve_context(self, current_time: Optional[float] = None,
466
+ lookback: float = 3600.0) -> List[Dict]:
467
+ current_time = current_time or time.time()
468
+ relevant = []
469
+ for event in self.buffer:
470
+ age = current_time - event.get("_t", current_time)
471
+ if age <= lookback:
472
+ recency = torch.exp(torch.tensor(-age / lookback)).item()
473
+ relevant.append({**event, "recency": recency, "age": age})
474
+ relevant.sort(key=lambda x: x["recency"], reverse=True)
475
+ return relevant
476
+
477
+ def retrieve_with_attention(self, query_embed: torch.Tensor, top_k: int = 10) -> List[Dict]:
478
+ # Simplified: use recency-weighted retrieval
479
+ return self.retrieve_context()[:top_k]
480
+
481
+ def export(self) -> List[Dict]:
482
+ return list(self.buffer)
483
+
484
+ def __len__(self):
485
+ return len(self.buffer)
486
+
487
+
488
+ # ============================================================================
489
+ # 3. KNOWLEDGE GRAPH ENGINE (RGCN + ComplEx + Symbolic Rules)
490
+ # ============================================================================
491
+
492
+ class RGCNLayer(nn.Module):
493
+ def __init__(self, in_dim: int, out_dim: int, num_relations: int, num_bases: int = 4):
494
+ super().__init__()
495
+ self.num_relations = num_relations
496
+ self.bases = nn.Parameter(torch.Tensor(num_bases, in_dim, out_dim))
497
+ self.comp = nn.Parameter(torch.Tensor(num_relations, num_bases))
498
+ self.self_loop = nn.Parameter(torch.Tensor(in_dim, out_dim))
499
+ self.bias = nn.Parameter(torch.Tensor(out_dim))
500
+ self.reset_parameters()
501
+
502
+ def reset_parameters(self):
503
+ nn.init.xavier_uniform_(self.bases)
504
+ nn.init.xavier_uniform_(self.comp)
505
+ nn.init.xavier_uniform_(self.self_loop)
506
+ nn.init.zeros_(self.bias)
507
+
508
+ def forward(self, x, edge_index, edge_type):
509
+ num_nodes = int(edge_index.max().item()) + 1 if x is None else x.size(0)
510
+ if x is None:
511
+ x = torch.eye(num_nodes, self.bases.size(1), device=edge_index.device)
512
+ weight = torch.einsum("rb,bio->rio", self.comp, self.bases)
513
+ out = torch.zeros(num_nodes, weight.size(2), device=x.device)
514
+ for rid in range(self.num_relations):
515
+ mask = edge_type == rid
516
+ if mask.sum() == 0:
517
+ continue
518
+ ei = edge_index[:, mask]
519
+ messages = torch.mm(x[ei[0]], weight[rid])
520
+ out.index_add_(0, ei[1], messages)
521
+ out = out + torch.mm(x, self.self_loop) + self.bias
522
+ return out
523
+
524
+
525
+ class KnowledgeGraphEncoder(nn.Module):
526
+ def __init__(self, num_nodes, hidden_dim, num_relations, num_layers=2, num_bases=4):
527
+ super().__init__()
528
+ self.node_embeddings = nn.Embedding(num_nodes, hidden_dim)
529
+ self.layers = nn.ModuleList([
530
+ RGCNLayer(hidden_dim, hidden_dim, num_relations, num_bases)
531
+ for _ in range(num_layers)
532
+ ])
533
+ self.norms = nn.ModuleList([nn.LayerNorm(hidden_dim) for _ in range(num_layers)])
534
+
535
+ def forward(self, edge_index, edge_type):
536
+ num_nodes = int(edge_index.max().item()) + 1
537
+ x = self.node_embeddings(torch.arange(num_nodes, device=edge_index.device))
538
+ for layer, norm in zip(self.layers, self.norms):
539
+ x = F.relu(norm(layer(x, edge_index, edge_type)))
540
+ return x
541
+
542
+
543
+ class ComplExScorer(nn.Module):
544
+ def __init__(self, num_nodes, num_relations, hidden_dim=50):
545
+ super().__init__()
546
+ self.head_real = nn.Embedding(num_nodes, hidden_dim)
547
+ self.head_imag = nn.Embedding(num_nodes, hidden_dim)
548
+ self.tail_real = nn.Embedding(num_nodes, hidden_dim)
549
+ self.tail_imag = nn.Embedding(num_nodes, hidden_dim)
550
+ self.rel_real = nn.Embedding(num_relations, hidden_dim)
551
+ self.rel_imag = nn.Embedding(num_relations, hidden_dim)
552
+ self.reset_parameters()
553
+
554
+ def reset_parameters(self):
555
+ for p in self.parameters():
556
+ nn.init.xavier_uniform_(p)
557
+
558
+ def forward(self, h, r, t):
559
+ hr, hi = self.head_real(h), self.head_imag(h)
560
+ tr, ti = self.tail_real(t), self.tail_imag(t)
561
+ rr, ri = self.rel_real(r), self.rel_imag(r)
562
+ return torch.sum(hr * rr * tr + hr * ri * ti + hi * rr * ti - hi * ri * tr, dim=-1)
563
+
564
+ def loss(self, h, r, t, neg_t=None):
565
+ pos = self.forward(h, r, t)
566
+ if neg_t is None:
567
+ neg_t = torch.randint(0, self.tail_real.num_embeddings, t.size(), device=t.device)
568
+ neg = self.forward(h, r, neg_t)
569
+ return (F.softplus(-pos) + F.softplus(neg)).mean()
570
+
571
+
572
+ class KnowledgeGraphEngine(nn.Module):
573
+ def __init__(self, embedding_dim=128, num_relations=20, max_nodes=10000):
574
+ super().__init__()
575
+ self.embedding_dim = embedding_dim
576
+ self.num_relations = num_relations
577
+ self.max_nodes = max_nodes
578
+ self.graph = nx.DiGraph()
579
+ self.node_id_map: Dict[str, int] = {}
580
+ self.relation_map: Dict[str, int] = {}
581
+ self.next_node_id = 0
582
+ self.next_rel_id = 0
583
+ self.encoder: Optional[KnowledgeGraphEncoder] = None
584
+ self.scorer: Optional[ComplExScorer] = None
585
+ self.symbolic_attention = nn.Parameter(torch.ones(num_relations))
586
+ self.rules: List[Tuple[Tuple[str, str, str], Tuple[str, str, str]]] = []
587
+
588
+ def _get_or_create_node(self, name: str) -> int:
589
+ if name not in self.node_id_map:
590
+ self.node_id_map[name] = self.next_node_id
591
+ self.graph.add_node(self.next_node_id, name=name)
592
+ self.next_node_id += 1
593
+ return self.node_id_map[name]
594
+
595
+ def _get_or_create_relation(self, name: str) -> int:
596
+ if name not in self.relation_map:
597
+ self.relation_map[name] = self.next_rel_id
598
+ self.next_rel_id += 1
599
+ return self.relation_map[name]
600
+
601
+ def add_fact(self, head: str, relation: str, tail: str, confidence: float = 1.0):
602
+ h = self._get_or_create_node(head)
603
+ t = self._get_or_create_node(tail)
604
+ r = self._get_or_create_relation(relation)
605
+ self.graph.add_edge(h, t, relation=r, name=relation, confidence=confidence)
606
+ self._ensure_capacity()
607
+
608
+ def add_rule(self, premise: Tuple[str, str, str], conclusion: Tuple[str, str, str]):
609
+ self.rules.append((premise, conclusion))
610
+
611
+ def _ensure_capacity(self):
612
+ if self.encoder is None and self.next_node_id > 0:
613
+ n = min(self.next_node_id, self.max_nodes)
614
+ r = max(self.next_rel_id, self.num_relations)
615
+ self.encoder = KnowledgeGraphEncoder(n, self.embedding_dim, r)
616
+ self.scorer = ComplExScorer(n, r, self.embedding_dim // 2)
617
+ logger.info(f"KG initialized: {n} nodes, {r} relations")
618
+
619
+ def _check_fact(self, fact: Tuple[str, str, str]) -> bool:
620
+ h, r, t = fact
621
+ if h not in self.node_id_map or t not in self.node_id_map or r not in self.relation_map:
622
+ return False
623
+ return self.graph.has_edge(self.node_id_map[h], self.node_id_map[t]) and \
624
+ self.graph.edges[self.node_id_map[h], self.node_id_map[t]].get("relation") == self.relation_map[r]
625
+
626
+ def reason_symbolic(self, query_head: str, query_relation: str) -> List[Dict]:
627
+ results = []
628
+ if query_head not in self.node_id_map:
629
+ return results
630
+ h_id = self.node_id_map[query_head]
631
+ r_name = query_relation
632
+ if r_name in self.relation_map:
633
+ r_id = self.relation_map[r_name]
634
+ for _, target, data in self.graph.out_edges(h_id, data=True):
635
+ if data.get("relation") == r_id:
636
+ results.append({
637
+ "head": query_head, "relation": r_name,
638
+ "tail": self.graph.nodes[target].get("name", str(target)),
639
+ "confidence": data.get("confidence", 1.0), "path": "direct",
640
+ })
641
+ # Rule inference
642
+ for premise, conclusion in self.rules:
643
+ p_head, p_rel, p_tail = premise
644
+ c_head, c_rel, c_tail = conclusion
645
+ if p_head == query_head and self._check_fact(premise):
646
+ results.append({
647
+ "head": c_head if c_head != "?" else query_head,
648
+ "relation": c_rel, "tail": c_tail,
649
+ "confidence": 0.8, "path": "inferred",
650
+ "rule": f"{premise} -> {conclusion}",
651
+ })
652
+ # Multi-hop BFS
653
+ for neighbor in nx.bfs_tree(self.graph, h_id, depth_limit=2).nodes():
654
+ if neighbor != h_id:
655
+ for path in nx.all_simple_paths(self.graph, h_id, neighbor, cutoff=2):
656
+ if len(path) > 1:
657
+ ed = self.graph.edges[path[0], path[1]]
658
+ results.append({
659
+ "head": query_head,
660
+ "relation": f"multi-hop via {ed.get('name', 'unknown')}",
661
+ "tail": self.graph.nodes[neighbor].get("name", str(neighbor)),
662
+ "confidence": 0.6 ** (len(path) - 1),
663
+ "path": "->".join(str(n) for n in path),
664
+ })
665
+ return sorted(results, key=lambda x: x["confidence"], reverse=True)
666
+
667
+ def reason_learned(self, query_head: str, query_relation: str, top_k: int = 5) -> List[Dict]:
668
+ if self.scorer is None or query_head not in self.node_id_map:
669
+ return []
670
+ h_id = self.node_id_map[query_head]
671
+ r_id = self.relation_map.get(query_relation)
672
+ if r_id is None:
673
+ return []
674
+ h_t = torch.tensor([h_id])
675
+ r_t = torch.tensor([r_id])
676
+ all_t = torch.arange(self.scorer.tail_real.num_embeddings)
677
+ scores = []
678
+ for i in range(0, len(all_t), 1000):
679
+ batch = all_t[i:i + 1000]
680
+ scores.extend(self.scorer(h_t.repeat(len(batch)), r_t.repeat(len(batch)), batch).tolist())
681
+ scores_t = torch.tensor(scores)
682
+ top_scores, top_idx = torch.topk(scores_t, min(top_k, len(scores_t)))
683
+ results = []
684
+ for idx, sc in zip(top_idx, top_scores):
685
+ node_name = self.graph.nodes[idx.item()].get("name", str(idx.item()))
686
+ results.append({
687
+ "head": query_head, "relation": query_relation,
688
+ "tail": node_name, "confidence": torch.sigmoid(sc).item(), "path": "learned",
689
+ })
690
+ return results
691
+
692
+ def query(self, text_query: str, top_k: int = 5) -> Dict[str, Any]:
693
+ parts = text_query.lower().split()
694
+ head = parts[0].capitalize() if parts else text_query.capitalize()
695
+ relation = " ".join(parts[1:]) if len(parts) > 1 else "related_to"
696
+ sym = self.reason_symbolic(head, relation)[:top_k]
697
+ learned = self.reason_learned(head, relation, top_k)
698
+ rel_id = self.relation_map.get(relation, 0)
699
+ sym_w = torch.sigmoid(self.symbolic_attention[rel_id % self.num_relations]).item()
700
+ learned_w = 1.0 - sym_w
701
+ for r in sym:
702
+ r["source"] = "symbolic"
703
+ r["fusion_weight"] = sym_w
704
+ for r in learned:
705
+ r["source"] = "learned"
706
+ r["fusion_weight"] = learned_w
707
+ all_r = sorted(sym + learned, key=lambda x: x.get("confidence", 0), reverse=True)
708
+ return {
709
+ "query": text_query, "results": all_r[:top_k],
710
+ "symbolic_weight": sym_w, "learned_weight": learned_w,
711
+ "num_symbolic": len(sym), "num_learned": len(learned),
712
+ }
713
+
714
+ def stats(self) -> Dict[str, Any]:
715
+ return {
716
+ "num_nodes": self.graph.number_of_nodes(),
717
+ "num_edges": self.graph.number_of_edges(),
718
+ "num_relations": len(self.relation_map),
719
+ "num_rules": len(self.rules),
720
+ }
721
+
722
+ def export(self) -> Dict[str, Any]:
723
+ edges = []
724
+ for u, v, d in self.graph.edges(data=True):
725
+ edges.append({"source": u, "target": v, "relation": d.get("name"), "confidence": d.get("confidence")})
726
+ return {
727
+ "nodes": {n: self.graph.nodes[n].get("name", str(n)) for n in self.graph.nodes()},
728
+ "edges": edges, "rules": self.rules,
729
+ }
730
+
731
+
732
+ # ============================================================================
733
+ # 4. AGENT ORCHESTRATION (4 roles + Hierarchical + BabyAGI loop)
734
+ # ============================================================================
735
+
736
+ class AgentRole:
737
+ RESEARCHER = "researcher"
738
+ ENGINEER = "engineer"
739
+ ANALYZER = "analyzer"
740
+ INTEGRATOR = "integrator"
741
+
742
+
743
+ class BaseAgent(nn.Module):
744
+ def __init__(self, role: str, hidden_dim: int = 128, vocab_size: int = 32000):
745
+ super().__init__()
746
+ self.role = role
747
+ self.hidden_dim = hidden_dim
748
+ self.encoder = nn.Sequential(
749
+ nn.Embedding(vocab_size, hidden_dim),
750
+ nn.LSTM(hidden_dim, hidden_dim, batch_first=True),
751
+ )
752
+ self.policy_head = nn.Linear(hidden_dim, hidden_dim)
753
+ self.value_head = nn.Linear(hidden_dim, 1)
754
+ self.task_history: deque = deque(maxlen=100)
755
+ self.performance_log: List[float] = []
756
+
757
+ def forward(self, input_ids: torch.Tensor) -> Dict[str, torch.Tensor]:
758
+ embeds = self.encoder[0](input_ids)
759
+ lstm_out, _ = self.encoder[1](embeds)
760
+ hidden = lstm_out[:, -1, :]
761
+ return {
762
+ "policy_logits": self.policy_head(hidden),
763
+ "value": self.value_head(hidden),
764
+ "hidden": hidden,
765
+ }
766
+
767
+ def act(self, observation: str) -> str:
768
+ self.task_history.append({"observation": observation, "t": time.time()})
769
+ actions = {
770
+ AgentRole.RESEARCHER: f"[RESEARCHER] Exploring knowledge for: '{observation[:50]}...'",
771
+ AgentRole.ENGINEER: f"[ENGINEER] Synthesizing tool for: '{observation[:50]}...'",
772
+ AgentRole.ANALYZER: f"[ANALYZER] Evaluating solution for: '{observation[:50]}...'",
773
+ AgentRole.INTEGRATOR: f"[INTEGRATOR] Merging components for: '{observation[:50]}...'",
774
+ }
775
+ return actions.get(self.role, f"[{self.role.upper()}] Processing: '{observation}'")
776
+
777
+ def update(self, reward: float):
778
+ self.performance_log.append(reward)
779
+
780
+
781
+ class HierarchicalAgent(nn.Module):
782
+ """Macro-policy generates blueprints; micro-policy executes conditioned on blueprint."""
783
+
784
+ def __init__(self, macro_dim: int = 256, micro_dim: int = 128, num_subgoals: int = 5):
785
+ super().__init__()
786
+ self.macro_dim = macro_dim
787
+ self.micro_dim = micro_dim
788
+ self.num_subgoals = num_subgoals
789
+ self.macro_decoder = nn.LSTM(macro_dim, macro_dim, batch_first=True)
790
+ self.subgoal_head = nn.Linear(macro_dim, num_subgoals)
791
+ self.termination_token = nn.Parameter(torch.randn(macro_dim))
792
+ self.micro_encoder = nn.LSTM(micro_dim + macro_dim, micro_dim, batch_first=True)
793
+ self.action_head = nn.Linear(micro_dim, 50)
794
+ self.current_blueprint: Optional[List[str]] = None
795
+ self.active_subgoal_idx = 0
796
+
797
+ def generate_blueprint(self, task_embedding: torch.Tensor) -> List[str]:
798
+ batch_size = task_embedding.size(0)
799
+ hidden = (torch.zeros(1, batch_size, self.macro_dim),
800
+ torch.zeros(1, batch_size, self.macro_dim))
801
+ input_tok = task_embedding.unsqueeze(1)
802
+ blueprints = []
803
+ for _ in range(self.num_subgoals):
804
+ out, hidden = self.macro_decoder(input_tok, hidden)
805
+ sg_logits = self.subgoal_head(out.squeeze(1))
806
+ sg_id = torch.argmax(sg_logits, dim=-1)
807
+ sim = torch.cosine_similarity(out.squeeze(1), self.termination_token.unsqueeze(0))
808
+ if sim.item() > 0.9:
809
+ break
810
+ blueprints.append(f"subgoal_{sg_id.item()}")
811
+ input_tok = out
812
+ self.current_blueprint = blueprints
813
+ self.active_subgoal_idx = 0
814
+ return blueprints
815
+
816
+ def execute_action(self, observation: torch.Tensor, blueprint: Optional[List[str]] = None) -> torch.Tensor:
817
+ if blueprint is not None:
818
+ self.current_blueprint = blueprint
819
+ if not self.current_blueprint:
820
+ return torch.zeros(1, 50)
821
+ active = self.current_blueprint[min(self.active_subgoal_idx, len(self.current_blueprint) - 1)]
822
+ subgoal_embed = torch.randn(1, self.macro_dim)
823
+ combined = torch.cat([observation, subgoal_embed], dim=-1)
824
+ out, _ = self.micro_encoder(combined.unsqueeze(1))
825
+ return self.action_head(out.squeeze(1))
826
+
827
+ def advance_subgoal(self):
828
+ self.active_subgoal_idx += 1
829
+
830
+ def reset(self):
831
+ self.current_blueprint = None
832
+ self.active_subgoal_idx = 0
833
+
834
+
835
+ class BabyAGILoop:
836
+ def __init__(self, objective: str, max_iterations: int = 50):
837
+ self.objective = objective
838
+ self.max_iterations = max_iterations
839
+ self.task_list: deque = deque()
840
+ self.completed: List[Dict] = []
841
+ self.results: Dict[int, Any] = {}
842
+ self.iteration = 0
843
+
844
+ def create_tasks(self, previous_result: str, task_desc: str) -> List[str]:
845
+ return [f"Sub-task {len(self.task_list) + i}: Analyze {previous_result[:30]}..." for i in range(3)]
846
+
847
+ def prioritize(self) -> List[str]:
848
+ tasks = list(self.task_list)
849
+ scores = [sum(1 for w in self.objective.lower().split() if w in t.lower()) for t in tasks]
850
+ return [t for _, t in sorted(zip(scores, tasks), reverse=True)]
851
+
852
+ def execute(self, task: str, agent: BaseAgent) -> str:
853
+ result = agent.act(task)
854
+ self.completed.append({"task": task, "result": result, "iteration": self.iteration})
855
+ return result
856
+
857
+ def run(self, agent: BaseAgent) -> Dict[str, Any]:
858
+ self.task_list.append(self.objective)
859
+ while self.iteration < self.max_iterations and self.task_list:
860
+ prioritized = self.prioritize()
861
+ self.task_list = deque(prioritized)
862
+ current = self.task_list.popleft()
863
+ prev = self.completed[-1]["result"] if self.completed else ""
864
+ result = self.execute(current, agent)
865
+ self.results[self.iteration] = result
866
+ for t in self.create_tasks(result, current):
867
+ if t not in self.task_list:
868
+ self.task_list.append(t)
869
+ self.iteration += 1
870
+ return {
871
+ "completed": self.completed, "results": self.results,
872
+ "iterations": self.iteration, "objective": self.objective,
873
+ }
874
+
875
+
876
+ class AetherAgentOrchestrator(nn.Module):
877
+ def __init__(self, config: AetherConfig):
878
+ super().__init__()
879
+ self.config = config
880
+ self.agents: Dict[str, BaseAgent] = nn.ModuleDict({
881
+ "researcher": BaseAgent(AgentRole.RESEARCHER, hidden_dim=config.macro_policy_dim),
882
+ "engineer": BaseAgent(AgentRole.ENGINEER, hidden_dim=config.micro_policy_dim),
883
+ "analyzer": BaseAgent(AgentRole.ANALYZER, hidden_dim=config.micro_policy_dim),
884
+ "integrator": BaseAgent(AgentRole.INTEGRATOR, hidden_dim=config.micro_policy_dim),
885
+ })
886
+ self.leader = BaseAgent("leader", hidden_dim=config.macro_policy_dim)
887
+ self.hierarchical = HierarchicalAgent(macro_dim=config.macro_policy_dim, micro_dim=config.micro_policy_dim)
888
+ self.routing_weights = nn.Parameter(torch.ones(len(self.agents)))
889
+ self.aggregation_gate = nn.Softmax(dim=0)
890
+ self.agent_tasks: Dict[str, BabyAGILoop] = {}
891
+ self.interactions: List[Dict] = []
892
+ self.task_count = 0
893
+
894
+ def forward(self, task: str, context: Dict[str, Any]) -> Dict[str, Any]:
895
+ task_embed = torch.randn(1, self.config.macro_policy_dim)
896
+ blueprint = self.hierarchical.generate_blueprint(task_embed)
897
+ routing_probs = self.aggregation_gate(self.routing_weights)
898
+ agent_outputs = {}
899
+ for i, (name, agent) in enumerate(self.agents.items()):
900
+ weight = routing_probs[i].item()
901
+ if weight < 0.10:
902
+ continue
903
+ sub_task = blueprint[min(i, len(blueprint) - 1)] if blueprint else task
904
+ output = agent.act(f"[{name}] {sub_task}")
905
+ agent_outputs[name] = {"output": output, "weight": weight, "sub_task": sub_task}
906
+ synthesis = self.leader.act(f"Synthesize: {task} with inputs: {list(agent_outputs.keys())}")
907
+ self.interactions.append({
908
+ "task": task, "blueprint": blueprint,
909
+ "agent_outputs": agent_outputs, "leader_synthesis": synthesis,
910
+ "routing_probs": routing_probs.detach().cpu().tolist(),
911
+ "t": time.time(),
912
+ })
913
+ self.task_count += 1
914
+ return {
915
+ "output": synthesis, "blueprint": blueprint,
916
+ "agent_outputs": agent_outputs,
917
+ "routing_weights": routing_probs.detach().cpu().tolist(),
918
+ }
919
+
920
+ def execute(self, task: str, kg_context: Any, context: Dict[str, Any]) -> Dict[str, Any]:
921
+ return self.forward(task, context)
922
+
923
+ def textual_backprop(self, global_gradient: str, performance_feedback: float, beta: float = 0.5) -> Dict[str, str]:
924
+ updates = {}
925
+ for name, agent in self.agents.items():
926
+ local_grad = f"{global_gradient} + {name} perf={performance_feedback:.3f}"
927
+ blended = local_grad
928
+ updates[name] = blended
929
+ self.routing_weights.data += performance_feedback * 0.01
930
+ return updates
931
+
932
+ def co_evolve_interactions(self) -> List[Dict]:
933
+ rewards = []
934
+ for interaction in self.interactions[-10:]:
935
+ n_agents = len(interaction.get("agent_outputs", {}))
936
+ complexity = len(interaction.get("blueprint", []))
937
+ reward = n_agents * 0.1 + min(complexity * 0.05, 0.5)
938
+ rewards.append({"reward": reward, "agents_involved": n_agents})
939
+ return rewards
940
+
941
+ def run_babyagi(self, objective: str, max_iterations: int = 20) -> Dict[str, Any]:
942
+ loop = BabyAGILoop(objective, max_iterations)
943
+ result = loop.run(self.agents["researcher"])
944
+ self.agent_tasks[objective] = loop
945
+ return result
946
+
947
+ def stats(self) -> Dict[str, Any]:
948
+ return {
949
+ "total_tasks": self.task_count,
950
+ "num_agents": len(self.agents),
951
+ "total_interactions": len(self.interactions),
952
+ "routing_weights": self.routing_weights.detach().cpu().tolist(),
953
+ }
954
+
955
+
956
+ # ============================================================================
957
+ # 5. EVOLUTION ENGINE (MAP-Elites + Quality-Diversity + Auto-Oversight)
958
+ # ============================================================================
959
+
960
+ class MAPelitesArchive:
961
+ def __init__(self, dims=(10, 10), ranges=None):
962
+ self.dims = dims
963
+ self.ranges = ranges or [(0, 1), (0, 1)]
964
+ self.archive: Dict[Tuple[int, int], Tuple[AetherConfig, float]] = {}
965
+
966
+ def _index(self, measures: np.ndarray) -> Tuple[int, int]:
967
+ indices = []
968
+ for m, (lo, hi), dim in zip(measures, self.ranges, self.dims):
969
+ norm = (m - lo) / (hi - lo + 1e-8)
970
+ idx = int(np.clip(norm * dim, 0, dim - 1))
971
+ indices.append(idx)
972
+ return tuple(indices)
973
+
974
+ def add(self, config: AetherConfig, fitness: float, measures: np.ndarray) -> bool:
975
+ idx = self._index(measures)
976
+ if idx not in self.archive or self.archive[idx][1] < fitness:
977
+ self.archive[idx] = (config, fitness)
978
+ return True
979
+ return False
980
+
981
+ def sample(self, n: int = 1) -> List[AetherConfig]:
982
+ if not self.archive:
983
+ return []
984
+ items = list(self.archive.values())
985
+ selected = random.sample(items, min(n, len(items)))
986
+ return [cfg for cfg, _ in selected]
987
+
988
+ def get_best(self) -> Optional[Tuple[AetherConfig, float]]:
989
+ if not self.archive:
990
+ return None
991
+ return max(self.archive.values(), key=lambda x: x[1])
992
+
993
+ def stats(self) -> Dict[str, float]:
994
+ total_cells = self.dims[0] * self.dims[1]
995
+ return {
996
+ "coverage": len(self.archive) / total_cells,
997
+ "qd_score": sum(f for _, f in self.archive.values()),
998
+ "max_fitness": max((f for _, f in self.archive.values()), default=0),
999
+ }
1000
+
1001
+
1002
+ class AetherEvolutionEngine:
1003
+ def __init__(self, config: AetherConfig):
1004
+ self.config = config
1005
+ self.archive = MAPelitesArchive(
1006
+ dims=config.archive_dims,
1007
+ ranges=[(0, 1), (0, 1)], # (symbolic_bias_proxy, fitness)
1008
+ )
1009
+ self.generation = 0
1010
+ self.experience_log: List[Dict] = []
1011
+
1012
+ def generate_candidates(self, base_config: AetherConfig, population_size: int = 8) -> List[AetherConfig]:
1013
+ candidates = [base_config]
1014
+ archive_seeds = self.archive.sample(n=min(2, len(self.archive.archive)))
1015
+ for _ in range(population_size - len(archive_seeds) - 1):
1016
+ candidates.append(self._mutate(base_config))
1017
+ for cfg in archive_seeds:
1018
+ candidates.append(cfg)
1019
+ return candidates
1020
+
1021
+ def _mutate(self, config: AetherConfig) -> AetherConfig:
1022
+ vec = config.to_vector()
1023
+ noise = np.random.normal(0, config.mutation_rate, size=vec.shape)
1024
+ mutated = vec + noise * vec
1025
+ new_cfg = AetherConfig.from_vector(mutated)
1026
+ # Preserve meta fields
1027
+ new_cfg.generations = config.generations
1028
+ new_cfg.enable_self_modification = config.enable_self_modification
1029
+ new_cfg.enable_parallel_agents = config.enable_parallel_agents
1030
+ new_cfg.archive_dims = config.archive_dims
1031
+ return new_cfg
1032
+
1033
+ def select(self, candidates: List[AetherConfig], fitness_scores: List[float],
1034
+ alpha_exploration: float = 0.3) -> List[AetherConfig]:
1035
+ if not candidates or not fitness_scores:
1036
+ return candidates[:2] if len(candidates) >= 2 else candidates
1037
+ vectors = np.array([c.to_vector() for c in candidates])
1038
+ f = np.array(fitness_scores)
1039
+ f_norm = (f - f.min()) / (f.max() - f.min() + 1e-8)
1040
+ k = min(4, len(candidates) - 1)
1041
+ novelties = []
1042
+ for i, v in enumerate(vectors):
1043
+ dists = np.linalg.norm(vectors - v, axis=1)
1044
+ dists[i] = np.inf
1045
+ knn = np.partition(dists, k)[:k]
1046
+ novelties.append(np.mean(knn))
1047
+ nov_norm = np.array(novelties) / (max(novelties) + 1e-8)
1048
+ scores = f_norm * np.sqrt(nov_norm + 1e-8)
1049
+ n_select = max(1, len(candidates) // 2)
1050
+ top_indices = np.argsort(scores)[-n_select:]
1051
+ return [candidates[i] for i in top_indices]
1052
+
1053
+ def mutate(self, candidates: List[AetherConfig], mutation_rate: float = 0.15) -> List[AetherConfig]:
1054
+ mutated = []
1055
+ for cfg in candidates:
1056
+ new_cfg = self._mutate(cfg)
1057
+ # Hard constraints
1058
+ if new_cfg.macro_policy_dim > 512:
1059
+ new_cfg.macro_policy_dim = 512
1060
+ if new_cfg.micro_policy_dim > new_cfg.macro_policy_dim:
1061
+ new_cfg.micro_policy_dim = new_cfg.macro_policy_dim // 2
1062
+ mutated.append(new_cfg)
1063
+ return mutated
1064
+
1065
+ def update_archive(self, candidates: List[AetherConfig], fitness_scores: List[float]):
1066
+ for cfg, fitness in zip(candidates, fitness_scores):
1067
+ if fitness == -float("inf"):
1068
+ continue
1069
+ # Behavioral descriptor: symbolic bias proxy = num_agents / max_agents
1070
+ sym_proxy = cfg.num_agents / cfg.max_agents
1071
+ measures = np.array([sym_proxy, np.clip(fitness, 0, 1)])
1072
+ improved = self.archive.add(cfg, fitness, measures)
1073
+ if improved:
1074
+ logger.debug(f"Archive improved at cell fitness={fitness:.4f}")
1075
+
1076
+ def get_diversity_stats(self) -> Dict[str, float]:
1077
+ return self.archive.stats()
1078
+
1079
+
1080
+ # ============================================================================
1081
+ # 6. AETHER CORE (Orchestrator + Evolution Loop + Auto-Oversight)
1082
+ # ============================================================================
1083
+
1084
+ class AetherCore(nn.Module):
1085
+ def __init__(self, config: Optional[AetherConfig] = None):
1086
+ super().__init__()
1087
+ self.config = config or AetherConfig()
1088
+ self.generation = 0
1089
+ self.architecture_history: List[Dict] = []
1090
+ self.fitness_log: List[float] = []
1091
+ self.metadata = {"birth": time.time(), "version": "0.2.0-autonomous"}
1092
+
1093
+ # Subsystems (lazily initialized where possible)
1094
+ self._memory: Optional[CoALAMemory] = None
1095
+ self._temporal: Optional[TemporalMemory] = None
1096
+ self._evolution: Optional[AetherEvolutionEngine] = None
1097
+ self._agents: Optional[AetherAgentOrchestrator] = None
1098
+ self._knowledge: Optional[KnowledgeGraphEngine] = None
1099
+ self._oversight: Optional[AutoOversight] = None
1100
+
1101
+ # Neuro-symbolic fusion gate (trainable)
1102
+ self.symbolic_gate = nn.Parameter(torch.tensor(0.0))
1103
+ self.neural_gate = nn.Parameter(torch.tensor(0.0))
1104
+
1105
+ logger.info("AETHER Core v0.2.0-autonomous initialized")
1106
+
1107
+ @property
1108
+ def memory(self) -> CoALAMemory:
1109
+ if self._memory is None:
1110
+ self._memory = CoALAMemory(capacity=self.config.working_memory_capacity)
1111
+ return self._memory
1112
+
1113
+ @property
1114
+ def temporal(self) -> TemporalMemory:
1115
+ if self._temporal is None:
1116
+ self._temporal = TemporalMemory(buffer_size=self.config.episodic_buffer_size)
1117
+ return self._temporal
1118
+
1119
+ @property
1120
+ def evolution(self) -> AetherEvolutionEngine:
1121
+ if self._evolution is None:
1122
+ self._evolution = AetherEvolutionEngine(self.config)
1123
+ return self._evolution
1124
+
1125
+ @property
1126
+ def agents(self) -> AetherAgentOrchestrator:
1127
+ if self._agents is None:
1128
+ self._agents = AetherAgentOrchestrator(self.config)
1129
+ return self._agents
1130
+
1131
+ @property
1132
+ def knowledge(self) -> KnowledgeGraphEngine:
1133
+ if self._knowledge is None:
1134
+ self._knowledge = KnowledgeGraphEngine(
1135
+ embedding_dim=self.config.kg_embedding_dim,
1136
+ num_relations=self.config.kg_num_relations,
1137
+ )
1138
+ return self._knowledge
1139
+
1140
+ @property
1141
+ def oversight(self) -> AutoOversight:
1142
+ if self._oversight is None:
1143
+ self._oversight = AutoOversight(self.config)
1144
+ return self._oversight
1145
+
1146
+ def forward(self, task: str, context: Optional[Dict] = None) -> Dict[str, Any]:
1147
+ context = context or {}
1148
+ kg_context = self.knowledge.query(task, top_k=5)
1149
+ self.memory.store({"task": task, "kg_context": kg_context, "t": time.time()})
1150
+ result = self.agents.execute(task, kg_context, context)
1151
+ # Neuro-symbolic fusion
1152
+ sym_w = torch.sigmoid(self.symbolic_gate)
1153
+ neu_w = torch.sigmoid(self.neural_gate)
1154
+ total = sym_w + neu_w + 1e-8
1155
+ sym_w, neu_w = sym_w / total, neu_w / total
1156
+ self.temporal.store({
1157
+ "task": task, "result": result,
1158
+ "weights": {"symbolic": sym_w.item(), "neural": neu_w.item()},
1159
+ })
1160
+ return {
1161
+ "output": result, "symbolic_weight": sym_w.item(),
1162
+ "neural_weight": neu_w.item(), "kg_context": kg_context,
1163
+ "generation": self.generation,
1164
+ }
1165
+
1166
+ def _default_evaluator(self, candidate: AetherConfig) -> float:
1167
+ """
1168
+ Fully automated fitness function — no external API.
1169
+ Scores: synthetic reasoning benchmarks + memory stress + knowledge graph coverage.
1170
+ """
1171
+ scores = []
1172
+ try:
1173
+ # 1. Agent orchestration efficiency
1174
+ orch = AetherAgentOrchestrator(candidate)
1175
+ task_embed = torch.randn(1, candidate.macro_policy_dim)
1176
+ blueprint = orch.hierarchical.generate_blueprint(task_embed)
1177
+ scores.append(min(1.0, len(blueprint) / 4.0))
1178
+
1179
+ # 2. Knowledge graph reasoning coverage
1180
+ kg = KnowledgeGraphEngine(embedding_dim=candidate.kg_embedding_dim, num_relations=candidate.kg_num_relations)
1181
+ for i in range(15):
1182
+ kg.add_fact(f"Entity{i}", "connects_to", f"Entity{i+1}")
1183
+ q = kg.query("Entity0 connects_to", top_k=5)
1184
+ scores.append(min(1.0, len(q["results"]) / 3.0))
1185
+
1186
+ # 3. Memory throughput
1187
+ mem = WorkingMemory(capacity=candidate.working_memory_capacity)
1188
+ for i in range(50):
1189
+ mem.store({"idx": i, "data": list(range(10))})
1190
+ retrieved = mem.retrieve("idx", top_k=5)
1191
+ scores.append(min(1.0, len(retrieved) / 5.0))
1192
+
1193
+ # 4. Config balance penalty (prefer moderate values)
1194
+ balance = 1.0 - abs(candidate.macro_policy_dim - 256) / 256.0
1195
+ scores.append(max(0.0, balance))
1196
+
1197
+ except Exception as e:
1198
+ logger.warning(f"Fitness evaluation failed: {e}")
1199
+ return -float("inf")
1200
+
1201
+ return float(np.mean(scores))
1202
+
1203
+ def evolve(self, num_generations: Optional[int] = None,
1204
+ evaluator: Optional[Callable[[AetherConfig], float]] = None) -> Dict[str, Any]:
1205
+ num_generations = num_generations or self.config.generations
1206
+ evaluator = evaluator or self._default_evaluator
1207
+ logger.info(f"=== AUTONOMOUS EVOLUTION: {num_generations} generations ===")
1208
+
1209
+ best_fitness = -float("inf")
1210
+ best_config: Optional[AetherConfig] = None
1211
+
1212
+ for gen in range(num_generations):
1213
+ self.generation = gen
1214
+ logger.info(f"\n--- Generation {gen} ---")
1215
+
1216
+ # 1. Generate candidates
1217
+ candidates = self.evolution.generate_candidates(self.config, self.config.population_size)
1218
+ logger.info(f"Generated {len(candidates)} candidates")
1219
+
1220
+ # 2. Evaluate + Auto-oversight gate
1221
+ fitness_scores = []
1222
+ approved_candidates = []
1223
+ for candidate in candidates:
1224
+ # Automated decision — no human
1225
+ approved, score, reason = self.oversight.decide(candidate, self)
1226
+ if approved:
1227
+ # Full fitness evaluation
1228
+ fitness = evaluator(candidate)
1229
+ fitness_scores.append(fitness)
1230
+ approved_candidates.append(candidate)
1231
+ logger.info(f" Candidate approved | reason={reason} | fitness={fitness:.4f}")
1232
+ else:
1233
+ fitness_scores.append(-float("inf"))
1234
+ logger.info(f" Candidate REJECTED | reason={reason}")
1235
+
1236
+ # 3. Auto-rollback check
1237
+ current_best = max((f for f in fitness_scores if f > -float("inf")), default=-float("inf"))
1238
+ if self.oversight.should_rollback(current_best):
1239
+ logger.warning(f"ROLLBACK TRIGGERED: fitness dropped to {current_best:.4f}")
1240
+ if self.oversight.last_good_config is not None:
1241
+ self.config = copy.deepcopy(self.oversight.last_good_config)
1242
+ logger.info("Rolled back to last known good configuration")
1243
+ continue
1244
+
1245
+ # 4. Select (Performance-Novelty)
1246
+ selected = self.evolution.select(candidates, fitness_scores)
1247
+
1248
+ # 5. Mutate
1249
+ mutated = self.evolution.mutate(selected)
1250
+
1251
+ # 6. Validate via oversight (second pass for mutated)
1252
+ validated = []
1253
+ validated_scores = []
1254
+ for m in mutated:
1255
+ ok, _, reason = self.oversight.decide(m, self)
1256
+ if ok:
1257
+ validated.append(m)
1258
+ validated_scores.append(evaluator(m))
1259
+ else:
1260
+ logger.info(f" Mutated candidate rejected: {reason}")
1261
+
1262
+ # 7. Integrate best
1263
+ if validated and validated_scores:
1264
+ best_idx = int(np.argmax(validated_scores))
1265
+ best_mutated = validated[best_idx]
1266
+ current_fitness = validated_scores[best_idx]
1267
+
1268
+ if current_fitness > best_fitness:
1269
+ best_fitness = current_fitness
1270
+ best_config = best_mutated
1271
+ self.config = best_mutated
1272
+ self.oversight.update_good_checkpoint(best_mutated, best_fitness)
1273
+ arch_hash = hashlib.sha256(
1274
+ json.dumps(asdict(best_mutated), sort_keys=True).encode()
1275
+ ).hexdigest()[:16]
1276
+ self.architecture_history.append({
1277
+ "generation": gen, "hash": arch_hash,
1278
+ "fitness": best_fitness, "config": asdict(best_mutated),
1279
+ })
1280
+ logger.info(f"*** NEW BEST: gen={gen} fitness={best_fitness:.4f} hash={arch_hash} ***")
1281
+
1282
+ # 8. Update MAP-Elites archive
1283
+ self.evolution.update_archive(candidates, fitness_scores)
1284
+ self.fitness_log.append(best_fitness)
1285
+
1286
+ # 9. Self-reflection per generation
1287
+ reflection = self.self_reflect()
1288
+ logger.info(f"Reflection: {reflection['recommendations']}")
1289
+
1290
+ return {
1291
+ "best_fitness": best_fitness,
1292
+ "best_config": asdict(best_config) if best_config else None,
1293
+ "generations": num_generations,
1294
+ "history": self.architecture_history,
1295
+ "oversight_summary": self.oversight.summary(),
1296
+ "archive_stats": self.evolution.get_diversity_stats(),
1297
+ }
1298
+
1299
+ def self_reflect(self) -> Dict[str, Any]:
1300
+ recs = []
1301
+ if len(self.fitness_log) > 5:
1302
+ recent = self.fitness_log[-5:]
1303
+ if max(recent) - min(recent) < 0.01:
1304
+ recs.append("Fitness plateau detected. Increase diversity or mutation rate.")
1305
+ if recent[-1] < recent[0]:
1306
+ recs.append("Declining trend. Rollback or expand search.")
1307
+
1308
+ sym = torch.sigmoid(self.symbolic_gate).item()
1309
+ if sym < 0.3:
1310
+ recs.append("Symbolic reasoning underutilized. Boost KG integration.")
1311
+ elif sym > 0.7:
1312
+ recs.append("Symbolic dominance. Increase neural flexibility.")
1313
+
1314
+ return {
1315
+ "generation": self.generation,
1316
+ "architectures_tested": len(self.architecture_history),
1317
+ "fitness_trend": self.fitness_log,
1318
+ "neuro_symbolic_balance": {"symbolic": sym, "neural": 1.0 - sym},
1319
+ "recommendations": recs,
1320
+ "oversight": self.oversight.summary(),
1321
+ }
1322
+
1323
+ def export_state(self) -> Dict[str, Any]:
1324
+ return {
1325
+ "config": asdict(self.config),
1326
+ "generation": self.generation,
1327
+ "architecture_history": self.architecture_history,
1328
+ "fitness_log": self.fitness_log,
1329
+ "metadata": self.metadata,
1330
+ "knowledge": self.knowledge.export(),
1331
+ "memory": self.memory.export(),
1332
+ "model_state_dict": {k: v.cpu().tolist() for k, v in self.state_dict().items()},
1333
+ }
1334
+
1335
+ @classmethod
1336
+ def from_state(cls, state: Dict[str, Any]) -> "AetherCore":
1337
+ cfg = AetherConfig(**state["config"])
1338
+ core = cls(config=cfg)
1339
+ core.generation = state["generation"]
1340
+ core.architecture_history = state["architecture_history"]
1341
+ core.fitness_log = state["fitness_log"]
1342
+ core.metadata = state["metadata"]
1343
+ return core
1344
+
1345
+
1346
+ # ============================================================================
1347
+ # 7. RUNNABLE MAIN
1348
+ # ============================================================================
1349
+
1350
+ def run_autonomous_demo():
1351
+ print("=" * 70)
1352
+ print(" AETHER v0.2.0 — AUTONOMOUS SELF-EVOLVING ARCHITECTURE")
1353
+ print(" Zero human oversight. Automated regression gating + rollback.")
1354
+ print("=" * 70)
1355
+
1356
+ config = AetherConfig(
1357
+ population_size=6,
1358
+ generations=5,
1359
+ mutation_rate=0.12,
1360
+ macro_policy_dim=128,
1361
+ micro_policy_dim=64,
1362
+ num_agents=4,
1363
+ working_memory_capacity=16,
1364
+ episodic_buffer_size=500,
1365
+ kg_embedding_dim=64,
1366
+ kg_num_relations=10,
1367
+ )
1368
+
1369
+ core = AetherCore(config)
1370
+
1371
+ # Seed knowledge graph
1372
+ print("\n[1] Seeding Knowledge Graph...")
1373
+ kg = core.knowledge
1374
+ facts = [
1375
+ ("Intelligence", "requires", "Reasoning"),
1376
+ ("Reasoning", "requires", "Memory"),
1377
+ ("Memory", "enables", "Learning"),
1378
+ ("Learning", "produces", "Intelligence"),
1379
+ ("Agent", "has_role", "Researcher"),
1380
+ ("Agent", "has_role", "Engineer"),
1381
+ ("Agent", "has_role", "Analyzer"),
1382
+ ("Agent", "has_role", "Integrator"),
1383
+ ]
1384
+ for h, r, t in facts:
1385
+ kg.add_fact(h, r, t)
1386
+ print(f" KG: {kg.stats()}")
1387
+
1388
+ # Single forward pass demo
1389
+ print("\n[2] Forward Pass Demo (neuro-symbolic query)...")
1390
+ result = core.forward("Intelligence requires")
1391
+ print(f" Symbolic weight: {result['symbolic_weight']:.3f}")
1392
+ print(f" Neural weight: {result['neural_weight']:.3f}")
1393
+ print(f" Results: {len(result['kg_context']['results'])} items")
1394
+ for r in result["kg_context"]["results"]:
1395
+ print(f" → {r['head']} --{r['relation']}--> {r['tail']} (conf={r.get('confidence',0):.2f}, src={r.get('source','?')})")
1396
+
1397
+ # Agent orchestration demo
1398
+ print("\n[3] Agent Orchestration Demo...")
1399
+ agent_result = core.agents.execute("Optimize reasoning pipeline", {}, {})
1400
+ print(f" Leader synthesis: {agent_result['output'][:80]}...")
1401
+ print(f" Agents activated: {list(agent_result['agent_outputs'].keys())}")
1402
+ print(f" Routing weights: {[f'{w:.3f}' for w in agent_result['routing_weights']]}")
1403
+
1404
+ # Evolution loop (fully automated)
1405
+ print("\n[4] AUTONOMOUS EVOLUTION LOOP (no human oversight)...")
1406
+ evolution_result = core.evolve(num_generations=5)
1407
+
1408
+ print("\n[5] EVOLUTION RESULTS")
1409
+ print(f" Best fitness achieved: {evolution_result['best_fitness']:.4f}")
1410
+ print(f" Generations run: {evolution_result['generations']}")
1411
+ print(f" Architecture changes: {len(evolution_result['history'])}")
1412
+ print(f" MAP-Elites coverage: {evolution_result['archive_stats']['coverage']:.2%}")
1413
+ print(f" MAP-Elites QD score: {evolution_result['archive_stats']['qd_score']:.2f}")
1414
+ print(f" Auto-oversight approved: {evolution_result['oversight_summary']['approved']}")
1415
+ print(f" Auto-oversight rejected: {evolution_result['oversight_summary']['rejected']}")
1416
+ print(f" Consecutive rejections: {evolution_result['oversight_summary']['consecutive_rejections']}")
1417
+
1418
+ print("\n[6] Architecture Evolution Trajectory")
1419
+ for entry in evolution_result["history"]:
1420
+ print(f" Gen {entry['generation']:02d} | hash={entry['hash']} | fitness={entry['fitness']:.4f} | "
1421
+ f"agents={entry['config']['num_agents']} | macro={entry['config']['macro_policy_dim']} | "
1422
+ f"mut_rate={entry['config']['mutation_rate']:.3f}")
1423
+
1424
+ # Self-reflection
1425
+ print("\n[7] Self-Reflection")
1426
+ reflection = core.self_reflect()
1427
+ for rec in reflection["recommendations"]:
1428
+ print(f" → {rec}")
1429
+
1430
+ # Export checkpoint
1431
+ print("\n[8] Exporting state checkpoint...")
1432
+ state = core.export_state()
1433
+ checkpoint_path = "/app/aether_checkpoint.json"
1434
+ with open(checkpoint_path, "w") as f:
1435
+ json.dump(state, f, indent=2, default=str)
1436
+ print(f" Checkpoint saved to: {checkpoint_path}")
1437
+
1438
+ print("\n" + "=" * 70)
1439
+ print(" DEMO COMPLETE. AETHER is fully autonomous.")
1440
+ print("=" * 70)
1441
+ return core, evolution_result
1442
+
1443
+
1444
+ if __name__ == "__main__":
1445
+ run_autonomous_demo()