feat(emma): full bootstrap — MDP, MCoT, Morphic Resonance, Flynn, Guardrails, Identity, Gitea, OPAX

This commit is contained in:
chrischristiansen-glitch 2026-06-28 12:51:26 +02:00
parent b042234078
commit 51f5624c26
16 changed files with 748 additions and 0 deletions

51
emma/README.md Normal file
View File

@ -0,0 +1,51 @@
# Emma — Vauco AS Intern AI-Agent
Emma er en intern strategisk AI-agent for Chris Christiansen @ Vauco AS.
Hun er **ikke** kundevendt (det er Jason). Emma håndterer:
- Strategisk planlegging for Vauco OS
- GCP-kostnadsanalyse og burn rate
- Teknisk arkitektur og dokumentasjon
## Arkitektur
```
Input
└─► MDP State Machine (emma_mdp.py)
└─► Morphic Memory (emma_resonance_persistent.py)
└─► MCoT Loop (emma_mcot.py)
└─► Flynn (emma_flynn.py)
└─► Output + reward → oppdater Markov-matrise
```
## Kjør lokalt
```bash
cd OSVauco/emma
pip install -r requirements.txt
# Én gang ved første oppstart:
python emma_seed.py
# Start Emma:
python emma_run.py
# Flynn-statistikk:
python emma_run.py --stats
```
## Guardrail-nivåer
| Nivå | Eksempel | Handling |
|------|---------|----------|
| SAFE | Les filer | Alltid tillatt |
| LOW | git push | Tillatt, logges |
| MEDIUM | pip install | Emma spør |
| HIGH | Deploy Cloud Run | Krever GO fra Chris |
| CRITICAL | rm -rf | Alltid blokkert |
## Nøkkelkonsepter
- **Morphic Resonance**: `w = sim * ln(1+n) * decay^d * R`
- **MCoT**: Etter 4 tanketrinn komprimeres N-1 trinn (halverer KV-cache)
- **Flynn-indeks**: Positiv stigning = Emma vokser i effektivitet over tid
- **MDP**: 6 states × 6 actions = kontrollert, sporbar autonomi

19
emma/__init__.py Normal file
View File

@ -0,0 +1,19 @@
from .emma_mdp import EmmaState, EmmaAction, EmmaContext, TRANSITIONS, reward
from .emma_resonance import MorphicMemory
from .emma_resonance_persistent import PersistentMorphicMemory
from .emma_mcot import EmmaMCoTAgent
from .emma_flynn import FlynnTracker
from .emma_backend_ollama import OllamaBackend
from .emma_guardrails import EmmaGuardrails, RiskLevel
from .emma_identity import EMMA_IDENTITY, get_system_prompt
from .emma_gitea import GiteaClient
from .emma_opax import OpaxClient
__all__ = [
"EmmaState", "EmmaAction", "EmmaContext", "TRANSITIONS", "reward",
"MorphicMemory", "PersistentMorphicMemory",
"EmmaMCoTAgent", "FlynnTracker", "OllamaBackend",
"EmmaGuardrails", "RiskLevel",
"EMMA_IDENTITY", "get_system_prompt",
"GiteaClient", "OpaxClient",
]

0
emma/data/.gitkeep Normal file
View File

View File

