Auto-commit via make git (triggered by NFDOS)

This commit is contained in:
neoricalex 2025-11-16 23:07:23 +01:00
parent fdbe7793e8
commit 7a50ecf1ed
10 changed files with 567 additions and 341 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Autotools
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
config.log
config.status
configure
install-sh
missing
# Python build artefacts
__pycache__/
*.pyc
*.pyo
*.pyd
*.egg-info/
build/
dist/
# QEMU files
*.img
*.qcow2
# Editor junk
*.swp
*.swo
*~
.idea/
.vscode/

View File

@ -38,6 +38,7 @@ from .neurotron_config import (
)
from .cortex import Cortex
from .autodiagnostic import AutoDiagnostic # se for usado dentro do Cortex, ok mesmo assim
from neurotron.logbus import logbus
# =======================================================================================
@ -66,19 +67,18 @@ def detect_persistent_mode():
if _mounted(mount_point):
os.environ["NEUROTRON_MODE"] = "persistent"
os.environ["NEUROTRON_RUNTIME"] = "/var/neurotron/data"
os.environ["NEUROTRON_LOG"] = "/var/neurotron/logs"
os.environ["NEUROTRON_LOG_DIR"] = "/var/neurotron/logs"
else:
os.environ["NEUROTRON_MODE"] = "volatile"
os.environ["NEUROTRON_RUNTIME"] = "/tmp/neurotron_data"
os.environ["NEUROTRON_LOG"] = "/tmp/neurotron_logs"
os.environ["NEUROTRON_LOG_DIR"] = "/tmp/neurotron_logs"
runtime_dir = Path(os.environ["NEUROTRON_RUNTIME"])
log_dir = Path(os.environ["NEUROTRON_LOG"])
log_dir = Path(os.environ["NEUROTRON_LOG_DIR"])
runtime_dir.mkdir(parents=True, exist_ok=True)
log_dir.mkdir(parents=True, exist_ok=True)
return runtime_dir, log_dir
def read_system_metrics():
"""
CPU, memória e loadavg a partir de /proc.

View File

@ -1,67 +1,87 @@
from __future__ import annotations
import json, os
import json
from datetime import datetime, timezone
from rich.console import Console
from rich.table import Table
from pathlib import Path
from neurotron.logbus import logbus
from .neurotron_config import (
NEUROTRON_DATASET_PATH, NEUROTRON_HISTORY_KEEP, NEUROTRON_DIAG_SCHEMA,
HOMEOSTASIS_CPU_WARN, HOMEOSTASIS_CPU_ALERT,
HOMEOSTASIS_MEM_WARN, HOMEOSTASIS_MEM_ALERT,
HOMEOSTASIS_LOAD_WARN, HOMEOSTASIS_LOAD_ALERT,
NEUROTRON_DATASET_PATH,
NEUROTRON_HISTORY_KEEP,
NEUROTRON_DIAG_SCHEMA,
HOMEOSTASIS_CPU_WARN,
HOMEOSTASIS_CPU_ALERT,
HOMEOSTASIS_MEM_WARN,
HOMEOSTASIS_MEM_ALERT,
HOMEOSTASIS_LOAD_WARN,
HOMEOSTASIS_LOAD_ALERT,
)
from .perception import Perception
console = Console()
def _now_iso():
return datetime.now(timezone.utc).isoformat()
class AutoDiagnostic:
"""
Subsistema silencioso de diagnóstico do Neurotron.
Responsabilidades:
- recolher perceções internas (CPU, MEM, LOAD)
- comparar com diagnóstico anterior
- atualizar last_diagnostic.json
- manter histórico (rolling)
- atualizar telemetria contínua
- emitir eventos para logbus (não para stdout)
"""
def __init__(self, runtime_dir: str, log_dir: str):
self.runtime_dir = runtime_dir
self.log_dir = log_dir
self.runtime_dir = Path(runtime_dir)
self.log_dir = Path(log_dir)
self.data_dir = Path(NEUROTRON_DATASET_PATH)
self.data_dir.mkdir(parents=True, exist_ok=True)
self.last_file = self.data_dir / "last_diagnostic.json"
self.telemetry_file = self.data_dir / "telemetry.json"
self.perception = Perception()
self.current = None
self.previous = None
# ----------------------------------------------------------------------
# Utilitários internos
# ----------------------------------------------------------------------
def _load_previous(self):
if not self.last_file.exists():
return None
try:
with open(self.last_file, "r") as f:
return json.load(f)
except Exception:
return None
if self.last_file.exists():
return json.loads(self.last_file.read_text())
except Exception as e:
logbus.emit(f"[diag.warn] falha ao ler último diagnóstico: {e}")
return None
def _save_current(self, payload: dict):
def _save_current(self, payload):
history = []
if self.last_file.exists():
try:
with open(self.last_file, "r") as f:
prev = json.load(f)
history = prev.get("history", [])
history.append({
"timestamp": prev.get("timestamp"),
"cpu_percent": prev.get("cpu_percent"),
"mem_percent": prev.get("mem_percent"),
"loadavg": prev.get("loadavg"),
"state": prev.get("state", "UNKNOWN"),
})
history = history[-NEUROTRON_HISTORY_KEEP:]
except Exception:
history = []
prev = self._load_previous()
if prev:
history = prev.get("history", [])
history.append({
"timestamp": prev.get("timestamp"),
"cpu_percent": prev.get("cpu_percent"),
"mem_percent": prev.get("mem_percent"),
"loadavg": prev.get("loadavg"),
"state": prev.get("state", "UNKNOWN"),
})
history = history[-NEUROTRON_HISTORY_KEEP:]
payload["history"] = history
with open(self.last_file, "w") as f:
json.dump(payload, f, indent=2)
try:
self.last_file.write_text(json.dumps(payload, indent=2))
except Exception as e:
logbus.emit(f"[diag.error] falha ao gravar diagnóstico: {e}")
def _classify_state(self, cpu, mem, l1):
# valores podem ser "?"
try:
cpu = float(cpu)
mem = float(mem)
@ -69,81 +89,32 @@ class AutoDiagnostic:
except Exception:
return "UNKNOWN"
# ALERT/CRITICAL
if cpu >= HOMEOSTASIS_CPU_ALERT or mem >= HOMEOSTASIS_MEM_ALERT or l1 >= HOMEOSTASIS_LOAD_ALERT:
return "CRITICAL"
if cpu >= HOMEOSTASIS_CPU_WARN or mem >= HOMEOSTASIS_MEM_WARN or l1 >= HOMEOSTASIS_LOAD_WARN:
return "ALERT"
# OKs
return "STABLE"
def _delta(self, a, b):
try:
if isinstance(a, list) and isinstance(b, list) and len(a) == len(b):
return [round(float(x) - float(y), 2) for x, y in zip(a, b)]
return round(float(a) - float(b), 2)
except Exception:
return "?"
def _render_mini_trend(self, values, width=24, charset="▁▂▃▄▅▆▇█"):
if not values:
return ""
lo = min(values); hi = max(values)
if not isinstance(lo, (int, float)) or not isinstance(hi, (int, float)):
return ""
span = (hi - lo) or 1.0
levels = len(charset) - 1
bars = []
for v in values[-width:]:
if not isinstance(v, (int, float)):
bars.append("·")
continue
i = int(round((v - lo) / span * levels))
bars.append(charset[i])
return "".join(bars)
# ----------------------------------------------------------------------
# Execução principal
# ----------------------------------------------------------------------
def run_exam(self):
console.print("\n[bold]🤖 Iniciando rotina de Auto-Diagnóstico Evolutivo...[/bold]\n")
"""
Realiza um diagnóstico silencioso, atualiza os ficheiros,
e retorna (state, payload).
"""
snap = self.perception.snapshot()
cpu = snap.get("cpu_percent", "?")
mem = snap.get("mem_percent", "?")
cpu = snap.get("cpu_percent", "?")
mem = snap.get("mem_percent", "?")
load = snap.get("loadavg", ["?", "?", "?"])
l1 = load[0] if isinstance(load, list) and load else "?"
prev = self._load_previous()
self.previous = prev
# deltas
cpu_prev = prev.get("cpu_percent") if prev else "?"
mem_prev = prev.get("mem_percent") if prev else "?"
load_prev = prev.get("loadavg") if prev else ["?", "?", "?"]
d_cpu = self._delta(cpu, cpu_prev)
d_mem = self._delta(mem, mem_prev)
d_load = self._delta(load, load_prev)
# estado
l1 = load[0] if isinstance(load, list) and load else "?"
state = self._classify_state(cpu, mem, l1)
# tabela
table = Table(title="🩺 Exame Clínico Evolutivo", show_lines=True)
table.add_column("Sinal Vital")
table.add_column("Atual", justify="right")
table.add_column("Δ", justify="center")
table.add_column("Anterior", justify="right")
def fmt(v):
if isinstance(v, list):
return str(v)
return str(v)
table.add_row("CPU (%)", fmt(cpu), fmt(d_cpu), fmt(cpu_prev))
table.add_row("Memória (%)", fmt(mem), fmt(d_mem), fmt(mem_prev))
table.add_row("Carga média (1/5/15)", fmt(load), "" if d_load == "?" else fmt(d_load), fmt(load_prev))
console.print(table)
payload = {
"schema": NEUROTRON_DIAG_SCHEMA,
"timestamp": _now_iso(),
@ -156,17 +127,22 @@ class AutoDiagnostic:
"term": snap.get("env_term"),
},
}
self._save_current(payload)
console.print(f"[green]✔ Histórico evolutivo atualizado em:[/green] \n{self.last_file}")
self._update_telemetry(payload)
# Atualiza telemetria contínua
# Falamos apenas através do logbus
logbus.emit(f"[diag] estado={state} cpu={cpu} mem={mem} load1={l1}")
return state, payload
# ----------------------------------------------------------------------
def _update_telemetry(self, payload):
try:
telemetry_file = Path(NEUROTRON_DATASET_PATH) / "telemetry.json"
telemetry_file.parent.mkdir(parents=True, exist_ok=True)
telemetry = []
if telemetry_file.exists():
telemetry = json.loads(telemetry_file.read_text() or "[]")
if self.telemetry_file.exists():
telemetry = json.loads(self.telemetry_file.read_text() or "[]")
telemetry.append({
"timestamp": payload["timestamp"],
@ -176,10 +152,9 @@ class AutoDiagnostic:
"state": payload.get("state"),
})
telemetry = telemetry[-128:] # manter últimas 128 amostras
telemetry_file.write_text(json.dumps(telemetry, indent=2))
telemetry = telemetry[-128:]
self.telemetry_file.write_text(json.dumps(telemetry, indent=2))
except Exception as e:
console.print(f"[yellow]⚠️ Falha ao atualizar telemetria:[/] {e}")
logbus.emit(f"[diag.warn] falha ao atualizar telemetria: {e}")
return state, payload

View File

@ -1,27 +1,36 @@
import json
import time
from collections import defaultdict, deque
from pathlib import Path
from collections import defaultdict, deque
from time import sleep
from rich.console import Console
from neurotron.logbus import logbus
from .neuron import Neuron
from .hippocampus import Hippocampus
from .perception import Perception
from .motor import Motor
from .neurotron_config import (
NEUROTRON_MODE, NEUROTRON_TICK, NEUROTRON_TICK_MIN, NEUROTRON_TICK_MAX, NEUROTRON_TICK_STEP,
NEUROTRON_DIAG_EVERY_TICKS, NEUROTRON_DATASET_PATH,
HEARTBEAT_ENABLED, HEARTBEAT_STYLE, NEUROTRON_THRESHOLDS,
TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
)
from .autodiagnostic import AutoDiagnostic
from .neurotron_config import (
NEUROTRON_MODE, NEUROTRON_TICK,
NEUROTRON_TICK_MIN, NEUROTRON_TICK_MAX, NEUROTRON_TICK_STEP,
NEUROTRON_DIAG_EVERY_TICKS,
NEUROTRON_DATASET_PATH,
HEARTBEAT_ENABLED, HEARTBEAT_STYLE,
NEUROTRON_THRESHOLDS,
TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
)
# =====================================================================
# Neurónios básicos
# =====================================================================
class VitalSigns(Neuron):
name = "VitalSigns"
def observe(self) -> None:
def observe(self):
snap = self.ctx.perception.snapshot()
self.publish("vitals", snap)
self.ctx.memory.remember("observe.vitals", snap)
@ -29,123 +38,152 @@ class VitalSigns(Neuron):
class EchoAgent(Neuron):
name = "EchoAgent"
def think(self) -> None:
def think(self):
msg = self.consume("vitals")
if msg:
self.publish("actions", {"action": "echo", "text": f"CPU {msg.get('cpu_percent', '?')}%"})
self.publish("actions", {
"action": "echo",
"text": f"CPU {msg.get('cpu_percent', '?')}%"
})
# =====================================================================
# C O R T E X — versão V6 (Pure LogBus Edition)
# =====================================================================
class Cortex:
"""
Orquestrador: liga neurónios, bus de mensagens, memória, IO e ciclo cognitivo.
Agora com Telemetria Contínua (V5): heartbeat, microalertas e flush periódico.
Orquestrador cognitivo do Neurotron.
Totalmente silencioso toda saída vai para o logbus.
"""
def __init__(self, runtime_dir, log_dir, tick_seconds=NEUROTRON_TICK):
self.runtime_dir = runtime_dir
self.log_dir = log_dir
self.runtime_dir = Path(runtime_dir)
self.log_dir = Path(log_dir)
self.tick = float(tick_seconds)
self.mode = NEUROTRON_MODE
self._tick_count = 0
self.diagnostic = AutoDiagnostic(runtime_dir, log_dir)
self.console = Console()
self.diagnostic = AutoDiagnostic(runtime_dir, log_dir)
self.memory = Hippocampus(log_dir=log_dir)
self.perception = Perception()
self.motor = Motor()
# Message bus simples: channels → deque
# Message bus interno
self.bus = defaultdict(lambda: deque(maxlen=32))
# Telemetria em memória (curto prazo)
# Telemetria curta em RAM
self.telemetry = deque(maxlen=TELEMETRY_MAXLEN)
# Regista neurónios (podes adicionar mais à medida)
self.neurons: list[Neuron] = [
# Neurónios ativos
self.neurons = [
VitalSigns(self),
EchoAgent(self),
]
self._booted = False
# Caminho para gravar a telemetria
# Caminho telemetria longa
self.telemetry_path = Path(NEUROTRON_DATASET_PATH) / "telemetry.json"
self.telemetry_path.parent.mkdir(parents=True, exist_ok=True)
# ——— ciclo de vida ———
def boot(self) -> None:
# =================================================================
# BOOT / SHUTDOWN
# =================================================================
def boot(self):
if self._booted:
return
self.console.print("[bold cyan]🧠 Neurotron[/] — boot")
logbus.emit("🧠 boot() — inicializando Neurotron")
self.memory.remember("boot", {"version": "0.1", "tick": self.tick})
self._booted = True
#state, _ = self.diagnostic.run_exam()
#self._apply_homeostasis(state)
def shutdown(self, reason=""):
logbus.emit(f"⚠️ shutdown — {reason}")
self.memory.remember("shutdown", {"reason": reason})
def fatal(self, e: Exception):
logbus.emit(f"🔥 fatal error: {repr(e)}")
self.memory.remember("fatal", {"error": repr(e)})
raise e
# =================================================================
# CICLO COGNITIVO
# =================================================================
def observe(self):
for n in self.neurons:
n.observe()
def think(self):
for n in self.neurons:
n.think()
def act(self):
action = self.bus_consume("actions")
if not action:
return
if action.get("action") == "echo":
res = self.motor.run("echo", [action.get("text", "")])
self.memory.remember("act.echo", res)
# Redireciona stdout para logbus
if res.get("stdout"):
logbus.emit(f"[motor.echo] {res['stdout'].strip()}")
if res.get("stderr"):
logbus.emit(f"[motor.echo.err] {res['stderr'].strip()}")
def rest(self):
# Heartbeat + microalertas
if HEARTBEAT_ENABLED:
self._heartbeat_and_telemetry()
sleep(self.tick)
self._tick_count += 1
# Diagnóstico periódico
if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
state, _ = self.diagnostic.run_exam()
self._apply_homeostasis(state)
# Flush periódico telemetria
if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
self._flush_telemetry()
# =================================================================
# HOMEOSTASE
# =================================================================
def _apply_homeostasis(self, state):
old = self.tick
if state == "CRITICAL":
self.mode = "diagnostic"
self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP)
elif state == "ALERT":
self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP / 2)
elif state == "STABLE":
self.tick = max(NEUROTRON_TICK_MIN, self.tick - NEUROTRON_TICK_STEP / 2)
# UNKNOWN → não mexe
def shutdown(self, reason: str = ""):
self.console.print(f"[yellow]shutdown:[/] {reason}")
self.memory.remember("shutdown", {"reason": reason})
if self.tick != old:
logbus.emit(f"[homeostasis] tick ajustado {old:.2f}s → {self.tick:.2f}s (state={state})")
def fatal(self, e: Exception):
self.console.print(f"[red]fatal:[/] {e!r}")
self.memory.remember("fatal", {"error": repr(e)})
print(f"fatal: {repr(e)}")
raise
# =================================================================
# HEARTBEAT + MICROALERTAS
# =================================================================
# ——— loop ———
def observe(self) -> None:
for n in self.neurons:
n.observe()
def think(self) -> None:
for n in self.neurons:
n.think()
def act(self) -> None:
# Consumir ações agregadas e executar
action = self.bus_consume("actions")
if action and action.get("action") == "echo":
res = self.motor.run("echo", [action.get("text", "")])
self.memory.remember("act.echo", res)
if res.get("stdout"):
self.console.print(f"[green]{res['stdout'].strip()}[/]")
def rest(self):
# Heartbeat e microalertas antes de dormir
if HEARTBEAT_ENABLED:
self._heartbeat_and_telemetry()
# Pausa regulada
sleep(self.tick)
# Contador e rotinas periódicas
self._tick_count += 1
if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
#state, _ = self.diagnostic.run_exam()
#self._apply_homeostasis(state)
pass
if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
self._flush_telemetry()
# ——— telemetria/alertas ———
def _heartbeat_and_telemetry(self):
snap = self.perception.snapshot()
cpu = snap.get("cpu_percent", "?")
mem = (snap.get("mem") or {}).get("percent", "?")
load = snap.get("loadavg") or []
# Adiciona ao buffer de telemetria
cpu = snap.get("cpu_percent")
mem = snap.get("mem_percent")
load = snap.get("loadavg")
load1 = load[0] if isinstance(load, list) and load else None
# Salva telemetria curta
self.telemetry.append({
"ts": time.time(),
"cpu": cpu,
@ -154,23 +192,13 @@ class Cortex:
"tick": self.tick,
})
# Microalertas com base nos limiares
self._evaluate_microalerts(cpu, mem, load)
self._evaluate_microalerts(cpu, mem, load1)
# Heartbeat visual
color = self._color_for_levels(cpu, mem, load)
if HEARTBEAT_STYLE == "compact":
self.console.print(f"[bold {color}]💓[/] CPU: {cpu}% | MEM: {mem}% | TICK: {self.tick:.2f}s")
else:
self.console.print(
f"[bold {color}]💓 [Heartbeat][/bold {color}] "
f"CPU: {cpu}% | MEM: {mem}% | LOAD: {load} | TICK: {self.tick:.2f}s | MODE: {self.mode}"
)
# Heartbeat → logbus
logbus.emit(f"💓 cpu={cpu}% mem={mem}% tick={self.tick:.2f}s")
def _evaluate_microalerts(self, cpu, mem, load):
def _evaluate_microalerts(self, cpu, mem, load1):
alerts = []
# Normaliza
load1 = load[0] if (isinstance(load, (list, tuple)) and load) else None
try:
if isinstance(cpu, (int, float)) and cpu >= NEUROTRON_THRESHOLDS["cpu_high"]:
@ -179,15 +207,15 @@ class Cortex:
alerts.append(("mem", mem))
if isinstance(load1, (int, float)) and load1 >= NEUROTRON_THRESHOLDS["load1_high"]:
alerts.append(("load1", load1))
except KeyError:
pass # thresholds incompletos → sem microalertas
except Exception:
return
if not alerts:
return
for (metric, value) in alerts:
self.console.print(f"[yellow]⚠️ Microalerta:[/] {metric.upper()} {value} — ajustando homeostase (tick +{NEUROTRON_TICK_STEP:.2f}s)")
# Ajuste simples de segurança
for metric, value in alerts:
logbus.emit(f"⚠️ microalerta: {metric}={value} — ajustando tick")
self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP)
self.memory.remember("microalert", {
@ -196,36 +224,28 @@ class Cortex:
"new_tick": self.tick,
})
def _color_for_levels(self, cpu, mem, load):
# Heurística simples de cor
try:
load1 = load[0] if (isinstance(load, (list, tuple)) and load) else 0.0
high = (
(isinstance(cpu, (int, float)) and cpu >= NEUROTRON_THRESHOLDS["cpu_high"]) or
(isinstance(mem, (int, float)) and mem >= NEUROTRON_THRESHOLDS["mem_high"]) or
(isinstance(load1, (int, float)) and load1 >= NEUROTRON_THRESHOLDS["load1_high"])
)
if high:
return "yellow"
except Exception:
pass
return "green"
# =================================================================
# TELEMETRIA LONGA
# =================================================================
def _flush_telemetry(self):
# Grava o buffer de telemetria em JSON (mantendo histórico curto)
try:
data = list(self.telemetry)
with self.telemetry_path.open("w") as f:
json.dump(data, f)
self.memory.remember("telemetry.flush", {"count": len(data), "path": str(self.telemetry_path)})
self.telemetry_path.write_text(json.dumps(data))
self.memory.remember("telemetry.flush", {
"count": len(data),
"path": str(self.telemetry_path)
})
except Exception as e:
self.console.print(f"[red]✖ Falha ao gravar telemetria:[/] {e!r}")
self.memory.remember("telemetry.error", {"error": repr(e)})
logbus.emit(f"[telemetry.error] {e}")
# ——— bus ———
def bus_publish(self, channel: str, payload: dict) -> None:
# =================================================================
# BUS
# =================================================================
def bus_publish(self, channel, payload):
self.bus[channel].append(payload)
def bus_consume(self, channel: str) -> dict | None:
def bus_consume(self, channel):
q = self.bus[channel]
return q.popleft() if q else None
return q.popleft() if q else None

View File

@ -3,7 +3,7 @@ from datetime import datetime
try:
import orjson as json
except Exception: # fallback leve
except Exception:
import json # type: ignore
class Hippocampus:
@ -11,9 +11,11 @@ class Hippocampus:
Memória contextual simples (JSON Lines): append-only.
Guarda perceções, decisões e ações para replays futuros.
"""
def __init__(self, log_dir: Path):
self.log_dir = log_dir
self.events_file = log_dir / "events.jsonl"
self.log_dir.mkdir(parents=True, exist_ok=True)
self.events_file = self.log_dir / "events.jsonl"
def remember(self, kind: str, data: dict) -> None:
rec = {
@ -21,14 +23,17 @@ class Hippocampus:
"kind": kind,
"data": data,
}
try:
if "orjson" in json.__name__:
blob = json.dumps(rec)
else:
blob = json.dumps(rec) # type: ignore
# Compatível com orjson e stdlib json
blob = json.dumps(rec)
if isinstance(blob, str):
blob = blob.encode("utf-8")
with self.events_file.open("ab") as f:
f.write(blob if isinstance(blob, bytes) else blob.encode("utf-8"))
f.write(blob)
f.write(b"\n")
except Exception:
# evitar crash por IO em early boot
# Evitar crash em early boot ou IO quebrado
pass

