ORION-Runtime / autonomous /execution_engine.py
Alvoradozerouno's picture
ORION System Update 2026-05-12: Multi-Agent Discussion, KRIA Validation, Agent Refactor, DDGK->ORION Migration
da3e674
Raw
History Blame Contribute Delete
35.9 kB
# -*- coding: utf-8 -*-
"""
EXECUTION ENGINE — TaskExecutor
================================
Fuehrt Tasks TATSAECHLICH aus (nicht nur planen).
Task Queue (JSON) -> Executor -> Action -> Audit -> Next Task
Unterstützte Actions:
- send_email: SMTP-E-Mail tatsächlich versenden
- export_ffg_form: Formular via FormFiller exportieren
- query_esa_api: ESA openEO API tatsächlich abfragen
- query_cern_api: CERN OpenData API tatsächlich abfragen
- run_guardian_check: Guardian Risk-Check tatsächlich ausführen
- write_file: Datei tatsächlich schreiben
- run_script: Python-Skript tatsächlich ausführen
Jede Aktion erzeugt:
- SHA-256 Audit-Eintrag
- EpistemicState: known (erledigt) vs estimated (geplant)
"""
from __future__ import annotations
import hashlib
import json
import logging
import os
import smtplib
import subprocess
import sys
import time
import traceback
from dataclasses import dataclass, field
from datetime import datetime, timezone
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional
# Projekt-Root ins Path
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from autonomous.audit_chain import AuditChain
from autonomous.epistemic_state import EpistemicState, EpistemicStatus
logger = logging.getLogger("execution_engine")
# ─── ENUMS ────────────────────────────────────────────────────────────────────
class TaskStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
SKIPPED = "skipped"
class EpistemicActionState(Enum):
KNOWN = "known" # Tatsaechlich erledigt, verifiziert
ESTIMATED = "estimated" # Geplant, noch nicht ausgefuehrt
# ─── DATA CLASSES ─────────────────────────────────────────────────────────────
@dataclass
class TaskResult:
"""Ergebnis einer Task-Ausführung."""
task_id: str
task_type: str
status: TaskStatus
output: Dict[str, Any] = field(default_factory=dict)
error: str = ""
sha256: str = ""
epistemic_state: str = EpistemicActionState.ESTIMATED.value
started_at: str = ""
completed_at: str = ""
duration_ms: float = 0.0
def to_dict(self) -> dict:
return {
"task_id": self.task_id,
"task_type": self.task_type,
"status": self.status.value,
"output": self.output,
"error": self.error,
"sha256": self.sha256,
"epistemic_state": self.epistemic_state,
"started_at": self.started_at,
"completed_at": self.completed_at,
"duration_ms": self.duration_ms,
}
# ─── ACTION HANDLERS ──────────────────────────────────────────────────────────
class ActionHandler:
"""
Basisklasse fuer konkrete Action-Handler.
Jeder Handler fuehrt eine tatsaechliche Aktion aus.
"""
def __init__(self, audit_chain: AuditChain, epistemic: EpistemicState):
self.audit = audit_chain
self.epistemic = epistemic
def execute(self, params: Dict[str, Any]) -> TaskResult:
raise NotImplementedError
class SendEmailHandler(ActionHandler):
"""Versendet E-Mails tatsächlich via SMTP."""
ACTION_TYPE = "send_email"
def execute(self, params: Dict[str, Any]) -> TaskResult:
to_addr = params.get("to", "")
subject = params.get("subject", "")
body = params.get("body", "")
html = params.get("html", False)
if not to_addr or not subject:
return TaskResult(
task_id=params.get("_task_id", "email"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error="Missing 'to' or 'subject' parameter",
)
smtp_host = os.getenv("SMTP_HOST", "smtp.gmail.com")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_user = os.getenv("SMTP_USER", "")
smtp_pass = os.getenv("SMTP_PASS", "")
if not smtp_user or not smtp_pass:
return TaskResult(
task_id=params.get("_task_id", "email"),
task_type=self.ACTION_TYPE,
status=TaskStatus.SKIPPED,
error="SMTP credentials not configured (SMTP_USER/SMTP_PASS missing in .env)",
output={"to": to_addr, "subject": subject},
)
start = time.monotonic()
try:
msg = MIMEMultipart("alternative")
msg["From"] = smtp_user
msg["To"] = to_addr
msg["Subject"] = subject
if html:
msg.attach(MIMEText(body, "html", "utf-8"))
else:
msg.attach(MIMEText(body, "plain", "utf-8"))
with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as server:
server.starttls()
server.login(smtp_user, smtp_pass)
server.sendmail(smtp_user, [to_addr], msg.as_string())
duration = (time.monotonic() - start) * 1000
result_sha = hashlib.sha256(
json.dumps({"to": to_addr, "subject": subject}, sort_keys=True).encode()
).hexdigest()
self.audit.record("email_sent", {"to": to_addr, "subject": subject, "sha256": result_sha})
self.epistemic.add_belief(
f"Email sent to {to_addr}: {subject}",
EpistemicStatus.CERTAIN, 0.95, source="execution_engine", ttl_hours=720,
)
return TaskResult(
task_id=params.get("_task_id", "email"),
task_type=self.ACTION_TYPE,
status=TaskStatus.COMPLETED,
output={"to": to_addr, "subject": subject, "sent_at": datetime.now(timezone.utc).isoformat()},
sha256=result_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
except Exception as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("email_failed", {"to": to_addr, "error": str(exc)})
return TaskResult(
task_id=params.get("_task_id", "email"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=str(exc),
output={"to": to_addr, "subject": subject},
duration_ms=duration,
)
class ExportFFGFormHandler(ActionHandler):
"""Exportiert FFG-Formulare tatsächlich via FormFiller."""
ACTION_TYPE = "export_ffg_form"
def execute(self, params: Dict[str, Any]) -> TaskResult:
from space.form_filler import FormFiller
application_path = params.get("application", "")
output_path = params.get("output", "")
portal = params.get("portal", "ffg_ecall")
if not application_path:
return TaskResult(
task_id=params.get("_task_id", "ffg_form"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error="Missing 'application' parameter (path to markdown file)",
)
start = time.monotonic()
try:
filler = FormFiller()
app_data = filler.load_markdown_application(application_path)
if output_path:
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
if portal == "ffg_ecall":
xml_str = filler.export_ffg_ecall(app_data)
out.write_text(xml_str, encoding="utf-8")
output_info = {"format": "ffg_ecall_xml", "output_file": str(out), "size_bytes": out.stat().st_size}
elif portal == "esa_sysnova":
esa_dict = filler.export_esa_sysnova(app_data)
json_str = json.dumps(esa_dict, indent=2, ensure_ascii=False)
out.write_text(json_str, encoding="utf-8")
output_info = {"format": "esa_sysnova_json", "output_file": str(out), "size_bytes": out.stat().st_size}
elif portal == "eu_funding":
json_ld = filler.export_eu_funding(app_data)
out.write_text(json_ld, encoding="utf-8")
output_info = {"format": "eu_funding_jsonld", "output_file": str(out), "size_bytes": out.stat().st_size}
else:
return TaskResult(
task_id=params.get("_task_id", "ffg_form"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=f"Unknown portal: {portal}",
)
else:
# Kein Output-Pfad — nur validieren
errors = filler.validate_form(app_data, portal)
output_info = {"format": portal, "validation_errors": len(errors), "errors": errors}
duration = (time.monotonic() - start) * 1000
result_sha = hashlib.sha256(
json.dumps(app_data["project"], sort_keys=True, ensure_ascii=False).encode()
).hexdigest()
self.audit.record("form_exported", {"application": application_path, "output": output_path or "validation_only", "portal": portal, "sha256": result_sha})
self.epistemic.add_belief(
f"Form exported: {application_path} -> {output_path or 'validation'} ({portal})",
EpistemicStatus.CERTAIN, 0.9, source="execution_engine", ttl_hours=720,
)
return TaskResult(
task_id=params.get("_task_id", "ffg_form"),
task_type=self.ACTION_TYPE,
status=TaskStatus.COMPLETED,
output=output_info,
sha256=result_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
except Exception as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("form_export_failed", {"application": application_path, "error": str(exc)})
return TaskResult(
task_id=params.get("_task_id", "ffg_form"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=f"{exc}\n{traceback.format_exc()}",
duration_ms=duration,
)
class QueryESAAPIHandler(ActionHandler):
"""Führt ESA API Query tatsächlich aus."""
ACTION_TYPE = "query_esa_api"
def execute(self, params: Dict[str, Any]) -> TaskResult:
backend = params.get("backend", "openeo-dev.vito.be")
save_path = params.get("save", "")
start = time.monotonic()
try:
# HTTP-Anfrage ohne externe Abhaengigkeiten
from urllib.request import Request, urlopen
import ssl
# SSL-Kontext: Erst mit verify, bei Fallback ohne
ssl_context = ssl.create_default_context()
use_ssl_verify = True
url = f"https://{backend}/openeo/1.2"
req = Request(url, headers={"User-Agent": "ORION-DDGK-ExecutionEngine/1.0"})
try:
with urlopen(req, timeout=20, context=ssl_context) as resp:
backend_data = json.loads(resp.read())
except Exception as ssl_exc:
if "CERTIFICATE_VERIFY_FAILED" in str(ssl_exc):
logger.warning("SSL verify failed for %s, retrying without verification", backend)
ssl_context = ssl._create_unverified_context()
use_ssl_verify = False
with urlopen(req, timeout=20, context=ssl_context) as resp:
backend_data = json.loads(resp.read())
else:
raise
# Collections abfragen
coll_url = f"https://{backend}/openeo/1.2/collections"
coll_req = Request(coll_url, headers={"User-Agent": "ORION-DDGK-ExecutionEngine/1.0"})
with urlopen(coll_req, timeout=20, context=ssl_context) as resp:
coll_data = json.loads(resp.read())
collections = coll_data.get("collections", [])
result_data = {
"backend": backend,
"backend_info": backend_data,
"collections_count": len(collections),
"collection_ids": [c.get("id", "") for c in collections[:20]],
"ssl_verified": use_ssl_verify,
"queried_at": datetime.now(timezone.utc).isoformat(),
}
if save_path:
out = Path(save_path)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(result_data, indent=2, ensure_ascii=False), encoding="utf-8")
result_data["saved_to"] = str(out)
duration = (time.monotonic() - start) * 1000
result_sha = hashlib.sha256(json.dumps(result_data, sort_keys=True).encode()).hexdigest()
self.audit.record("esa_api_queried", {"backend": backend, "collections": len(collections), "sha256": result_sha})
self.epistemic.add_belief(
f"ESA API queried: {backend}{len(collections)} collections",
EpistemicStatus.CERTAIN, 0.9, source="execution_engine", ttl_hours=24,
)
return TaskResult(
task_id=params.get("_task_id", "esa_api"),
task_type=self.ACTION_TYPE,
status=TaskStatus.COMPLETED,
output=result_data,
sha256=result_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
except Exception as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("esa_api_failed", {"backend": backend, "error": str(exc)})
return TaskResult(
task_id=params.get("_task_id", "esa_api"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=str(exc),
output={"backend": backend, "queried_at": datetime.now(timezone.utc).isoformat()},
duration_ms=duration,
)
class QueryCERNAPIHandler(ActionHandler):
"""Führt CERN API Query tatsächlich aus."""
ACTION_TYPE = "query_cern_api"
def execute(self, params: Dict[str, Any]) -> TaskResult:
save_path = params.get("save", "")
query = params.get("query", "")
size = params.get("size", 10)
start = time.monotonic()
try:
from urllib.request import Request, urlopen
url = f"https://opendata.cern.ch/api/records/?size={size}"
if query:
url += f"&q={query}"
req = Request(url, headers={"User-Agent": "ORION-DDGK-ExecutionEngine/1.0"})
with urlopen(req, timeout=20) as resp:
data = json.loads(resp.read())
hits = data.get("hits", {})
records = hits.get("hits", [])
total = hits.get("total", 0)
record_summaries = []
for rec in records[:size]:
md = rec.get("metadata", {})
record_summaries.append({
"id": rec.get("id", ""),
"title": md.get("title", "Untitled"),
"collection": md.get("collection", md.get("collections", "")),
"experiment": md.get("experiment", ""),
})
result_data = {
"query": query or "all",
"records_returned": len(records),
"records_total": total,
"records": record_summaries,
"queried_at": datetime.now(timezone.utc).isoformat(),
}
if save_path:
out = Path(save_path)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(result_data, indent=2, ensure_ascii=False), encoding="utf-8")
result_data["saved_to"] = str(out)
duration = (time.monotonic() - start) * 1000
result_sha = hashlib.sha256(json.dumps(result_data, sort_keys=True).encode()).hexdigest()
self.audit.record("cern_api_queried", {"query": query, "records": len(records), "sha256": result_sha})
self.epistemic.add_belief(
f"CERN API queried: {len(records)} records (total: {total})",
EpistemicStatus.CERTAIN, 0.9, source="execution_engine", ttl_hours=24,
)
return TaskResult(
task_id=params.get("_task_id", "cern_api"),
task_type=self.ACTION_TYPE,
status=TaskStatus.COMPLETED,
output=result_data,
sha256=result_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
except Exception as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("cern_api_failed", {"query": query, "error": str(exc)})
return TaskResult(
task_id=params.get("_task_id", "cern_api"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=str(exc),
output={"query": query, "queried_at": datetime.now(timezone.utc).isoformat()},
duration_ms=duration,
)
class RunGuardianCheckHandler(ActionHandler):
"""Führt Guardian Risk-Check tatsächlich aus."""
ACTION_TYPE = "run_guardian_check"
def execute(self, params: Dict[str, Any]) -> TaskResult:
from orion_guardian_v2 import ORIONGuardianV2, AssessmentContext
target = params.get("target", "all_forms")
check_actions = params.get("actions", [])
start = time.monotonic()
guardian = ORIONGuardianV2()
# Wenn keine konkreten Aktionen gegeben, Standard-Checks
if not check_actions:
if target == "all_forms":
check_actions = [
{"action": "export_ffg_ecall", "tool": "FormFiller", "args": {"portal": "ffg_ecall"}},
{"action": "export_esa_sysnova", "tool": "FormFiller", "args": {"portal": "esa_sysnova"}},
{"action": "export_eu_funding", "tool": "FormFiller", "args": {"portal": "eu_funding"}},
]
elif target == "api_calls":
check_actions = [
{"action": "query_esa_api", "tool": "ESAAdapter", "args": {"backend": "openeo-dev.vito.be"}},
{"action": "query_cern_api", "tool": "CERNAdapter", "args": {}},
]
elif target == "email":
check_actions = [
{"action": "send_email_via_smtp", "tool": "SMTP", "args": {"host": os.getenv("SMTP_HOST", "")}},
]
else:
check_actions = [
{"action": f"guardian_check_target_{target}", "tool": "generic", "args": {}},
]
assessments = []
overall_risk = 0
for ca in check_actions:
ctx = AssessmentContext(
action=ca.get("action", ""),
tool_name=ca.get("tool", ""),
tool_args=ca.get("args", {}),
user_approved=params.get("user_approved", False),
)
assessment = guardian.assess(ctx)
assessments.append({
"action": ca.get("action", ""),
"score": assessment.score,
"level": assessment.level,
"decision": assessment.decision,
"reasons": assessment.reasons,
"chain_hash": assessment.chain_hash,
})
overall_risk = max(overall_risk, assessment.score)
duration = (time.monotonic() - start) * 1000
result_sha = hashlib.sha256(
json.dumps(assessments, sort_keys=True).encode()
).hexdigest()
self.audit.record("guardian_check", {"target": target, "overall_risk": overall_risk, "checks": len(assessments), "sha256": result_sha})
self.epistemic.add_belief(
f"Guardian check completed: target={target}, risk={overall_risk}, checks={len(assessments)}",
EpistemicStatus.CERTAIN, 0.95, source="execution_engine", ttl_hours=168,
)
return TaskResult(
task_id=params.get("_task_id", "guardian"),
task_type=self.ACTION_TYPE,
status=TaskStatus.COMPLETED,
output={
"target": target,
"overall_risk_score": overall_risk,
"assessments": assessments,
"checks_run": len(assessments),
},
sha256=result_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
class WriteFileHandler(ActionHandler):
"""Schreibt Dateien tatsächlich."""
ACTION_TYPE = "write_file"
def execute(self, params: Dict[str, Any]) -> TaskResult:
file_path = params.get("path", "")
content = params.get("content", "")
encoding = params.get("encoding", "utf-8")
mode = params.get("mode", "write") # write | append
if not file_path:
return TaskResult(
task_id=params.get("_task_id", "write_file"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error="Missing 'path' parameter",
)
start = time.monotonic()
try:
out = Path(file_path)
out.parent.mkdir(parents=True, exist_ok=True)
if mode == "append" and out.exists():
out.write_text(out.read_text(encoding=encoding) + content, encoding=encoding)
else:
out.write_text(content, encoding=encoding)
duration = (time.monotonic() - start) * 1000
file_sha = hashlib.sha256(content.encode(encoding)).hexdigest()
self.audit.record("file_written", {"path": str(out), "size": out.stat().st_size, "sha256": file_sha})
self.epistemic.add_belief(
f"File written: {file_path} ({out.stat().st_size} bytes)",
EpistemicStatus.CERTAIN, 0.95, source="execution_engine",
)
return TaskResult(
task_id=params.get("_task_id", "write_file"),
task_type=self.ACTION_TYPE,
status=TaskStatus.COMPLETED,
output={"path": str(out), "size_bytes": out.stat().st_size, "mode": mode},
sha256=file_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
except Exception as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("file_write_failed", {"path": file_path, "error": str(exc)})
return TaskResult(
task_id=params.get("_task_id", "write_file"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=str(exc),
duration_ms=duration,
)
class RunScriptHandler(ActionHandler):
"""Führt Python-Skripte tatsächlich aus."""
ACTION_TYPE = "run_script"
def execute(self, params: Dict[str, Any]) -> TaskResult:
script = params.get("script", "")
args = params.get("args", [])
cwd = params.get("cwd", str(ROOT))
capture_output = params.get("capture_output", True)
timeout = params.get("timeout", 300)
if not script:
return TaskResult(
task_id=params.get("_task_id", "run_script"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error="Missing 'script' parameter",
)
start = time.monotonic()
try:
cmd = [sys.executable, script] + args
result = subprocess.run(
cmd,
cwd=cwd,
capture_output=capture_output,
text=True,
timeout=timeout,
)
duration = (time.monotonic() - start) * 1000
result_sha = hashlib.sha256(
json.dumps({"script": script, "returncode": result.returncode}, sort_keys=True).encode()
).hexdigest()
output_data = {
"script": script,
"returncode": result.returncode,
"stdout": result.stdout[-5000:] if capture_output and result.stdout else "",
"stderr": result.stderr[-5000:] if capture_output and result.stderr else "",
}
status = TaskStatus.COMPLETED if result.returncode == 0 else TaskStatus.FAILED
self.audit.record(
"script_executed",
{"script": script, "returncode": result.returncode, "sha256": result_sha},
)
self.epistemic.add_belief(
f"Script executed: {script} (rc={result.returncode})",
EpistemicStatus.CERTAIN if result.returncode == 0 else EpistemicStatus.LIKELY,
0.9 if result.returncode == 0 else 0.5,
source="execution_engine",
)
return TaskResult(
task_id=params.get("_task_id", "run_script"),
task_type=self.ACTION_TYPE,
status=status,
output=output_data,
sha256=result_sha,
epistemic_state=EpistemicActionState.KNOWN.value,
duration_ms=duration,
)
except subprocess.TimeoutExpired as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("script_timeout", {"script": script, "timeout": timeout})
return TaskResult(
task_id=params.get("_task_id", "run_script"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=f"Timeout after {timeout}s: {exc}",
duration_ms=duration,
)
except Exception as exc:
duration = (time.monotonic() - start) * 1000
self.audit.record("script_failed", {"script": script, "error": str(exc)})
return TaskResult(
task_id=params.get("_task_id", "run_script"),
task_type=self.ACTION_TYPE,
status=TaskStatus.FAILED,
error=str(exc),
duration_ms=duration,
)
# ─── TASK EXECUTOR ────────────────────────────────────────────────────────────
HANDLER_REGISTRY: Dict[str, type] = {
SendEmailHandler.ACTION_TYPE: SendEmailHandler,
ExportFFGFormHandler.ACTION_TYPE: ExportFFGFormHandler,
QueryESAAPIHandler.ACTION_TYPE: QueryESAAPIHandler,
QueryCERNAPIHandler.ACTION_TYPE: QueryCERNAPIHandler,
RunGuardianCheckHandler.ACTION_TYPE: RunGuardianCheckHandler,
WriteFileHandler.ACTION_TYPE: WriteFileHandler,
RunScriptHandler.ACTION_TYPE: RunScriptHandler,
}
class TaskExecutor:
"""
Haupt-Executor: Liest Task-Queue, fuehrt Tasks aus, schreibt Audit.
"""
def __init__(
self,
queue_path: Optional[str | Path] = None,
audit_chain: Optional[AuditChain] = None,
epistemic: Optional[EpistemicState] = None,
agent_id: int = 1,
agent_name: str = "TaskExecutor",
):
self.queue_path = Path(queue_path) if queue_path else ROOT / "autonomous" / "task_queue.json"
self.agent_id = agent_id
self.agent_name = agent_name
self.audit = audit_chain or AuditChain(agent_id=agent_id, agent_name=agent_name)
self.epistemic = epistemic or EpistemicState(agent_id=agent_id, agent_name=agent_name)
self.handlers: Dict[str, ActionHandler] = {}
self.results: List[TaskResult] = []
# Handler registrieren
for action_type, handler_cls in HANDLER_REGISTRY.items():
self.handlers[action_type] = handler_cls(self.audit, self.epistemic)
# ── Queue I/O ─────────────────────────────────────────────────────────
def load_queue(self) -> List[Dict[str, Any]]:
"""Laedt die Task-Queue aus JSON."""
if not self.queue_path.exists():
raise FileNotFoundError(f"Task queue not found: {self.queue_path}")
data = json.loads(self.queue_path.read_text(encoding="utf-8"))
if isinstance(data, dict):
tasks = data.get("tasks", [])
elif isinstance(data, list):
tasks = data
else:
raise ValueError(f"Invalid queue format: expected list or dict with 'tasks'")
return tasks
def save_queue(self, tasks: List[Dict[str, Any]]) -> None:
"""Speichert die aktualisierte Task-Queue."""
queue_data = {
"version": "1.0.0",
"updated_at": datetime.now(timezone.utc).isoformat(),
"total_tasks": len(tasks),
"tasks": tasks,
}
self.queue_path.write_text(
json.dumps(queue_data, indent=2, ensure_ascii=False), encoding="utf-8"
)
# ── Execution ─────────────────────────────────────────────────────────
def execute_task(self, task: Dict[str, Any]) -> TaskResult:
"""Fuehrt einen einzelnen Task aus."""
task_type = task.get("type", task.get("action", ""))
params = task.get("params", task.get("args", task.get("config", {})))
task_id = task.get("id", task.get("task_id", f"task_{int(time.time())}"))
# Task-ID in Params speichern
params["_task_id"] = task_id
ts_start = datetime.now(timezone.utc).isoformat()
# Status auf RUNNING setzen
task["status"] = TaskStatus.RUNNING.value
task["started_at"] = ts_start
handler = self.handlers.get(task_type)
if not handler:
result = TaskResult(
task_id=task_id,
task_type=task_type,
status=TaskStatus.FAILED,
error=f"Unknown task type: {task_type}. Available: {list(self.handlers.keys())}",
started_at=ts_start,
completed_at=datetime.now(timezone.utc).isoformat(),
)
else:
result = handler.execute(params)
result.started_at = ts_start
result.completed_at = datetime.now(timezone.utc).isoformat()
# Task in Queue aktualisieren
task["status"] = result.status.value
task["result"] = result.to_dict()
task["completed_at"] = result.completed_at
self.results.append(result)
# Audit
self.audit.record(
f"task_{result.status.value}",
{"task_id": task_id, "type": task_type, "sha256": result.sha256},
)
return result
def execute_all(self, stop_on_error: bool = False) -> List[TaskResult]:
"""Fuehrt alle Tasks in der Queue aus."""
tasks = self.load_queue()
# Sicherstellen dass jeder Task einen ID hat
for i, task in enumerate(tasks):
if "id" not in task and "task_id" not in task:
task["id"] = f"task_{i:03d}_{task.get('type', task.get('action', 'unknown'))}"
if "status" not in task:
task["status"] = TaskStatus.PENDING.value
print(f"\n{'='*60}")
print(f" EXECUTION PIPELINE START")
print(f" Tasks: {len(tasks)}")
print(f" Queue: {self.queue_path}")
print(f"{'='*60}\n")
for i, task in enumerate(tasks):
task_id = task.get("id", task.get("task_id", "?"))
task_type = task.get("type", task.get("action", "?"))
status = task.get("status", TaskStatus.PENDING.value)
# Ueberspringe bereits erledigte Tasks
if status in (TaskStatus.COMPLETED.value, TaskStatus.SKIPPED.value):
print(f" [{i+1}/{len(tasks)}] SKIP {task_id} ({task_type}) — already {status}")
continue
print(f" [{i+1}/{len(tasks)}] EXEC {task_id} ({task_type}) ...", end=" ", flush=True)
result = self.execute_task(task)
icon = {
TaskStatus.COMPLETED: "OK",
TaskStatus.FAILED: "FAIL",
TaskStatus.SKIPPED: "SKIP",
TaskStatus.RUNNING: "RUN",
TaskStatus.PENDING: "PEND",
}.get(result.status, "??")
print(f"[{icon}] {result.duration_ms:.0f}ms")
if result.error:
print(f" ERROR: {result.error[:120]}")
if result.sha256:
print(f" SHA-256: {result.sha256[:32]}...")
if result.status == TaskStatus.FAILED and stop_on_error:
print(f"\n STOP on error at task {task_id}")
break
# Queue aktualisieren
self.save_queue(tasks)
# Zusammenfassung
completed = sum(1 for r in self.results if r.status == TaskStatus.COMPLETED)
failed = sum(1 for r in self.results if r.status == TaskStatus.FAILED)
skipped = sum(1 for r in self.results if r.status == TaskStatus.SKIPPED)
print(f"\n{'='*60}")
print(f" EXECUTION PIPELINE END")
print(f" Completed: {completed} | Failed: {failed} | Skipped: {skipped}")
print(f" Audit entries: {self.audit.count()}")
print(f" Epistemic beliefs: {len(self.epistemic.beliefs)}")
print(f"{'='*60}\n")
# Epistemic State speichern
epistemic_path = ROOT / "autonomous" / "epistemic_executor.json"
self.epistemic.save(epistemic_path)
return self.results
def execute_single(self, task_id: str) -> TaskResult:
"""Fuehrt einen spezifischen Task aus der Queue aus."""
tasks = self.load_queue()
for task in tasks:
tid = task.get("id", task.get("task_id", ""))
if tid == task_id:
return self.execute_task(task)
raise ValueError(f"Task not found: {task_id}")
def get_report(self) -> Dict[str, Any]:
"""Erstellt Ausfuehrungsbericht."""
return {
"executed_at": datetime.now(timezone.utc).isoformat(),
"total_tasks": len(self.results),
"completed": sum(1 for r in self.results if r.status == TaskStatus.COMPLETED),
"failed": sum(1 for r in self.results if r.status == TaskStatus.FAILED),
"skipped": sum(1 for r in self.results if r.status == TaskStatus.SKIPPED),
"results": [r.to_dict() for r in self.results],
"audit_entries": self.audit.count(),
"epistemic_beliefs": len(self.epistemic.beliefs),
"audit_integrity": self.audit.verify_chain()[0],
}