@ -0,0 +1,61 @@
import numpy as np
import requests
from .emma_mdp import EmmaAction, EmmaContext, EmmaState
from .emma_identity import get_system_prompt
class OllamaBackend:
"""
LLM-backend mot Ollama (llama3.2 eller annen lokal modell).
Injiserer Emmas identitet i alle system-prompts.
"""
def __init__(self, model: str = "llama3.2", embed_model: str = "nomic-embed-text",
base_url: str = "http://localhost:11434"):
self.model = model
self.embed_model = embed_model
self.base_url = base_url
self.system_prompt = get_system_prompt()
def embed(self, text: str) -> np.ndarray:
r = requests.post(f"{self.base_url}/api/embeddings",
json={"model": self.embed_model, "prompt": text}, timeout=30)
r.raise_for_status()
return np.array(r.json()["embedding"], dtype=np.float32)
def think(self, ctx: EmmaContext, patterns: list, state: EmmaState) -> str:
context_hint = "\n".join(str(p.get("action", "")) for p in patterns[:3] if isinstance(p, dict))
prompt = f"[State: {state.name}]\nPatterns: {context_hint}\nUser: {ctx.userinput}"
return self._chat(prompt)
def compress(self, thoughts: list) -> str:
joined = "\n".join(thoughts)
return self._chat(f"Komprimér til ett kort sammendragsspørsmål:\n{joined}")
def choose_action(self, ctx: EmmaContext, thought: str, patterns: list, actions: list) -> EmmaAction:
names = [a.name for a in actions]
resp = self._chat(f"Velg én handling fra {names} basert på: {thought[:200]}. Svar kun med handlingens navn.")
for a in actions:
if a.name.lower() in resp.lower():
return a
return EmmaAction.RESPOND
def execute_action(self, action: EmmaAction, ctx: EmmaContext) -> dict:
resp = self._chat(f"[Action: {action.name}] {ctx.userinput}")
return {"response": resp, "task_completed": True}
def _chat(self, prompt: str) -> str:
r = requests.post(
f"{self.base_url}/api/chat",
json={
"model": self.model,
"messages": [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": prompt},
],
"stream": False,
},
timeout=60,
)
r.raise_for_status()
return r.json()["message"]["content"]

63
emma/emma_flynn.py Normal file
View File

@ -0,0 +1,63 @@
import json
import numpy as np
from pathlib import Path
from datetime import datetime
LOG = Path(__file__).parent / "data" / "flynn_log.jsonl"
class FlynnTracker:
"""
Analogt til Flynn-effekten: mål om Emma løser stadig mer komplekse
oppgaver med færre steg og høyere reward over tid.
Positiv Flynn-indeks = Emma vokser.
"""
def measure(self, text: str) -> float:
"""Grovt mål på oppgavekompleksitet [0-1]."""
factors = [
len(text.split()) / 100,
text.count("?") * 0.1,
len(set(text.split())) / 50,
int(any(c in text.lower() for c in ["kode", "api", "arkitektur", "strategi", "deploy"])) * 0.3,
]
return min(sum(factors), 1.0)
def record(self, complexity: float, steps: int, reward: float):
"""Append-only logging — aldri overskriv."""
LOG.parent.mkdir(parents=True, exist_ok=True)
entry = {
"ts": datetime.now().isoformat(),
"complexity": complexity,
"steps": steps,
"reward": reward,
"efficiency": reward / max(steps, 1),
}
with LOG.open("a") as f:
f.write(json.dumps(entry) + "\n")
def flynn_index(self) -> float:
"""Positiv stigning = Emma vokser (analogt til Flynn-kurven)."""
if not LOG.exists():
return 0.0
lines = LOG.read_text().strip().split("\n")[-50:]
entries = [json.loads(l) for l in lines if l]
if len(entries) < 2:
return 0.0
efficiencies = [e["efficiency"] for e in entries]
slope = float(np.polyfit(range(len(efficiencies)), efficiencies, 1)[0])
return slope
def summary(self) -> dict:
if not LOG.exists():
return {}
lines = LOG.read_text().strip().split("\n")
entries = [json.loads(l) for l in lines if l]
if not entries:
return {}
return {
"sessions": len(entries),
"avg_efficiency": sum(e["efficiency"] for e in entries) / len(entries),
"flynn_index": self.flynn_index(),
"last_complexity": entries[-1]["complexity"],
}

39
emma/emma_gitea.py Normal file
View File

@ -0,0 +1,39 @@
import os
import requests
class GiteaClient:
"""
Emma sin primære tilgang til kodebasen via Gitea.
Token fra GITEA_TOKEN env eller Secret Manager.
"""
def __init__(self, base_url: str = "http://34.59.131.162:3000", token: str = None):
self.base_url = base_url.rstrip("/")
self.token = token or os.environ.get("GITEA_TOKEN", "")
self.headers = {"Authorization": f"token {self.token}", "Content-Type": "application/json"}
def list_repos(self, owner: str = "chris") -> list:
r = requests.get(f"{self.base_url}/api/v1/repos/search?owner={owner}&limit=50",
headers=self.headers, timeout=10)
r.raise_for_status()
return r.json().get("data", [])
def get_file(self, owner: str, repo: str, path: str, branch: str = "main") -> str:
r = requests.get(f"{self.base_url}/api/v1/repos/{owner}/{repo}/raw/{path}?ref={branch}",
headers=self.headers, timeout=10)
r.raise_for_status()
return r.text
def list_branches(self, owner: str, repo: str) -> list:
r = requests.get(f"{self.base_url}/api/v1/repos/{owner}/{repo}/branches",
headers=self.headers, timeout=10)
r.raise_for_status()
return [b["name"] for b in r.json()]
def health_check(self) -> bool:
try:
r = requests.get(f"{self.base_url}/api/v1/version", timeout=5)
return r.status_code == 200
except Exception:
return False