83
src/neurotron/logbus.py Normal file
View File

@ -0,0 +1,83 @@
"""
Neurotron LogBus Sistema Centralizado de Logging
==================================================
Objetivo:
- Centralizar *toda* a saída de logs do Neurotron
- Funcionar tanto no dashboard curses quanto em modo headless
- Manter logs persistentes quando o hipocampo físico está montado
- Fornecer uma API simples:
from neurotron.logbus import log
log("mensagem")
Componentes:
- LogBus: roteador de mensagens (fila stdout ficheiro)
- log(): função global simplificada
"""
from __future__ import annotations
import json
import time
from queue import Queue
from pathlib import Path
class LogBus:
def __init__(self):
self.queue: Queue | None = None # usado pelo dashboard
self.stdout_enabled = True # imprime em modo headless
self.persist_enabled = False # grava em disco
self.persist_file: Path | None = None
# ---------------------------------------------------------
# Inicialização de destinos
# ---------------------------------------------------------
def enable_dashboard(self, q: Queue):
"""Liga o LogBus ao dashboard (curses)."""
self.queue = q
def enable_persist(self, path: Path):
"""Ativa salvamento permanente da stream de logs."""
self.persist_file = path
self.persist_enabled = True
path.parent.mkdir(parents=True, exist_ok=True)
if not path.exists():
path.write_text("") # cria ficheiro vazio
# ---------------------------------------------------------
# Emissão centralizada
# ---------------------------------------------------------
def log(self, message: str):
ts = time.strftime("%H:%M:%S")
line = f"[{ts}] {message}"
# 1) Dashboard (queue)
if self.queue:
try:
self.queue.put(line)
except Exception:
pass
# 2) STDOUT
if self.stdout_enabled:
try:
print(line)
except Exception:
pass
# 3) Persistência
if self.persist_enabled and self.persist_file:
try:
with open(self.persist_file, "a") as f:
f.write(json.dumps({"ts": ts, "msg": message}) + "\n")
except Exception:
pass
# Instância global — usada em todo o Neurotron
logbus = LogBus()
def log(msg: str):
"""Função simples: apenas delega ao LogBus global."""
logbus.log(msg)

