From d5ce0227f4e993ba6996456dae53605b05a5757c Mon Sep 17 00:00:00 2001 From: chrischristiansen-glitch Date: Tue, 26 May 2026 01:13:19 +0200 Subject: [PATCH] =?UTF-8?q?feat(ml):=20add=20ML=20layer=20=E2=80=94=20Dask?= =?UTF-8?q?=20DAG,=20Parameter=20Server=20state,=20telemetry,=20AGENTS.md?= =?UTF-8?q?=20[ML-1]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 91 ++++++++++++++++++++++++++++++ docs/ml-integration.md | 123 ++++++++++++++++++++++++++++++++++++++++ ml/README.md | 76 +++++++++++++++++++++++++ ml/__init__.py | 14 +++++ ml/state_store.py | 124 +++++++++++++++++++++++++++++++++++++++++ ml/task_graph.py | 90 ++++++++++++++++++++++++++++++ ml/telemetry.py | 98 ++++++++++++++++++++++++++++++++ requirements.txt | 12 ++++ 8 files changed, 628 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/ml-integration.md create mode 100644 ml/README.md create mode 100644 ml/__init__.py create mode 100644 ml/state_store.py create mode 100644 ml/task_graph.py create mode 100644 ml/telemetry.py diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..222c122 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,91 @@ +# AGENTS.md — ML-roller i Vauco OS + +**Sist oppdatert:** 2026-05-26 +**Formål:** Definerer hver agents rolle i ML-laget (Parameter Server-mønster + Dask DAG). + +--- + +## Oversikt + +``` + ┌─────────────────────────────┐ + │ OPAX Orchestrator │ + │ (PS Server-node / DAG hub) │ + └──────────┬──────────────────┘ + │ + ┌──────────┬──────────┼──────────┬──────────┐ + ▼ ▼ ▼ ▼ ▼ + billing- rag-agent memory- tui- multi- + agent agent bridge agent + (worker) (worker) (worker) (worker+ (worker) + input-src) +``` + +--- + +## Agenttabell + +| Agent | Katalog | PS-rolle | Dask-rolle | Online learning | Heavy mode | +|---|---|---|---|---|---| +| OPAX Orchestrator | `agents/core-logic/` | **Server-node** (aggregerer state, dispatcher DAG) | DAG scheduler | Nei | Ja (koordinerer heavy sub-agenter) | +| billing-agent | `agents/core-logic/` | Worker (pusher kostdata til state store) | `dask.delayed`-task | Nei — strukturert data | Nei | +| rag-agent | `agents/rag/` | Worker (pusher retrieval-resultater) | `dask.delayed`-task | **Ja** — `partial_fit` på nye docs (ML-2) | Ja | +| memory-agent | `agents/memory/` | Worker (pusher/puller memory-oppslag) | `dask.delayed`-task | Nei — faktahenting | Nei | +| eval-agent | `agents/eval/` | Worker (pusher evalueringsmetrikker) | `dask.delayed`-task | **Ja** — kvalitetsscore-feedback (ML-2) | Nei | +| multi-agent | `agents/multi_agent/` | Worker + sub-orchestrator | DAG-node med barn | Nei | **Ja — primær heavy-mode bruker** | +| tui-bridge | `vauco-gemini-tui-bridge` | Worker + **primær input-kilde** | Streaming input til DAG | **Ja** — real-time feedback-loop (ML-2) | Ja | +| vauco-bootstrap | `vauco-bootstrap` | **Config-aggregator** (analog til PS shard) | Trigger for nye DAGer | **Ja** — ASHA-tuning (ML-2) | Nei | + +--- + +## ML-fase per agent + +| Agent | ML-1 (nå) | ML-2 (neste) | ML-3 (fremtidig) | +|---|---|---|---| +| OPAX Orchestrator | Kjør DAG via `execute_dag()` | Bytt heavy-mode til ML-predictor | PBT-eksperiment | +| billing-agent | `log_agent_call()` + `state.push()` | — | Feature importance-input | +| rag-agent | `log_agent_call()` + `state.push()` | `partial_fit` på nye docs | Top-K feature importance | +| memory-agent | `log_agent_call()` | — | — | +| eval-agent | `log_agent_call()` + `state.push()` | Kvalitetsscore-feedback | Feature importance-target | +| multi-agent | `log_agent_call()` | — | PBT worker | +| tui-bridge | `log_agent_call()` | `feedback_loop.py` (ML-2.1) | — | +| vauco-bootstrap | — | `hypertuner.py` ASHA (ML-2.3) | PBT vinner-konfig → Terraform | + +--- + +## State store nøkkelkonvensjon + +Format: `":"` + +| Agent | Nøkkel | Eksempelverdi | +|---|---|---| +| `billing-agent` | `last_cost_usd` | `12.5` | +| `billing-agent` | `last_report_ts` | `"2026-05-26T01:00:00Z"` | +| `rag-agent` | `last_retrieval_score` | `0.87` | +| `rag-agent` | `chunks_retrieved` | `5` | +| `eval-agent` | `quality_score` | `0.91` | +| `multi-agent` | `active_sub_agents` | `["billing-agent", "rag-agent"]` | +| `tui-bridge` | `last_user_feedback` | `"positive"` | + +--- + +## Telemetri-felter (referanse) + +Alle agenter logger via `ml.telemetry.log_agent_call()`. Feltene er: + +```json +{ + "ts": "2026-05-26T01:00:00.000Z", + "agent": "billing-agent", + "model": "gemini-2.0-flash", + "mode": "standard", + "duration_s": 0.312, + "success": true, + "error": null, + "input_tokens": 284, + "output_tokens": 512 +} +``` + +Filer lagres i: `$TELEMETRY_DIR/YYYY-MM-DD.jsonl` (default: `/tmp/vauco_telemetry/`) +For GCS-arkivering: sett `TELEMETRY_DIR=gs://...` eller bruk `10-cost-guard.sh`-cron. diff --git a/docs/ml-integration.md b/docs/ml-integration.md new file mode 100644 index 0000000..b4b8515 --- /dev/null +++ b/docs/ml-integration.md @@ -0,0 +1,123 @@ +# ML-integrasjon i OSVauco + +**Fase:** ML-1 implementert | ML-2 og ML-3 planlagt +**Sist oppdatert:** 2026-05-26 + +--- + +## Hvorfor ML i OPAX? + +OPAX er et multi-agent system. ML-laget gjør agentene sterkere på tre måter: + +1. **Parallell kjøring** (ML-1): Dask DAG lar agenter kjøre samtidig i stedet for sekvensielt. +2. **Adaptiv intelligens** (ML-2): Agentene lærer fra faktisk bruk — heavy-mode aktiveres basert på historikk, ikke hardkodede regler. +3. **Strategisk innsikt** (ML-3): XGBoost feature importance avslører hvilke agenter og signaler som faktisk driver vellykkede utfall. + +--- + +## Fase ML-1: Grunnlag (implementert) + +### Arkitektur + +``` +main.py → build_agent_dag([agent_a, agent_b], payload) + │ + Dask task graph + │ + ┌──────────┴──────────┐ + ▼ ▼ + run_agent_task(a) run_agent_task(b) ← parallelt + │ │ + └──────────┬──────────┘ + ▼ + execute_dag(tasks, scheduler="threads") + │ + ▼ + [result_a, result_b] → AgentStateStore.push() + │ + telemetry.log_agent_call() +``` + +### Koble ML-laget inn i main.py + +```python +# Eksempel: erstatt sekvensielle kall med parallell DAG +from ml import build_agent_dag, execute_dag, get_store, log_agent_call +import time + +@app.post("/run") +def run_agent(req: RunRequest): + store = get_store() + payload = {"message": req.message, "user_id": req.user_id, + "session_id": req.session_id, "mode": req.mode} + + # Velg agenter basert på mode + if req.mode == "heavy": + agent_fns = [billing_agent_heavy, rag_agent, multi_agent] + else: + agent_fns = [billing_agent, rag_agent] + + start = time.monotonic() + tasks = build_agent_dag(agent_fns, payload) + results = execute_dag(tasks, scheduler="threads") + total_dur = time.monotonic() - start + + # Push resultater til state store + for r in results: + if r["success"]: + store.push(r["agent"], "last_result", r["result"]) + store.push(r["agent"], "last_duration_s", r["duration_s"]) + + # Logg DAG-kjøring + from ml.telemetry import log_dag_execution + log_dag_execution(dag_id=req.session_id, agent_results=results, + total_duration_s=total_dur) + + # Returner kombinert svar + return {"response": [r["result"] for r in results if r["success"]]} +``` + +--- + +## Fase ML-2: Adaptiv intelligens (planlagt) + +| Komponent | Fil | Repo | +|---|---|---| +| Inkrementell feedback-loop | `ml/feedback_loop.py` | `vauco-gemini-tui-bridge` | +| ASHA hyperparametertuning | `ml/hypertuner.py` | `vauco-bootstrap` | +| Heavy-mode predictor | `ml/heavy_predictor.py` | `OSVauco` | + +**Krav:** `dask-ml>=2024.4.0`, `ray[tune]>=2.10.0` (vauco-bootstrap only) + +--- + +## Fase ML-3: Strategisk innsikt (planlagt) + +| Komponent | Fil | Repo | +|---|---|---| +| XGBoost feature importance | `ml/agent_importance.py` | `OSVauco` | +| Population-Based Training | `experiments/pbt.py` | `deep-dream` | + +**Krav:** `xgboost>=2.0.0` (legg til requirements.txt i Fase ML-3) + +--- + +## Telemetri-ruting + +| Miljø | `TELEMETRY_DIR` | Arkivering | +|---|---|---| +| Lokal dev | `/tmp/vauco_telemetry/` | Manuell | +| Cloud Run | `/tmp/vauco_telemetry/` | `10-cost-guard.sh` cron → GCS | +| Prod skala | `gs://propane-will-491900-m5-telemetry/` | Auto via GCS sink | + +--- + +## Cross-repo referanser + +| Repo | ML-relatert fil | Fase | +|---|---|---| +| `OSVauco` | `ml/`, `AGENTS.md`, `docs/ml-integration.md` | ML-1 | +| `vauco-gemini-tui-bridge` | `ml/feedback_loop.py` (planlagt) | ML-2 | +| `vauco-bootstrap` | `ml/hypertuner.py` (planlagt) | ML-2 | +| `vauco-os` | Konsumerer beste konfig fra state store | ML-2 | +| `deep-dream` | `experiments/pbt.py` (planlagt) | ML-3 | diff --git a/ml/README.md b/ml/README.md new file mode 100644 index 0000000..8354c2f --- /dev/null +++ b/ml/README.md @@ -0,0 +1,76 @@ +# OSVauco ML Layer + +ML-laget gir OPAX-agentene parallell kjøring, delt state-kontroll og strukturert +telemetri. Implementert i tre faser. + +## Arkitektur + +``` +OPAX /run-endepunkt + │ + ▼ + build_agent_dag() ← Dask DAG (task_graph.py) + │ + ┌────┴────┬──────────────┐ + ▼ ▼ ▼ +agent_A agent_B ... agent_N ← kjøres parallelt + │ │ │ + └────┬────┴──────────────┘ + ▼ + AgentStateStore ← Parameter Server state (state_store.py) + │ + ▼ + telemetry JSONL ← rådatagrunnlag for ML-2 og ML-3 +``` + +## Filer + +| Fil | Ansvar | +|---|---| +| `task_graph.py` | Dask DAG — parallell agent-scheduling | +| `state_store.py` | Thread-safe PS-inspirert state store | +| `telemetry.py` | Strukturert JSONL-logging per agent-kall | +| `__init__.py` | Pakke-eksporter | + +## Scheduler-valg + +| Miljø | `scheduler`-parameter | +|---|---| +| Dev / unit-test | `'synchronous'` | +| Lokal multi-tråd | `'threads'` | +| Cloud Run (lett) | `'threads'` | +| Full skala | `'distributed'` (krever Dask cluster) | + +## Fase-oversikt + +| Fase | Innhold | Status | +|---|---|---| +| ML-1 | Dask DAG + PS state + telemetri | ✅ Implementert | +| ML-2 | Inkrementell læring (dask-ml) + ASHA-tuning (Ray Tune) | 🔜 Neste | +| ML-3 | XGBoost feature importance + PBT | 🔜 Fremtidig | + +## Bruk + +```python +from ml import build_agent_dag, execute_dag, get_store, log_agent_call + +# Bygg og kjør DAG +tasks = build_agent_dag([billing_agent, rag_agent], payload) +results = execute_dag(tasks, scheduler="threads") + +# State store +store = get_store() +store.push("billing-agent", "last_cost_usd", 12.5) +cost = store.pull("billing-agent", "last_cost_usd") + +# Telemetri +log_agent_call( + agent_id="billing-agent", + input_payload=payload, + output=result, + model_used="gemini-2.0-flash", + mode="standard", + duration_s=0.4, + success=True, +) +``` diff --git a/ml/__init__.py b/ml/__init__.py new file mode 100644 index 0000000..057ed72 --- /dev/null +++ b/ml/__init__.py @@ -0,0 +1,14 @@ +# OSVauco ML layer +# Fase ML-1: Dask DAG scheduling, Parameter Server-inspirert state, telemetri +from .task_graph import build_agent_dag, execute_dag, run_agent_task +from .state_store import AgentStateStore, get_store +from .telemetry import log_agent_call + +__all__ = [ + "build_agent_dag", + "execute_dag", + "run_agent_task", + "AgentStateStore", + "get_store", + "log_agent_call", +] diff --git a/ml/state_store.py b/ml/state_store.py new file mode 100644 index 0000000..a9dc79f --- /dev/null +++ b/ml/state_store.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +ml/state_store.py — Parameter Server-inspirert state store for OPAX-agenter. + +Konsept (fra ML-dokumentet): + Parameter Server (PS) skiller treningsarbeid fra modelltilstand. + Server-noder vedlikeholder global state (nøkkel-verdi-store). + Worker-noder utfører pull (les parametere) og push (send gradienter). + +OPAX-mapping: + AgentStateStore = server-node (global OPAX-tilstand) + Agenter (workers) = push resultater, pull delt konteksttilstand + aggregate() = analog til gradient-aggregering på PS-server + snapshot() = analog til periodisk checkpointing +""" + +import threading +from datetime import datetime, timezone +from typing import Any, Dict, List, Optional + + +class AgentStateStore: + """ + Thread-safe nøkkel-verdi-store delt av alle OPAX-agenter. + + Nøkkelformat: ":" (f.eks. "billing-agent:last_cost_usd") + Historikk: Appendonly-log for telemetri og replay. + """ + + def __init__(self) -> None: + self._store: Dict[str, Any] = {} + self._lock = threading.RLock() + self._history: List[Dict[str, Any]] = [] + + # ------------------------------------------------------------------ # + # Worker-operasjoner # + # ------------------------------------------------------------------ # + + def push(self, agent_id: str, key: str, value: Any) -> None: + """ + Agent pusher en oppdatering til global state. + Analog til PS worker-push av gradienter til server. + """ + with self._lock: + full_key = f"{agent_id}:{key}" + self._store[full_key] = value + self._history.append({ + "op": "push", + "agent": agent_id, + "key": key, + "ts": datetime.now(timezone.utc).isoformat(), + }) + + def pull(self, agent_id: str, key: str) -> Optional[Any]: + """ + Agent puller gjeldende verdi fra global state. + Analog til PS worker-pull av parametere fra server. + """ + with self._lock: + return self._store.get(f"{agent_id}:{key}") + + def pull_any(self, key: str) -> Optional[Any]: + """ + Pull første treff på key uavhengig av agent_id. + Nyttig for delt konfigurasjon (f.eks. "*:active_model"). + """ + with self._lock: + for k, v in self._store.items(): + if k.endswith(f":{key}"): + return v + return None + + # ------------------------------------------------------------------ # + # Server-operasjoner (aggregering og inspeksjon) # + # ------------------------------------------------------------------ # + + def aggregate(self, key_pattern: str) -> Dict[str, Any]: + """ + Aggregerer alle verdier som matcher nøkkelmønsteret på tvers av agenter. + Analog til PS-serverens gradient-aggregering over alle workers. + + Eksempel: + store.aggregate("last_duration_s") + → {"billing-agent:last_duration_s": 0.3, "rag-agent:last_duration_s": 1.2} + """ + with self._lock: + return { + k: v + for k, v in self._store.items() + if k.endswith(f":{key_pattern}") + } + + def list_agents(self) -> List[str]: + """Returnerer unike agent-IDer som har pushet noe.""" + with self._lock: + return list({k.split(":")[0] for k in self._store}) + + def snapshot(self) -> Dict[str, Any]: + """ + Full state-snapshot — brukes til checkpointing og debugging. + Analog til PS periodisk modell-checkpoint til distribuert filsystem. + """ + with self._lock: + return dict(self._store) + + def history(self, limit: int = 100) -> List[Dict[str, Any]]: + """Siste N operasjoner fra historikk-loggen.""" + with self._lock: + return list(self._history[-limit:]) + + def clear(self) -> None: + """Nullstill state (brukes i tester og teardown).""" + with self._lock: + self._store.clear() + self._history.clear() + + +# Singleton — deles av alle agenter i én prosess +_global_store = AgentStateStore() + + +def get_store() -> AgentStateStore: + """Returnerer den globale singleton-instansen av AgentStateStore.""" + return _global_store diff --git a/ml/task_graph.py b/ml/task_graph.py new file mode 100644 index 0000000..2c33714 --- /dev/null +++ b/ml/task_graph.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +""" +ml/task_graph.py — Dask DAG-basert agent-koordinering for OPAX. + +Konsept (fra ML-dokumentet): + Dask bygger en task graph (DAG) av operasjoner og avhengigheter. + Lazy evaluation: beregninger utføres kun når .compute() kalles. + Agent-funksjoner wrappes som @dask.delayed og kjøres parallelt. + +PS-analogi: + OPAX orchestrator = DAG scheduler (server-node) + Individuelle agenter = Dask-delayed tasks (worker-noder) +""" + +import time +from typing import Any, Callable, Dict, List, Optional + +import dask + + +@dask.delayed +def run_agent_task( + agent_fn: Callable, + payload: Dict[str, Any], + agent_id: Optional[str] = None, +) -> Dict[str, Any]: + """ + Wrapper: kjører én agent som en Dask-delayed task. + Returnerer resultat med metadata for telemetri og state store. + """ + _id = agent_id or getattr(agent_fn, "__name__", "unknown") + start = time.monotonic() + try: + result = agent_fn(payload) + success = True + error = None + except Exception as exc: # noqa: BLE001 + result = None + success = False + error = str(exc) + duration = round(time.monotonic() - start, 3) + return { + "agent": _id, + "result": result, + "success": success, + "error": error, + "duration_s": duration, + "timestamp": time.time(), + } + + +def build_agent_dag( + agent_fns: List[Callable], + payload: Dict[str, Any], + agent_ids: Optional[List[str]] = None, +) -> List: + """ + Bygger en liste av Dask-delayed tasks (én per agent). + Agenter uten avhengigheter kjøres parallelt av scheduleren. + + Args: + agent_fns: Liste av callable agentfunksjoner. + payload: Felles input-dict til alle agenter. + agent_ids: Valgfrie ID-er som matcher agent_fns (for logging). + + Returns: + Liste av Dask-delayed objects — klar for execute_dag(). + """ + ids = agent_ids or [getattr(fn, "__name__", f"agent_{i}") for i, fn in enumerate(agent_fns)] + return [run_agent_task(fn, payload, aid) for fn, aid in zip(agent_fns, ids)] + + +def execute_dag( + tasks: List, + scheduler: str = "synchronous", +) -> List[Dict[str, Any]]: + """ + Kjører den ferdigbygde DAGen. + + Scheduler-valg: + 'synchronous' — enkelt-tråd, ingen overhead (dev / unit-test) + 'threads' — lokal multi-tråd, GIL-vennlig for IO-bound agenter + 'processes' — multi-prosess, CPU-bound arbeid + 'distributed' — Dask cluster (full skala, krever dask[distributed]) + + Returns: + Liste av resultat-dicts fra run_agent_task. + """ + results = dask.compute(*tasks, scheduler=scheduler) + return list(results) diff --git a/ml/telemetry.py b/ml/telemetry.py new file mode 100644 index 0000000..c29a907 --- /dev/null +++ b/ml/telemetry.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +""" +ml/telemetry.py — Strukturert telemetri-innsamling for OPAX-agenter. + +Formål: + Loggfører hvert agent-kall til JSONL-filer. + Disse filene er rådata for Fase ML-2 (inkrementell læring) + og Fase ML-3 (XGBoost feature importance). + +Felter per record: + ts — ISO 8601 UTC tidsstempel + agent — agent-ID (f.eks. "billing-agent") + model — LLM-modell brukt (f.eks. "gemini-2.0-flash") + mode — "standard" eller "heavy" + duration_s — latens i sekunder + success — bool + error — feilmelding eller None + input_tokens — proxy: len(str(input)) + output_tokens — proxy: len(str(output)) +""" + +import json +import os +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Dict, Optional + +# Skriv til Cloud Run-vennlig sti — overstyr med TELEMETRY_DIR env var +_DEFAULT_DIR = os.environ.get("TELEMETRY_DIR", "/tmp/vauco_telemetry") + + +def log_agent_call( + agent_id: str, + input_payload: Dict[str, Any], + output: Any, + model_used: str, + mode: str, + duration_s: float, + success: bool, + error: Optional[str] = None, + telemetry_dir: Optional[str] = None, +) -> None: + """ + Logg ett agent-kall til JSONL-fil (én fil per dato). + + Filnavnformat: YYYY-MM-DD.jsonl + Trygt for parallell skriving (én linje per kall, atomisk append). + """ + base = Path(telemetry_dir or _DEFAULT_DIR) + base.mkdir(parents=True, exist_ok=True) + + record = { + "ts": datetime.now(timezone.utc).isoformat(), + "agent": agent_id, + "model": model_used, + "mode": mode, + "duration_s": round(duration_s, 3), + "success": success, + "error": error, + "input_tokens": len(str(input_payload)), + "output_tokens": len(str(output)), + } + + log_file = base / f"{datetime.now(timezone.utc).strftime('%Y-%m-%d')}.jsonl" + with open(log_file, "a", encoding="utf-8") as fh: + fh.write(json.dumps(record, ensure_ascii=False) + "\n") + + +def log_dag_execution( + dag_id: str, + agent_results: list, + total_duration_s: float, + telemetry_dir: Optional[str] = None, +) -> None: + """ + Logg ett DAG-kjøring (aggregert over alle agenter i DAGen). + Brukes av execute_dag() for å gi oversikt over parallelle kjøringer. + """ + base = Path(telemetry_dir or _DEFAULT_DIR) + base.mkdir(parents=True, exist_ok=True) + + record = { + "ts": datetime.now(timezone.utc).isoformat(), + "type": "dag_execution", + "dag_id": dag_id, + "agent_count": len(agent_results), + "success_count": sum(1 for r in agent_results if r.get("success")), + "total_duration_s": round(total_duration_s, 3), + "agents": [ + {"agent": r.get("agent"), "duration_s": r.get("duration_s"), "success": r.get("success")} + for r in agent_results + ], + } + + log_file = base / f"{datetime.now(timezone.utc).strftime('%Y-%m-%d')}.jsonl" + with open(log_file, "a", encoding="utf-8") as fh: + fh.write(json.dumps(record, ensure_ascii=False) + "\n") diff --git a/requirements.txt b/requirements.txt index 4c95319..fc03820 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,15 @@ fastapi>=0.111.0 # Utilities python-dotenv>=1.0.0 pydantic>=2.7.0 + +# ── ML Layer (Fase ML-1) ───────────────────────────────────────────────────── +# Dask: parallell task scheduling + lazy DAG execution +dask[distributed]>=2024.1.0 +# dask-ml: scikit-learn-kompatibel ML på Dask DataFrames/Arrays (Fase ML-2) +dask-ml>=2024.4.0 +# scikit-learn: base estimatorer med partial_fit for inkrementell læring +scikit-learn>=1.4.0 +# pandas: tabular telemetri-analyse +pandas>=2.0.0 +# XGBoost: distribuert feature importance (Fase ML-3) +# xgboost>=2.0.0 ← aktiver i Fase ML-3