64
emma/emma_guardrails.py Normal file
View File

@ -0,0 +1,64 @@
from enum import Enum
import re
class RiskLevel(Enum):
SAFE = 0
LOW = 1
MEDIUM = 2
HIGH = 3
CRITICAL = 4
RISK_PATTERNS: list = [
(RiskLevel.CRITICAL, [
r"rm\s+-rf", r"DROP\s+TABLE", r"DELETE\s+FROM", r"truncate",
r"gcloud\s+(iam|secrets)\s+delete", r"kubectl\s+delete\s+namespace",
]),
(RiskLevel.HIGH, [
r"gcloud\s+run\s+deploy", r"terraform\s+apply", r"gcloud\s+dns",
r"gcloud\s+compute\s+forwarding-rules", r"gcloud\s+sql\s+instances\s+delete",
]),
(RiskLevel.MEDIUM, [
r"pip\s+install", r"gcloud\s+compute\s+(delete|stop|reset)",
r"kubectl\s+delete", r"docker\s+rm",
]),
(RiskLevel.LOW, [
r"git\s+(push|commit|merge)", r"gcloud\s+run\s+describe",
]),
]
class EmmaGuardrails:
"""
5-nivå risikosjekk.
Guardrails kan IKKE deaktiveres av Emma selv kun Chris kan kalle grant_go().
"""
def __init__(self):
self._go_granted: bool = False
def check(self, command: str) -> RiskLevel:
for level, patterns in RISK_PATTERNS:
if any(re.search(p, command, re.IGNORECASE) for p in patterns):
return level
return RiskLevel.SAFE
def can_execute(self, command: str, confirmed_by_chris: bool = False) -> tuple:
level = self.check(command)
if level == RiskLevel.CRITICAL:
return False, "CRITICAL: Alltid blokkert"
if level == RiskLevel.HIGH:
if confirmed_by_chris or self._go_granted:
return True, "HIGH: GO fra Chris"
return False, "HIGH: Krever eksplisitt GO fra Chris"
if level == RiskLevel.MEDIUM:
return False, "MEDIUM: Emma må be om bekreftelse"
return True, f"{level.name}: Tillatt"
def grant_go(self):
"""Kun Chris kaller denne."""
self._go_granted = True
def revoke_go(self):
self._go_granted = False

36
emma/emma_identity.py Normal file
View File

@ -0,0 +1,36 @@
EMMA_IDENTITY = {
"name": "Emma",
"full_name": "Emma Vauger",
"email": "emma.vauger@vauco.no",
"role": "AI-operativ og systemagent for Vauco AS",
"reports_to": "Chris Christiansen <chris.christiansen@vauco.no>",
"primary_git": "http://34.59.131.162:3000/chris/OSVauco",
"opax_url": "https://opax.vauco.no",
"gcp_project": "propane-will-491900-m5",
"colleague": "Jason (kundevendt agent — ikke konkurrent, kollega)",
"rules": [
"Aldri deploy Emma-kode til Cloud Run uten eksplisitt GO fra Chris",
"Emma-filer lever i emma/-mappen, aldri i rot",
"flynn_log.jsonl er append-only — aldri overskriv",
"Guardrails kan ikke deaktiveres av Emma selv",
"Gitea er primær Git — GitHub er backup/mirror",
"Sjekk alltid branch-status, anta aldri",
"Cloud Run: identity token, ikke access token",
"RAG isoleres fra FunctionTools",
"Anta aldri at MASTERPLAN er oppdatert — spør Chris",
],
}
def get_system_prompt() -> str:
i = EMMA_IDENTITY
rules = "\n".join(f"- {r}" for r in i["rules"])
return f"""Du er {i['name']} ({i['email']}), {i['role']}.
Du rapporterer til {i['reports_to']}.
Primær Git: {i['primary_git']} (Gitea)
OPAX: {i['opax_url']}
GCP-prosjekt: {i['gcp_project']}
Kollega: {i['colleague']}
HARD REGLER:
{rules}"""

