-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathaura_main.py
More file actions
4523 lines (4019 loc) · 186 KB
/
Copy pathaura_main.py
File metadata and controls
4523 lines (4019 loc) · 186 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""
Aura Main Entry Point
---------------------
Standardized, single-entry launcher for CLI, Server, Desktop, and Watchdog modes.
Replaces: aura_launcher.py, aura_desktop.py, run_aura.py, run_aura_loop.py, and reboot.py.
"""
import argparse
import asyncio
import contextlib
import json
import logging
import multiprocessing
import os
import shutil
import signal
import subprocess
import sys
import tempfile
import threading
import time
from pathlib import Path
from typing import Any, TextIO
if sys.version_info < (3, 12): # noqa: UP036 - boot contract asserts a clear runtime guard.
raise SystemExit("Aura requires Python 3.12+")
import httpx
from core.governance_context import local_internal_governed_scope
from core.runtime.errors import record_degradation
from core.runtime.resource_observation import get_resource_observer
from core.runtime.root_signal_owner import RootShutdownSignalOwner
from core.runtime.shutdown_coordinator import is_shutdown_requested, request_shutdown
from core.runtime.subprocess_gateway import get_subprocess_gateway
from core.utils.singleton import (
acquire_instance_lock,
instance_lock_metadata_path,
parse_instance_lock_pid,
read_instance_lock_metadata,
read_instance_lock_pid,
release_instance_lock,
)
from core.utils.task_tracker import get_task_tracker
# QUAL-07: Define logger early so venv injection logging works.
logger = logging.getLogger("Aura.Main")
_RUNTIME_LOCK_CLAIMED = False
_AURA_MAIN_DEGRADATION_KEY = "aura_main"
_FAULT_FORENSICS_HANDLE: TextIO | None = None
_MANIFEST_UNREADY_LOG_STATE: dict[str, tuple[float, tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]]]] = {}
_MANIFEST_UNREADY_LOG_INTERVAL_S = 60.0
_MANIFEST_HEALTH_CACHE_MAX_AGE_S = 15.0
_AURA_MAIN_BOUNDARY_ERRORS = (
AttributeError,
ImportError,
LookupError,
OSError,
RuntimeError,
TimeoutError,
TypeError,
ValueError,
asyncio.InvalidStateError,
subprocess.SubprocessError,
httpx.HTTPError,
)
# Install global task supervision before subsystems spawn background tasks.
try:
import core.utils.asyncio_patch # noqa: F401
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation(_AURA_MAIN_DEGRADATION_KEY, exc)
# Phase 31: Native Apple Silicon Resilience Fixes
# 0. Force 'spawn' on macOS to prevent Cocoa/XPC deadlocks in child actors
if sys.platform == "darwin":
os.environ["OPENCV_VIDEOIO_AVFOUNDATION_USE_FRAME_RECEIVER"] = "0"
os.environ["PYAV_SKIP_AVF_FRAME_RECEIVER"] = "1"
try:
from core.media.safe_imports import install_main_process_cv2_guard
install_main_process_cv2_guard()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation("aura_main", exc)
with contextlib.suppress(RuntimeError):
multiprocessing.set_start_method("spawn", force=True)
# PyAV and OpenCV both bundle libavdevice and register the AVFoundation
# Objective-C classes AVFFrameReceiver / AVFAudioReceiver. Do not eager-load
# OpenCV in Aura's primary brain process: camera work belongs in the sensory
# sidecar or a deferred provider so STT/PyAV and cv2 cannot destabilize the
# live desktop runtime.
native_media_preload = os.environ.get("AURA_PRELOAD_NATIVE_MEDIA", "").strip().lower() in {
"1",
"true",
"yes",
"on",
}
safe_desktop_context = any(
os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
for name in ("AURA_SAFE_BOOT_DESKTOP", "AURA_LAUNCHED_FROM_APP", "AURA_HEADLESS")
) or any(arg in sys.argv for arg in ("--headless", "--desktop", "--gui-window"))
if native_media_preload and not safe_desktop_context:
try:
_devnull_fd = os.open(os.devnull, os.O_WRONLY)
_saved_stderr = os.dup(2)
try:
os.dup2(_devnull_fd, 2)
import av as _av # noqa: F401 (ordering matters — av first)
finally:
os.dup2(_saved_stderr, 2)
os.close(_devnull_fd)
os.close(_saved_stderr)
except ImportError as exc:
logger.debug("Optional AV/OpenCV preload skipped: %s", exc)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
# Never let the dylib suppression block boot.
record_degradation("aura_main", exc)
else:
logger.debug(
"Optional AV/OpenCV preload skipped: disabled for stable desktop boot."
)
# Early .env loading — ensures AURA_LOCAL_BACKEND and other env vars are
# available BEFORE module-level code in model_registry.py reads os.getenv().
# Without this, pydantic's env_file loading happens too late.
with contextlib.suppress(ImportError):
from dotenv import load_dotenv as _load_dotenv
_env_path = Path(__file__).resolve().parent / ".env"
if _env_path.exists():
_load_dotenv(_env_path, override=False)
# 1. Path Resolution & Environment Locking (Radical Fix)
PROJECT_ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(PROJECT_ROOT))
def _env_flag(name: str, default: bool = False) -> bool:
raw = os.environ.get(name)
if raw is None:
return default
return raw.strip().lower() in {"1", "true", "yes", "on", "enabled"}
def _foreground_only_runtime() -> bool:
try:
from core.runtime.background_policy import foreground_only_runtime
return bool(foreground_only_runtime())
except (ImportError, AttributeError, RuntimeError, ValueError):
return _env_flag("AURA_FOREGROUND_ONLY", False)
def _bounded_memory_ceiling_mb(
total_mb: float,
requested_mb: Any | None = None,
*,
absolute_ceiling_mb: float = 46080.0,
ceiling_fraction: float = 0.70,
floor_mb: float = 8192.0,
) -> float:
"""Return a host-safe memory kill ceiling.
Environment overrides are useful for lab runs, but a stale or excessive
value must not let the live desktop process grow until macOS kills the
whole machine. Unsafe overrides require an explicit opt-in flag.
"""
try:
total = max(float(total_mb), floor_mb)
except (TypeError, ValueError, OverflowError):
total = 65536.0
safe_ceiling = min(float(absolute_ceiling_mb), max(float(floor_mb), total * float(ceiling_fraction)))
if requested_mb is None:
return safe_ceiling
try:
requested = max(float(floor_mb), float(requested_mb))
except (TypeError, ValueError, OverflowError):
return safe_ceiling
if _env_flag("AURA_ALLOW_UNSAFE_MEMORY_LIMITS", False):
return requested
return min(requested, safe_ceiling)
def _should_start_keep_awake_controller() -> bool:
"""Start macOS keep-awake only from the root Aura process.
Multiprocessing spawn imports this module inside child actors as
``__mp_main__``. Starting keep-awake at import time from those children
leaks orphan ``caffeinate`` helpers and can keep actor processes alive after
shutdown, so the controller is root-process only.
"""
helper_modes = {
"-h",
"--help",
"--stop",
"--gui-window",
"--watchdog",
"--cli",
"--philosophy",
}
if any(arg in helper_modes for arg in sys.argv[1:]):
return False
try:
process_name = multiprocessing.current_process().name
except _AURA_MAIN_BOUNDARY_ERRORS:
process_name = "unknown"
return process_name == "MainProcess" and __name__ != "__mp_main__"
def _start_root_keep_awake_controller() -> None:
"""Start keep-awake only after the root singleton lock is acquired."""
if not _should_start_keep_awake_controller() or is_shutdown_requested():
return
try:
from core.runtime.keep_awake import start_from_environment
status = start_from_environment()
if status.active:
logger.info("Aura keep-awake assertion active: pid=%s", status.pid)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation("aura_main", exc)
logger.warning("Aura keep-awake setup failed: %s", exc)
def _should_force_root_process_exit_after_main(args: Any) -> bool:
"""Return true for long-lived root runtimes after their shutdown completes."""
if os.environ.get("PYTEST_CURRENT_TEST"):
return False
if _env_flag("AURA_DISABLE_HARD_EXIT_AFTER_MAIN", False):
return False
if not _should_start_keep_awake_controller():
return False
return not any(
bool(getattr(args, name, False))
for name in ("cli", "watchdog", "gui_window", "philosophy")
)
def _run_multiprocessing_finalizers_before_hard_exit(timeout_s: float = 3.0) -> bool:
"""Give multiprocessing a bounded chance to unregister queues/semaphores."""
done = threading.Event()
def _run_finalizers() -> None:
try:
import multiprocessing.util as mp_util
mp_util._exit_function()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation(
"aura_main",
exc,
action="continued root hard-exit after multiprocessing finalizer failed",
)
logger.debug("Multiprocessing finalizer failed before root hard-exit: %s", exc)
finally:
done.set()
thread = threading.Thread(
target=_run_finalizers,
name="aura-multiprocessing-finalizers",
daemon=True,
)
thread.start()
thread.join(timeout=max(0.0, float(timeout_s)))
if not done.is_set():
record_degradation(
"aura_main",
TimeoutError("multiprocessing finalizer timeout before hard exit"),
action="continued root hard-exit after bounded multiprocessing cleanup timed out",
)
logger.warning(
"Multiprocessing finalizers did not finish within %.1fs before root hard-exit.",
timeout_s,
)
return False
return True
def _shutdown_logging_before_hard_exit(timeout_s: float = 2.0) -> bool:
"""Flush logging without letting a wedged handler defeat process shutdown."""
done = threading.Event()
def _shutdown_logging() -> None:
try:
logging.shutdown()
finally:
done.set()
thread = threading.Thread(
target=_shutdown_logging,
name="aura-logging-shutdown",
daemon=True,
)
thread.start()
thread.join(timeout=max(0.0, float(timeout_s)))
return done.is_set()
def _finalize_root_runtime_process_exit(
args: Any,
exit_code: int = 0,
*,
signal_owner: RootShutdownSignalOwner | None = None,
) -> None:
if not _should_force_root_process_exit_after_main(args):
return
try:
from core.runtime.keep_awake import get_keep_awake_controller
get_keep_awake_controller().stop()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation(
"aura_main",
exc,
action="continued root process exit after keep-awake stop failed",
)
logger.debug("Keep-awake final stop failed during root process exit: %s", exc)
logger.info("Root runtime finalization started (exit_code=%d).", exit_code)
try:
from core.runtime.lifecycle_probe import hold_shutdown_probe_sync
hold_shutdown_probe_sync("root_finalization")
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
logger.warning("Root lifecycle probe hold failed: %s", exc)
lock_released = False
try:
release_instance_lock()
lock_released = True
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation(
"aura_main",
exc,
action="continued root process exit after lock release failed",
)
logger.debug("Instance lock release failed during root process exit: %s", exc)
finalizers_completed = _run_multiprocessing_finalizers_before_hard_exit()
persistence_close_reports: dict[str, object] = {}
try:
from core.memory.db_config import close_all_connections
persistence_close_reports["db_config"] = close_all_connections()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
persistence_close_reports["db_config"] = {
"clean": False,
"error": f"{type(exc).__name__}: {exc}",
}
try:
from core.security.governance_vault import close_governance_vault
persistence_close_reports["governance_vault"] = close_governance_vault()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
persistence_close_reports["governance_vault"] = {
"clean": False,
"error": f"{type(exc).__name__}: {exc}",
}
try:
from core.planning.mission_state import close_mission_state
persistence_close_reports["mission_state"] = close_mission_state()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
persistence_close_reports["mission_state"] = {
"clean": False,
"error": f"{type(exc).__name__}: {exc}",
}
try:
from core.runtime.receipts import close_receipt_store
persistence_close_reports["receipt_store"] = close_receipt_store()
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
persistence_close_reports["receipt_store"] = {
"clean": False,
"error": f"{type(exc).__name__}: {exc}",
}
try:
from core.runtime.runtime_hygiene import get_runtime_hygiene
runtime_hygiene = get_runtime_hygiene()
socket_cleanup_report = runtime_hygiene.close_root_exit_sockets()
root_resource_report = runtime_hygiene.get_root_exit_resource_report()
root_resource_report["socket_cleanup"] = socket_cleanup_report
if socket_cleanup_report.get("clean") is not True:
root_resource_report["clean"] = False
blockers = root_resource_report.setdefault("blockers", [])
if isinstance(blockers, list):
blockers.append("root_socket_cleanup_failed")
root_resource_report["persistence_close_reports"] = persistence_close_reports
if any(
isinstance(report, dict) and report.get("clean") is not True
for report in persistence_close_reports.values()
):
root_resource_report["clean"] = False
blockers = root_resource_report.setdefault("blockers", [])
if isinstance(blockers, list):
blockers.append("persistence_close_failed")
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
root_resource_report = {
"clean": False,
"blockers": ["root_resource_report_unavailable"],
"error": f"{type(exc).__name__}: {exc}",
}
logging_shutdown_completed = _shutdown_logging_before_hard_exit()
if not logging_shutdown_completed:
print(
"Logging shutdown exceeded its 2.0s finalization budget; "
"continuing root exit.",
file=sys.stderr,
flush=True,
)
try:
from core.runtime.shutdown_coordinator import publish_root_exit_verdict
publish_root_exit_verdict(
lock_released=lock_released,
finalizers_completed=finalizers_completed,
logging_shutdown_completed=logging_shutdown_completed,
root_resource_report=root_resource_report,
exit_code=exit_code,
)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
print(
f"Root process exit receipt persistence failed: {type(exc).__name__}: {exc}",
file=sys.stderr,
flush=True,
)
print(
f"Root runtime shutdown complete; exiting process with code {exit_code}.",
flush=True,
)
if signal_owner is not None:
signal_owner.close()
try:
from core.runtime.flight_recorder import get_flight_recorder
reason = (
signal_owner.first_reason
if signal_owner is not None and signal_owner.first_reason
else "root_finalization"
)
if get_flight_recorder().mark_clean_shutdown(reason):
print("Flight ring marked clean before root process exit.", flush=True)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation(
"aura_main",
exc,
action="continued root process exit without clean flight-ring marker",
)
print(
f"Flight ring clean marker failed: {type(exc).__name__}: {exc}",
file=sys.stderr,
flush=True,
)
os._exit(int(exit_code))
def _profile_is_proof(profile: str | None, ready_label: str | None = None) -> bool:
"""Return True for canonical proof/evaluation boot profiles."""
tokens = {
str(profile or "").strip().lower(),
str(ready_label or "").strip().lower(),
}
return bool(tokens & {"proof", "eval", "evaluation", "validation", "benchmark"}) or any(
any(marker in token for marker in ("proof", "validation", "benchmark"))
for token in tokens
if token
)
def _activate_proof_runtime_policy(profile: str | None, ready_label: str | None = None) -> None:
"""Make proof-profile boots enforce the same runtime policy everywhere.
Proof runners use the normal Aura boot path, but they need stricter lane
contracts so background/autonomy work cannot silently spin up a lower local
model while a primary-lane proof is being measured.
"""
if not _profile_is_proof(profile, ready_label):
return
os.environ["AURA_PROOF_RUN"] = "1"
os.environ.setdefault("AURA_PROOF_MODEL_TIER", "primary")
# Proof/evaluation boots must still use the canonical Aura runtime, but
# unsolicited background autonomy cannot compete with sealed evaluator turns.
os.environ["AURA_ENABLE_PROACTIVE_SYSTEMS"] = "0"
os.environ["AURA_ENABLE_RESEARCH_CYCLE"] = "0"
os.environ["AURA_ENABLE_SENSORIMOTOR_GROUNDING"] = "0"
os.environ["AURA_ENABLE_PROACTIVE_VISION"] = "0"
def _record_main_degradation(exc: BaseException, message: str, *args: Any) -> None:
record_degradation(_AURA_MAIN_DEGRADATION_KEY, exc)
logger.warning(message, *args, exc)
def _env_float(name: str, default: float, *, minimum: float | None = None, maximum: float | None = None) -> float:
raw = os.environ.get(name, "").strip()
if not raw:
value = default
else:
try:
value = float(raw)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
_record_main_degradation(exc, "Invalid float environment value for %s=%r; using %.2f: %s", name, raw, default)
value = default
if minimum is not None:
value = max(minimum, value)
if maximum is not None:
value = min(maximum, value)
return value
# [STABILITY] Force the execution context to the absolute path of the current venv
# This prevents the "ModuleNotFoundError" when pip is in the venv but the script runs elsewhere.
VENV_PATH = PROJECT_ROOT / ".venv"
if not VENV_PATH.exists():
VENV_PATH = PROJECT_ROOT / ".venv_aura"
if VENV_PATH.exists():
# Scan for any python3.x directory to handle version mismatches (e.g. venv is 3.12, system is 3.14)
lib_dir = VENV_PATH / "lib"
if lib_dir.exists():
curr_ver = f"python{sys.version_info.major}.{sys.version_info.minor}"
for py_dir in lib_dir.glob("python3.*"):
if py_dir.name != curr_ver:
logger.debug("⏭️ Skipping venv injection for mismatched version: %s (Current: %s)", py_dir.name, curr_ver)
continue
site_packages = py_dir / "site-packages"
if site_packages.exists() and str(site_packages) not in sys.path:
sys.path.insert(0, str(site_packages))
import site
site.addsitedir(str(site_packages))
logger.info("📍 Injected venv site-packages: %s", site_packages)
# Desktop resource protection keeps the main Aura process off the in-process
# MLX/Metal path. The managed LLM runtimes use their own subprocesses.
try:
from core.runtime.desktop_boot_safety import configure_inprocess_mlx_runtime
_mlx_runtime = configure_inprocess_mlx_runtime()
if _mlx_runtime.get("device") == "cpu":
logger.info(
"🛡️ In-process MLX pinned to CPU (%s).",
_mlx_runtime.get("reason", "guard"),
)
elif _mlx_runtime.get("device") == "metal":
logger.info(
"⚡ In-process MLX Metal retained (%s).",
_mlx_runtime.get("reason", "enabled"),
)
except _AURA_MAIN_BOUNDARY_ERRORS as _mlx_guard_exc:
logger.debug("In-process MLX boot guard unavailable: %s", _mlx_guard_exc)
# Phase 31: Native Apple Silicon Resilience Fixes
# 1. Address AVFFrameReceiver conflict (cv2 vs av/PyAV) on macOS
# This prevents the "AVFFrameReceiver: ... is already established" crash
if sys.platform == "darwin":
os.environ["OPENCV_VIDEOIO_AVFOUNDATION_USE_FRAME_RECEIVER"] = "0"
os.environ["PYAV_SKIP_AVF_FRAME_RECEIVER"] = "1"
# Strip PyInstaller matplotlib bloat in frozen builds
if getattr(sys, 'frozen', False):
os.environ.pop("MPLBACKEND", None)
# 2. Bootstrap configuration. Persistent logging is initialized only by
# main(); importing this module for lifecycle helpers must not open files or
# start a QueueListener thread.
try:
from core.config import config
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation(_AURA_MAIN_DEGRADATION_KEY, exc)
config = None # Ensure NameError is avoided.
def _ensure_bootstrap_logging() -> None:
"""Own persistent log resources only from an actual launcher invocation."""
global logger
try:
if config is None:
raise RuntimeError("Aura configuration is unavailable")
from core.observability.logging_config import setup_logging
setup_logging(log_dir=config.paths.log_dir)
logger = logging.getLogger("Aura.Main")
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
import traceback
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger("Aura.Main")
record_degradation(_AURA_MAIN_DEGRADATION_KEY, exc)
logger.error("BOOTSTRAP FAILURE: Could not initialize Aura logging.")
logger.error(traceback.format_exc())
# Category 11: Reliability Hardening
_supervisor_tree: Any | None = None
def get_supervisor_tree() -> Any:
global _supervisor_tree
if _supervisor_tree is None:
from core.supervisor.tree import get_tree
_supervisor_tree = get_tree()
return _supervisor_tree
# ---------------------------------------------------------------------------
# Utility Functions
# ---------------------------------------------------------------------------
def validate_security_config():
"""Verify that we aren't exposing a public API without authentication."""
if config is None:
logger.warning("⚠️ Config unavailable — skipping security validation (bootstrap failure).")
return
internal_only = getattr(config.security, "internal_only_mode", False)
api_token = config.api_token
# If host is NOT localhost and no token is set, we are in a dangerous state
# Note: We check this even if the user passed --host 127.0.0.1 because
# the server.py might override it or be proxied.
if not internal_only and not api_token:
from core.exceptions import SecurityConfigError
logger.critical("🚨 SECURITY VIOLATION: Public API access enabled but AURA_API_TOKEN is unset.")
logger.critical(" To fix this: Set AURA_API_TOKEN in .env or run with AURA_INTERNAL_ONLY=1")
raise SecurityConfigError("Public API access enabled without AURA_API_TOKEN")
def check_environment():
"""Verify system readiness."""
logger.info("🔍 Verifying Environment Integrity...")
logger.info("📍 RUNTIME PATH Diagnostic:")
logger.info(" • __file__: %s", __file__)
logger.info(" • sys.executable: %s", sys.executable)
logger.info(" • sys.path: %s", sys.path)
try:
import core
logger.info(" • core.__file__: %s", core.__file__)
except _AURA_MAIN_BOUNDARY_ERRORS as e:
record_degradation('aura_main', e)
logger.error(" • core import failed: %s", e)
if config is None:
logger.error("❌ Environment check aborted: Configuration not loaded.")
raise RuntimeError("Configuration not loaded")
# Perplexity Audit Fix: Fail-closed security validation
validate_security_config()
# Validate autonomous repair registry before self-modification can resume.
registry_file = config.paths.data_dir / "selfmod" / "pending_patch_registry.jsonl"
if registry_file.exists():
logger.info("🛠️ Validating self-modification repair registry...")
try:
from core.self_modification.repair_registry import validate_repair_registry
validate_repair_registry(registry_file)
except (ImportError, json.JSONDecodeError, OSError, TypeError, ValueError) as exc:
record_degradation("aura_main", exc)
raise RuntimeError(f"Self-modification repair registry is not trustworthy: {registry_file}") from exc
# Ensure home directory exists
config.paths.create_directories()
def kill_port(port: int, pattern: str = "aura"):
"""Terminate Aura-owned processes on a port.
Port 10003 is Aura's private supervisor lane and may be force-cleared.
Shared development ports such as 8000 stay pattern-limited so unrelated
local servers are not silently killed.
"""
try:
import psutil
from core.runtime.resource_observation import get_resource_observer
except ImportError:
logger.warning("psutil missing - skipping advanced port cleanup.")
return
force_all_ports = {10003}
shared_ports = {8000}
force_all = port in force_all_ports
if port in shared_ports:
logger.info(
"Port %s is a shared development port; cleanup is limited to processes matching pattern '%s'.",
port,
pattern,
)
observer = get_resource_observer()
process_table = {process.pid: process for process in observer.processes()}
for connection in observer.connections(kind="inet"):
try:
if connection.local_port != port or connection.pid <= 0:
continue
observed = process_table.get(connection.pid)
pid = int(connection.pid)
name = observed.name if observed is not None else ""
cmd_str = (
" ".join(observed.cmdline).lower() if observed is not None else ""
)
should_kill = force_all or (pattern in cmd_str or pattern in name.lower())
if should_kill:
if force_all:
logger.warning(
"Force-clearing Aura-private port %s by terminating PID %s (%s): %s",
port,
pid,
name,
cmd_str[:200],
)
logger.info("Terminating process %s (%s) on port %s...", pid, name, port)
action_process = psutil.Process(pid)
try:
action_process.terminate()
action_process.wait(timeout=3)
except psutil.TimeoutExpired:
logger.warning("Process %s resistant to SIGTERM. Sending SIGKILL.", pid)
action_process.kill()
else:
logger.warning(
"Leaving non-Aura process %s (%s) on shared port %s untouched.",
pid,
name,
port,
)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, PermissionError, SystemError, OSError) as exc:
logger.debug("Skipping process during port cleanup: %s", exc)
def clean_artifacts():
"""Purge stale bytecode and temporary caches."""
logger.info("🧹 Purging runtime artifacts...")
for p in PROJECT_ROOT.rglob("__pycache__"):
try:
shutil.rmtree(p)
except OSError as exc:
logger.debug("Unable to remove cache directory %s: %s", p, exc)
for p in PROJECT_ROOT.rglob("*.pyc"):
try:
p.unlink()
except OSError as exc:
logger.debug("Unable to remove bytecode file %s: %s", p, exc)
def _select_preferred_launcher_python(current_executable: str | None = None) -> Path | None:
"""Prefer a stable Homebrew Python 3.12 launcher over shimmed venv paths."""
if sys.platform != "darwin":
return None
current_raw = Path(current_executable or sys.executable)
current_raw_str = str(current_raw)
if "/.venv/" not in current_raw_str and "/.venv_aura/" not in current_raw_str:
return None
candidates: list[Path] = []
explicit = os.environ.get("AURA_PREFERRED_PYTHON")
if explicit:
candidates.append(Path(explicit))
candidates.extend(
[
Path("/opt/homebrew/opt/python@3.12/bin/python3.12"),
Path("/opt/homebrew/bin/python3.12"),
]
)
for candidate in candidates:
try:
resolved = candidate.resolve()
current_resolved = current_raw.resolve()
except FileNotFoundError:
continue
if resolved.exists() and resolved != current_resolved:
return candidate
return None
def _launcher_python_executable() -> str:
preferred = os.environ.get("AURA_PREFERRED_PYTHON", "").strip()
if preferred and Path(preferred).exists():
return preferred
return sys.executable
REAPER_MANIFEST_ENV = "AURA_REAPER_MANIFEST"
LEGACY_REAPER_MANIFEST = Path(tempfile.gettempdir()) / "aura_reaper_manifest.json"
REAPER_MANIFEST_DIR = Path.home() / ".aura" / "run" / "reaper"
def _new_reaper_manifest_path() -> Path:
runtime_id = os.environ.get("AURA_RUNTIME_ID", "").strip()
if not runtime_id:
runtime_id = f"{int(time.time())}-{os.getpid()}"
os.environ["AURA_RUNTIME_ID"] = runtime_id
return REAPER_MANIFEST_DIR / f"manifest-{runtime_id}.json"
def _ensure_reaper_manifest_env() -> Path:
"""Force every launcher/process surface for this boot to share one manifest path."""
raw_path = os.environ.get(REAPER_MANIFEST_ENV, "").strip()
if raw_path and Path(raw_path).expanduser() != LEGACY_REAPER_MANIFEST:
manifest_path = Path(raw_path).expanduser()
else:
manifest_path = _new_reaper_manifest_path()
os.environ[REAPER_MANIFEST_ENV] = str(manifest_path)
manifest_path.parent.mkdir(parents=True, exist_ok=True)
return manifest_path
def _maybe_relaunch_with_preferred_python():
if os.environ.get("AURA_SKIP_PREFERRED_PYTHON_RELAUNCH") == "1":
return
preferred = _select_preferred_launcher_python()
if not preferred:
return
logger.warning("🔁 Relaunching Aura with preferred interpreter: %s", preferred)
env = os.environ.copy()
env["AURA_SKIP_PREFERRED_PYTHON_RELAUNCH"] = "1"
env["AURA_PREFERRED_PYTHON"] = str(preferred)
# Preserve the production invariant across interpreter relaunches: the live
# desktop Cortex is Aura's in-process MLX lane.
env["AURA_LOCAL_BACKEND"] = "mlx"
os.execve(str(preferred), [str(preferred), *sys.argv], env)
# ---------------------------------------------------------------------------
# Shims & Compatibility
# ---------------------------------------------------------------------------
try:
from core.cognition.cognitive_integration_layer import CognitiveIntegrationLayer
CognitiveIntegration = CognitiveIntegrationLayer # Legacy Alias shim
except ImportError:
logger.debug("CognitiveIntegrationLayer unavailable; legacy alias not installed.")
# ---------------------------------------------------------------------------
# Modes
# ---------------------------------------------------------------------------
async def bootstrap_aura(orchestrator: Any):
"""Initialize background services using the Resilient Boot sequence."""
from core.bus.actor_bus import create_actor_bus
from core.container import ServiceContainer
from core.ops.resilient_boot import ResilientBoot
# Register core services early to satisfy boot dependencies
supervisor = get_supervisor_tree()
ServiceContainer.register_instance("supervisor", supervisor)
actor_bus = create_actor_bus() # Main bus for orchestrator
ServiceContainer.register_instance("actor_bus", actor_bus, failure_policy="degrade_with_receipt")
actor_bus.start()
# Guarded stage-based ignition
# Explicitly link internal refs to ensure property lookups match initialized instances
orchestrator._actor_bus = actor_bus
orchestrator._supervisor_tree = supervisor
tracker = get_task_tracker()
tracker.install_loop_hygiene(asyncio.get_running_loop())
boot = ResilientBoot(orchestrator)
# [STABILITY] Wait for ignition to complete before proceeding
# This ensures all core services and state repository are ready.
status = await boot.ignite()
logger.info("🛡️ [BOOT] Resilient Ignition finished with status: %s", status)
# Final interface check
if hasattr(orchestrator, "kernel_interface") and orchestrator.kernel_interface:
for _ in range(5):
if orchestrator.kernel_interface.is_ready():
break
await asyncio.sleep(1.0)
# Register supervisor tree in container (Redundant but safe)
# ServiceContainer.register_instance("supervisor", supervisor)
# Post-boot background tasks
from core.utils.memory_monitor import AppleSiliconMemoryMonitor
mem_monitor = AppleSiliconMemoryMonitor()
try:
ServiceContainer.register_instance("memory_monitor", mem_monitor, required=False)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation('aura_main', exc)
logger.debug("Memory monitor registration skipped: %s", exc)
tracker.create_task(mem_monitor.start(), name="memory_monitor.start")
logger.info("🛡️ Task Supervisor active (Memory monitoring enabled).")
# Hot-Swap Bridge
runtime_loop = asyncio.get_running_loop()
def _on_actor_restart(name: str, new_pipe: Any):
logger.info("🔄 [HOTSWAP] Detected restart of %s. Re-binding IPC...", name)
def _schedule_rebind():
actor_bus = ServiceContainer.get("actor_bus", default=None)
if actor_bus:
tracker.create_task(
actor_bus.update_actor(name, new_pipe),
name=f"actor_bus.update_actor.{name}",
)
runtime_loop.call_soon_threadsafe(_schedule_rebind)
supervisor.set_restart_callback(_on_actor_restart)
# Joy & Social Integration
try:
from skills.joy_social_integration import integrate_joy_social
# We integrate without explicit config to use local development adapters by default
# unless user has set environment variables.
integrate_joy_social(orchestrator)
logger.info("🌟 Joy & Social systems integrated into startup sequence.")
except ImportError:
logger.warning("⚠️ JoySocial skills not found — skipping integration.")
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation('aura_main', exc)
logger.error("❌ Failed to integrate JoySocial: %s", exc)
# Apply Consciousness, Response, and SafeMode Genesis Patches
try:
from core.consciousness.apply_patches import apply_consciousness_patches
from core.conversation.apply_response_patches import apply_response_patches
from core.runtime.safe_mode import apply_orchestrator_patches
apply_consciousness_patches(orchestrator)
apply_response_patches()
# Activate the dynamic autonomy bridge, honoring the persisted safe-mode
# toggle (previously boot always forced full mode, making the setting dead).
boot_safe_mode = False
try:
from interface.routes.settings import _runtime_should_restrict, get_settings
boot_safe_mode = _runtime_should_restrict(get_settings())
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation('aura_main', exc, severity="debug")
apply_orchestrator_patches(orchestrator, safe_mode=boot_safe_mode)
logger.info(
"🛡️ [GENESIS] Autonomy bridge and stability patches active (safe_mode=%s).",
boot_safe_mode,
)
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation('aura_main', exc)
logger.error("❌ Failed to apply gap-closing patches: %s", exc)
async def _activate_ulysses_covenant_for_boot() -> dict[str, Any] | None:
"""Materialize the health-required governance organ before runtime start."""
if not _env_flag("AURA_ENABLE_ULYSSES_COVENANT", True):
logger.info("Ulysses Covenant disabled by explicit runtime configuration.")
return None
try:
from core.sovereignty.ulysses import boot_ulysses_covenant
covenant = await asyncio.to_thread(boot_ulysses_covenant)
status = await asyncio.to_thread(covenant.status)
if not isinstance(status, dict):
raise TypeError("Ulysses Covenant status must be a mapping")
logger.info(
"⚓ Ulysses Covenant online — %d active bindings (%d hard), "
"integrity %.2f, chain length %d.",
status["active_contracts"],
status["hard"],
status["integrity"],
status["chain_length"],
)
return status
except _AURA_MAIN_BOUNDARY_ERRORS as exc:
record_degradation("aura_main", exc)
logger.warning("Ulysses Covenant boot failed: %s", exc)
return None
async def _boot_runtime_orchestrator(
*,
ready_label: str,
readiness_context: str | None = None,
profile: str | None = None,
artifact_root: str | Path | None = None,
):
"""Canonical runtime boot path shared by CLI/server/desktop surfaces."""
from core.container import ServiceContainer
from core.orchestrator import create_orchestrator
orchestrator = create_orchestrator()
await bootstrap_aura(orchestrator)
_mark_runtime_boot_phase("resilient_ignition_tail")
# ── Engineering foundations ───────────────────────────────────────
# Taint register, lockdep, PSI, OOM policy, structural verifier, pass
# manager, reconcilers, lifecycles, telemetry dictionary, rate groups.