View File

@ -1,27 +1,77 @@
import subprocess
from neurotron.logbus import logbus
class Motor:
"""
Ator do sistema: executa comandos controlados (whitelist).
Mantém-se minimal até termos política de segurança mais rica.
Subsistema 'ator' do Neurotron.
Executa comandos controlados (whitelist rígida),
sem side-effects e com logging centralizado.
FUTURO:
- níveis de permissão
- sandboxes
- ações abstratas (ex: mover, sinalizar, emitir evento)
"""
SAFE_CMDS = {
"echo": ["echo"],
"sh": ["/bin/sh"], # shell interativo (init)
"sh": ["/bin/sh"], # shell básico — útil para debug interno
}
def __init__(self):
pass
def run(self, cmd: str, args: list[str] | None = None) -> dict:
prog = self.SAFE_CMDS.get(cmd)
if not prog:
return {"ok": False, "error": f"cmd '{cmd}' não permitido"}
try:
full = prog + (args or [])
res = subprocess.run(full, capture_output=True, text=True)
return {
"ok": res.returncode == 0,
"code": res.returncode,
"stdout": res.stdout,
"stderr": res.stderr,
"""
Executa um comando permitido e retorna:
{
"ok": bool,
"code": int | None,
"stdout": str,
"stderr": str,
}
"""
if cmd not in self.SAFE_CMDS:
logbus.emit(f"[motor.warn] comando bloqueado: '{cmd}'")
return {
"ok": False,
"code": None,
"stdout": "",
"stderr": f"cmd '{cmd}' não permitido",
}
try:
full = self.SAFE_CMDS[cmd] + (args or [])
res = subprocess.run(
full,
capture_output=True,
text=True,
env={}, # ambiente neutro → evita leaks
)
ok = (res.returncode == 0)
if not ok:
logbus.emit(
f"[motor.warn] '{cmd}' retornou código {res.returncode}"
)
return {
"ok": ok,
"code": res.returncode,
"stdout": res.stdout or "",
"stderr": res.stderr or "",
}
except Exception as e:
return {"ok": False, "error": str(e)}
logbus.emit(f"[motor.error] exceção ao executar '{cmd}': {e}")
return {
"ok": False,
"code": None,
"stdout": "",
"stderr": str(e),
}

View File

@ -1,30 +1,58 @@
from typing import Any, Dict
from typing import Any, Dict, Optional
from neurotron.logbus import logbus
class Neuron:
"""
Classe-base de um neurónio-agente.
Cada neurónio pode observar/agir e trocar mensagens via o bus do Cortex.
Classe-base de um neurónio-agente.
Cada neurónio participa no ciclo cognitivo do Cortex:
observe() think() act()
Pode comunicar via:
- publish(channel, payload)
- consume(channel)
Implementações devem respeitar esta interface sem causar efeitos colaterais
externos (sem prints, sem IO não controlado).
"""
name = "Neuron"
name: str = "Neuron"
def __init__(self, ctx: "Cortex"):
self.ctx = ctx
# ----------------------------------------------------------------------
# Ciclo cognitivo — para override por neurónios concretos
# ----------------------------------------------------------------------
def observe(self) -> None:
"""Ler estado do mundo (sensores, /proc, eventos)."""
"""Ler estado do mundo (sensores, perceções, eventos internos)."""
return
def think(self) -> None:
"""Processar/planejar usando o estado atual."""
"""Processar informação, planear, atualizar estado interno."""
return
def act(self) -> None:
"""Executar uma ação (opcional)."""
"""Executar uma ação, enviar comandos, publicar eventos."""
return
# Utilitários
def publish(self, channel: str, payload: Dict[str, Any]) -> None:
self.ctx.bus_publish(channel, payload)
# ----------------------------------------------------------------------
# Comunicação inter-neurónios via Cortex Bus
# ----------------------------------------------------------------------
def consume(self, channel: str) -> Dict[str, Any] | None:
return self.ctx.bus_consume(channel)
def publish(self, channel: str, payload: Dict[str, Any]) -> None:
"""Publica mensagem num canal do bus cognitivo."""
try:
self.ctx.bus_publish(channel, payload)
except Exception as e:
logbus.emit(f"[warn] {self.name}.publish falhou: {e}")
def consume(self, channel: str) -> Optional[Dict[str, Any]]:
"""Lê (e remove) última mensagem disponível num canal."""
try:
return self.ctx.bus_consume(channel)
except Exception as e:
logbus.emit(f"[warn] {self.name}.consume falhou: {e}")
return None

View File

@ -1,42 +1,37 @@
"""
🧠 neurotron_config.py
NFDOS Núcleo de parâmetros vitais do Neurotron
------------------------------------------------
Nova versão para o layout:
.../neurotron/
src/
data/
NFDOS Núcleo de parâmetros vitais estáticos do Neurotron
Este módulo define apenas:
- parâmetros cognitivos estáticos
- thresholds
- limites
- caminhos locais relativos ao package (ex: DATA_DIR interno)
Ele NÃO define:
- diretórios de runtime (/var/neurotron, /tmp)
- diretórios de logs dinâmicos
- configurações voláteis
Essas pertencem ao runtime e são avaliadas no __main__.
"""
from pathlib import Path
# ======================================
# 🌐 Diretórios e Caminhos
# ======================================
# ============================================================================
# 📁 Diretórios internos do package (não confundir com runtime!)
# ============================================================================
# Diretório deste ficheiro → .../neurotron/src/neurotron_config.py
THIS_FILE = Path(__file__).resolve()
SRC_DIR = THIS_FILE.parent # .../neurotron/src
BASE_DIR = SRC_DIR.parent # .../neurotron/
SRC_DIR = THIS_FILE.parent # .../neurotron/src/neurotron
BASE_DIR = SRC_DIR.parent # .../neurotron/src
# Onde vivem as configs/logs da “instalação”
# Diretórios que fazem parte da instalação do package
DATA_DIR = BASE_DIR / "data"
CONFIG_DIR = DATA_DIR / "configs"
LOG_DIR = DATA_DIR / "logs"
PACKAGE_LOG_DIR = DATA_DIR / "logs" # logs do próprio package (não runtime!)
# Modo persistente do NFDOS (quando /var/neurotron está montado)
RUNTIME_DIR = Path("/var/run/neurotron")
MOUNT_POINT = "/var/neurotron"
# Candidatos para disco persistente do hipocampo
DISK_CANDIDATES = [
"/dev/vda", "/dev/vdb",
"/dev/sda", "/dev/hda"
]
# ======================================
# ⚙️ Parâmetros Cognitivos Principais
# ======================================
# ============================================================================
# ⚙️ Parâmetros Cognitivos
# ============================================================================
NEUROTRON_TICK = 1.0
NEUROTRON_VERBOSITY = 1
@ -59,9 +54,9 @@ NEUROTRON_TICK_STEP = 0.25
NEUROTRON_SEED = 42
NEUROTRON_MEMORY_SIZE = 256 # KB
# ======================================
# 🧩 Parâmetros de Subsistemas
# ======================================
# ============================================================================
# 🔌 Subsistemas
# ============================================================================
CORTEX_MAX_THREADS = 1
CORTEX_LOOP_DELAY = 0.1
@ -76,14 +71,13 @@ PERCEPTION_CPU_SOURCE = "/proc/stat"
PERCEPTION_MEM_SOURCE = "/proc/meminfo"
PERCEPTION_UPDATE_INTERVAL = 2.0
# ======================================
# 🧠 Parâmetros futuros
# ======================================
# ============================================================================
# 📊 Telemetria e diagnóstico
# ============================================================================
NEUROTRON_EXPANSION_MODE = "none"
NEUROTRON_DATASET_PATH = DATA_DIR
NEUROTRON_HISTORY_KEEP = 8
NEUROTRON_DIAG_SCHEMA = "v4"
HEARTBEAT_ENABLED = True
@ -97,22 +91,3 @@ NEUROTRON_THRESHOLDS = {
TELEMETRY_MAXLEN = 64
TELEMETRY_FLUSH_EVERY_TICKS = 5
# ======================================
# 🧭 Utilitário
# ======================================
def show_config():
"""Mostra a configuração atual do Neurotron (apenas NEUROTRON_*)"""
import json
cfg = {
k: v
for k, v in globals().items()
if k.startswith("NEUROTRON_")
}
print(json.dumps(cfg, indent=2, default=str))
if __name__ == "__main__":
show_config()

View File

@ -1,52 +1,75 @@
import os
from time import sleep
from neurotron.logbus import logbus
class Perception:
"""
Sensores internos via /proc:
- CPU % calculado por delta de /proc/stat
- CPU % por delta de /proc/stat
- Memória % via /proc/meminfo
- Carga média via /proc/loadavg
Sem dependências externas (psutil).
- Carga média /proc/loadavg ou os.getloadavg()
Totalmente silencioso (sem prints).
"""
# ----------------------------------------------------------------------
# CPU
# ----------------------------------------------------------------------
def _read_proc_stat(self):
"""Lê a linha 'cpu ' de /proc/stat. Retorna dict ou None."""
try:
with open("/proc/stat", "r") as f:
line = f.readline()
if not line.startswith("cpu "):
return None
parts = line.strip().split()[1:]
vals = list(map(int, parts[:10])) # user nice system idle iowait irq softirq steal guest guest_nice
vals = list(map(int, parts[:10]))
return {
"user": vals[0], "nice": vals[1], "system": vals[2], "idle": vals[3],
"iowait": vals[4], "irq": vals[5], "softirq": vals[6], "steal": vals[7],
"guest": vals[8], "guest_nice": vals[9],
}
except Exception:
return None
def _cpu_percent(self, interval=0.05):
"""Computa CPU% entre duas leituras."""
a = self._read_proc_stat()
if not a:
return "?"
sleep(interval) # micro-janelinha
sleep(interval)
b = self._read_proc_stat()
if not b:
return "?"
# Totais
idle_a = a["idle"] + a["iowait"]
idle_b = b["idle"] + b["iowait"]
non_a = sum(a.values()) - idle_a
non_b = sum(b.values()) - idle_b
total_a = idle_a + non_a
total_b = idle_b + non_b
totald = total_b - total_a
idled = idle_b - idle_a
if totald <= 0:
return "?"
usage = (totald - idled) * 100.0 / totald
return round(usage, 1)
# ----------------------------------------------------------------------
# Memória
# ----------------------------------------------------------------------
def _mem_percent(self):
try:
info = {}
@ -54,36 +77,73 @@ class Perception:
for line in f:
k, v = line.split(":", 1)
info[k.strip()] = v.strip()
def kB(key):
if key not in info: return None
return float(info[key].split()[0]) # kB
return float(info[key].split()[0]) if key in info else None
mem_total = kB("MemTotal")
mem_avail = kB("MemAvailable")
if not mem_total or mem_avail is None:
if mem_total is None or mem_avail is None:
return "?"
used = mem_total - mem_avail
return round(used * 100.0 / mem_total, 1)
except Exception:
return "?"
# ----------------------------------------------------------------------
# Loadavg
# ----------------------------------------------------------------------
def _loadavg(self):
try:
if hasattr(os, "getloadavg"):
l1, l5, l15 = os.getloadavg()
return [round(l1, 2), round(l5, 2), round(l15, 2)]
with open("/proc/loadavg", "r") as f:
parts = f.read().strip().split()
l1, l5, l15 = map(float, parts[:3])
return [round(l1, 2), round(l5, 2), round(l15, 2)]
except Exception:
return ["?", "?", "?"]
def snapshot(self) -> dict:
return {
"env_user": os.environ.get("USER") or "root",
"env_term": os.environ.get("TERM") or "unknown",
"cpu_percent": self._cpu_percent(),
"mem_percent": self._mem_percent(),
"loadavg": self._loadavg(),
}
# ----------------------------------------------------------------------
# Snapshot principal
# ----------------------------------------------------------------------
def snapshot(self) -> dict:
"""
Retorna snapshot interno:
{
"env_user": "...",
"env_term": "...",
"cpu_percent": n | "?",
"mem_percent": n | "?",
"loadavg": [l1, l5, l15]
}
Sempre seguro. Nunca lança exceção.
"""
try:
return {
"env_user": os.environ.get("USER") or "root",
"env_term": os.environ.get("TERM") or "unknown",
"cpu_percent": self._cpu_percent(),
"mem_percent": self._mem_percent(),
"loadavg": self._loadavg(),
}
except Exception as e:
# fallback extremo — nunca quebrar o Neurotron
logbus.emit(f"[warn] Perception.snapshot falhou: {e}")
return {
"env_user": "unknown",
"env_term": "unknown",
"cpu_percent": "?",
"mem_percent": "?",
"loadavg": ["?", "?", "?"],
}