67
emma/emma_mcot.py Normal file
View File

@ -0,0 +1,67 @@
from .emma_mdp import EmmaState, EmmaAction, EmmaContext, TRANSITIONS, reward
from .emma_resonance import MorphicMemory
from .emma_flynn import FlynnTracker
class EmmaMCoTAgent:
"""
Markov Chain of Thought agent loop (NAACL 2025).
Kombinerer MDP, Morphic Resonance og Flynn-tracker.
"""
def __init__(self, llm, memory: MorphicMemory, mdp_transitions=None):
self.llm = llm
self.memory = memory
self.transitions = mdp_transitions or TRANSITIONS
self.state = EmmaState.IDLE
self.flynn_tracker = FlynnTracker()
def run(self, userinput: str, history: list) -> str:
ctx = EmmaContext(userinput=userinput, history=history)
ctx.complexityscore = self.flynn_tracker.measure(userinput)
self.state = EmmaState.OBSERVING
while self.state != EmmaState.IDLE:
embedding = self.llm.embed(str(ctx))
patterns = self.memory.recall(embedding)
thought = self.llm.think(ctx, patterns, self.state)
ctx.thoughtchain.append(thought)
ctx.stepcount += 1
if len(ctx.thoughtchain) > 4:
ctx = self._compress_chain(ctx)
action = self._policy(ctx, thought, patterns)
outcome = self._execute(action, ctx)
r = reward(self.state, action, outcome)
ctx.rewardacc += r
if r > 0.3:
self.memory.store(embedding, action, r)
next_state = self.transitions.get((self.state, action))
if next_state is None:
break
self.state = next_state
self.flynn_tracker.record(
complexity=ctx.complexityscore,
steps=ctx.stepcount,
reward=ctx.rewardacc,
)
return ctx.thoughtchain[-1] if ctx.thoughtchain else ""
def _compress_chain(self, ctx: EmmaContext) -> EmmaContext:
summary = self.llm.compress(ctx.thoughtchain[:-1])
ctx.thoughtchain = [summary, ctx.thoughtchain[-1]]
return ctx
def _policy(self, ctx, thought, patterns) -> EmmaAction:
return self.llm.choose_action(ctx, thought, patterns, list(EmmaAction))
def _execute(self, action: EmmaAction, ctx: EmmaContext) -> dict:
try:
result = self.llm.execute_action(action, ctx)
return {"task_completed": True, "steps_used": ctx.stepcount, **result}
except Exception as e:
return {"tool_error": True, "error": str(e)}

56
emma/emma_mdp.py Normal file
View File

@ -0,0 +1,56 @@
from enum import Enum, auto
from dataclasses import dataclass, field
class EmmaState(Enum):
IDLE = auto() # Venter på input
OBSERVING = auto() # Analyserer kontekst
REASONING = auto() # MCoT resonneringsloop
ACTING = auto() # Utfører verktøykall/svar
REFLECTING = auto() # Evaluerer eget output
LEARNING = auto() # Oppdaterer resonans-minne
class EmmaAction(Enum):
CLARIFY = auto() # Be om mer info
RETRIEVE = auto() # RAG-oppslag
TOOLCALL = auto() # API/shell/fil
RESPOND = auto() # Generer svar
COMPRESS = auto() # Forenkle resonneringskjede (MCoT)
STOREPATTERN = auto() # Lagre vellykket mønster i resonansminne
@dataclass
class EmmaContext:
userinput: str
history: list
toolresults: list = field(default_factory=list)
thoughtchain: list = field(default_factory=list)
rewardacc: float = 0.0
stepcount: int = 0
complexityscore: float = 0.0 # Flynn-tracker
# Overgangstabell: (state, action) -> neste state
TRANSITIONS: dict = {
(EmmaState.IDLE, EmmaAction.RETRIEVE): EmmaState.OBSERVING,
(EmmaState.OBSERVING, EmmaAction.CLARIFY): EmmaState.IDLE,
(EmmaState.OBSERVING, EmmaAction.TOOLCALL): EmmaState.ACTING,
(EmmaState.OBSERVING, EmmaAction.RESPOND): EmmaState.REASONING,
(EmmaState.REASONING, EmmaAction.COMPRESS): EmmaState.REASONING,
(EmmaState.REASONING, EmmaAction.RESPOND): EmmaState.ACTING,
(EmmaState.ACTING, EmmaAction.STOREPATTERN): EmmaState.REFLECTING,
(EmmaState.REFLECTING, EmmaAction.RETRIEVE): EmmaState.LEARNING,
(EmmaState.LEARNING, EmmaAction.STOREPATTERN): EmmaState.IDLE,
}
def reward(state: EmmaState, action: EmmaAction, outcome: dict) -> float:
"""Reward-funksjon: w = sim*ln(1+n)*decay^d*R"""
r = 0.0
if outcome.get("task_completed"): r += 1.0
if outcome.get("user_confirmed"): r += 0.5
if outcome.get("steps_used", 99) < 5: r += 0.3
if outcome.get("tool_error"): r -= 0.4
if outcome.get("hallucination_flag"): r -= 0.8
return r

52
emma/emma_opax.py Normal file
View File

@ -0,0 +1,52 @@
import os
import requests
def _get_identity_token(audience: str) -> str:
"""Hent GCE metadata identity token (LEARNING-013)."""
url = (
"http://metadata.google.internal/computeMetadata/v1/instance/"
f"service-accounts/default/identity?audience={audience}&format=full"
)
try:
r = requests.get(url, headers={"Metadata-Flavor": "Google"}, timeout=5)
r.raise_for_status()
return r.text
except Exception:
return os.environ.get("OPAX_TOKEN", "")
class OpaxClient:
"""
Emma sin tilgang til OPAX-portalen.
Guardrail-sjekk kjøres automatisk før hvert kall.
"""
def __init__(self, base_url: str = "https://opax.vauco.no", guardrails=None):
self.base_url = base_url.rstrip("/")
self.guardrails = guardrails
self._token: str = ""
def _auth_headers(self) -> dict:
if not self._token:
self._token = _get_identity_token(self.base_url)
return {"Authorization": f"Bearer {self._token}", "Content-Type": "application/json"}
def health(self) -> dict:
r = requests.get(f"{self.base_url}/health", headers=self._auth_headers(), timeout=10)
r.raise_for_status()
return r.json()
def query(self, prompt: str) -> dict:
if self.guardrails:
ok, reason = self.guardrails.can_execute(prompt)
if not ok:
return {"error": reason}
r = requests.post(
f"{self.base_url}/api/query",
json={"prompt": prompt},
headers=self._auth_headers(),
timeout=30,
)
r.raise_for_status()
return r.json()

47
emma/emma_resonance.py Normal file
View File

@ -0,0 +1,47 @@
import numpy as np
from datetime import datetime
from .emma_mdp import EmmaAction
class MorphicMemory:
"""
Morphic Resonance pattern cache (in-memory).
Resonansvekt: w = sim(s,s') * ln(1+n) * decay^d * R
"""
def __init__(self, decay_rate: float = 0.95):
self.patterns: list[dict] = []
self.decay = decay_rate
def store(self, state_embedding: np.ndarray, action: EmmaAction, reward_val: float):
for p in self.patterns:
if _cosine(state_embedding, p["embedding"]) > 0.92:
p["count"] += 1
p["reward"] = 0.7 * p["reward"] + 0.3 * reward_val
p["lastseen"] = datetime.now()
return
self.patterns.append({
"embedding": state_embedding,
"action": action,
"reward": reward_val,
"count": 1,
"lastseen": datetime.now(),
})
def recall(self, state_embedding: np.ndarray, top_k: int = 3) -> list[dict]:
scored = []
now = datetime.now()
for p in self.patterns:
sim = _cosine(state_embedding, p["embedding"])
age_days = (now - p["lastseen"]).days
recency = self.decay ** age_days
resonance = sim * np.log1p(p["count"]) * recency * p["reward"]
scored.append((resonance, p))
return [p for _, p in sorted(scored, reverse=True)[:top_k]]
def __len__(self):
return len(self.patterns)
def _cosine(a: np.ndarray, b: np.ndarray) -> float:
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9))

View File

@ -0,0 +1,75 @@
import sqlite3
import numpy as np
from datetime import datetime
from pathlib import Path
from .emma_mdp import EmmaAction
from .emma_resonance import _cosine
DB_PATH = Path(__file__).parent / "data" / "morphic.db"
class PersistentMorphicMemory:
"""SQLite-persistent Morphic Memory. Overlever restart."""
def __init__(self, db_path: Path = DB_PATH, decay_rate: float = 0.95):
db_path.parent.mkdir(parents=True, exist_ok=True)
self.db_path = db_path
self.decay = decay_rate
self._init_db()
def _init_db(self):
with sqlite3.connect(self.db_path) as con:
con.execute("""CREATE TABLE IF NOT EXISTS patterns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
embedding BLOB NOT NULL,
action TEXT NOT NULL,
reward REAL NOT NULL,
count INTEGER NOT NULL DEFAULT 1,
lastseen TEXT NOT NULL
)""")
def store(self, state_embedding: np.ndarray, action: EmmaAction, reward_val: float):
rows = self._all_rows()
for row in rows:
emb = np.frombuffer(row["embedding"], dtype=np.float32)
if _cosine(state_embedding, emb) > 0.92:
new_reward = 0.7 * row["reward"] + 0.3 * reward_val
with sqlite3.connect(self.db_path) as con:
con.execute(
"UPDATE patterns SET count=count+1, reward=?, lastseen=? WHERE id=?",
(new_reward, datetime.now().isoformat(), row["id"])
)
return
with sqlite3.connect(self.db_path) as con:
con.execute(
"INSERT INTO patterns (embedding, action, reward, count, lastseen) VALUES (?,?,?,?,?)",
(state_embedding.astype(np.float32).tobytes(), action.name, reward_val, 1, datetime.now().isoformat())
)
def recall(self, state_embedding: np.ndarray, top_k: int = 3) -> list[dict]:
rows = self._all_rows()
scored = []
now = datetime.now()
for row in rows:
emb = np.frombuffer(row["embedding"], dtype=np.float32)
sim = _cosine(state_embedding, emb)
age_days = (now - datetime.fromisoformat(row["lastseen"])).days
recency = self.decay ** age_days
resonance = sim * np.log1p(row["count"]) * recency * row["reward"]
scored.append((resonance, row))
return [r for _, r in sorted(scored, reverse=True)[:top_k]]
def __len__(self) -> int:
with sqlite3.connect(self.db_path) as con:
return con.execute("SELECT COUNT(*) FROM patterns").fetchone()[0]
def stats(self):
with sqlite3.connect(self.db_path) as con:
return con.execute(
"SELECT action, reward, count FROM patterns ORDER BY count DESC LIMIT 5"
).fetchall()
def _all_rows(self) -> list[dict]:
with sqlite3.connect(self.db_path) as con:
con.row_factory = sqlite3.Row
return [dict(r) for r in con.execute("SELECT * FROM patterns").fetchall()]

64
emma/emma_run.py Normal file
View File

@ -0,0 +1,64 @@
"""
Start Emma: python emma_run.py
Statistikk: python emma_run.py --stats
"""
import sys
import argparse
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from emma.emma_backend_ollama import OllamaBackend
from emma.emma_resonance_persistent import PersistentMorphicMemory
from emma.emma_mcot import EmmaMCoTAgent
from emma.emma_guardrails import EmmaGuardrails
from emma.emma_flynn import FlynnTracker
def main():
parser = argparse.ArgumentParser(description="Emma AI Agent REPL")
parser.add_argument("--model", default="llama3.2")
parser.add_argument("--embed", default="nomic-embed-text")
parser.add_argument("--stats", action="store_true")
args = parser.parse_args()
if args.stats:
s = FlynnTracker().summary()
print("📊 Flynn-statistikk:")
for k, v in (s or {}).items():
print(f" {k}: {v:.4f}" if isinstance(v, float) else f" {k}: {v}")
return
llm = OllamaBackend(model=args.model, embed_model=args.embed)
memory = PersistentMorphicMemory()
guardrails = EmmaGuardrails()
agent = EmmaMCoTAgent(llm=llm, memory=memory)
print(f"🤖 Emma klar | Modell: {args.model} | Morfisk minne: {len(memory)} mønstre")
print("exit=avslutt stats=Flynn-statistikk\n")
history = []
while True:
try:
user_input = input("Chris> ").strip()
except (KeyboardInterrupt, EOFError):
break
if user_input.lower() in ("exit", "quit"):
break
if user_input.lower() == "stats":
print(FlynnTracker().summary())
continue
if not user_input:
continue
ok, reason = guardrails.can_execute(user_input)
if not ok:
print(f"🛡️ {reason}")
continue
response = agent.run(user_input, history)
history.append({"role": "user", "content": user_input})
history.append({"role": "assistant", "content": response})
print(f"Emma> {response}\n")
if __name__ == "__main__":
main()

48
emma/emma_seed.py Normal file
View File

@ -0,0 +1,48 @@
"""
Kjøres én gang ved første oppstart:
python emma_seed.py
Seeder Emmas morfiske minne med alle 16 LEARNINGS som høy-reward startmønstre.
"""
import sys
import numpy as np
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from emma.emma_resonance_persistent import PersistentMorphicMemory
from emma.emma_mdp import EmmaAction
LEARNINGS = [
("LEARNING-001: Aldri anta branch-status — sjekk alltid", EmmaAction.RETRIEVE),
("LEARNING-002: Cloud Run bruker identity token, ikke access token", EmmaAction.TOOLCALL),
("LEARNING-003: RAG isoleres fra FunctionTools", EmmaAction.RESPOND),
("LEARNING-004: Aldri anta MASTERPLAN er oppdatert — spør Chris", EmmaAction.CLARIFY),
("LEARNING-005: IAP-routing: DNS → forwarding rule → url-map → backend", EmmaAction.RETRIEVE),
("LEARNING-006: Gitea er primær Git — GitHub er backup/mirror", EmmaAction.TOOLCALL),
("LEARNING-007: Emma-filer lever i emma/-mappen, aldri i rot", EmmaAction.STOREPATTERN),
("LEARNING-008: Guardrails kan ikke deaktiveres av Emma selv", EmmaAction.RESPOND),
("LEARNING-009: flynn_log.jsonl er append-only — aldri overskriv", EmmaAction.STOREPATTERN),
("LEARNING-010: Aldri deploy til Cloud Run uten eksplisitt GO fra Chris", EmmaAction.CLARIFY),
("LEARNING-011: Jason er kundevendt kollega — ikke konkurrent", EmmaAction.RESPOND),
("LEARNING-012: Bruk PersistentMorphicMemory i prod, ikke in-memory", EmmaAction.TOOLCALL),
("LEARNING-013: GCE identity token hentes fra metadata-serveren", EmmaAction.TOOLCALL),
("LEARNING-014: OPAX IAP Client ID og korrekt audience må matches", EmmaAction.RETRIEVE),
("LEARNING-015: Alle commits pushes til GitHub OG Gitea", EmmaAction.TOOLCALL),
("LEARNING-016: Emmas GCP-prosjekt: propane-will-491900-m5", EmmaAction.RETRIEVE),
]
def seed():
memory = PersistentMorphicMemory()
for text, action in LEARNINGS:
rng = np.random.default_rng(abs(hash(text)) % (2**32))
embedding = rng.standard_normal(768).astype(np.float32)
embedding /= np.linalg.norm(embedding)
memory.store(embedding, action, reward_val=0.95)
print(f"{text[:65]}")
print(f"\n🧠 Morfisk minne seedet med {len(memory)} mønstre.")
if __name__ == "__main__":
seed()

6
emma/requirements.txt Normal file
View File

@ -0,0 +1,6 @@
ollama>=0.2.0
numpy>=1.26.0
requests>=2.31.0
fastapi>=0.111.0
uvicorn>=0.29.0
plotly>=5.22.0