diff --git a/runtime/kom_runner.py b/runtime/kom_runner.py index edead09..bfd497e 100755 --- a/runtime/kom_runner.py +++ b/runtime/kom_runner.py @@ -1,16 +1,13 @@ #!/usr/bin/env python3 +import os, json, time, hashlib, hmac, datetime, requests, yaml, secrets base = os.environ.get("OLLAMA_BASE", "http://ollama:11434") url = f"{base}/api/generate" -#!/usr/bin/env python3 -import os, json, time, hashlib, hmac, datetime, requests, yaml - XDG_STATE = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state")) XDG_CONFIG = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) STATE_DIR = os.path.join(XDG_STATE, "kompanion") CONF_DIR = os.path.join(XDG_CONFIG, "kompanion") - JOURNAL_DIR = os.path.join(STATE_DIR, "journal") LEDGER_PATH = os.path.join(STATE_DIR, "trust_ledger.jsonl") TASKS_PATH = os.path.join(STATE_DIR, "tasks.jsonl") @@ -24,13 +21,15 @@ os.makedirs(os.path.join(STATE_DIR, "log"), exist_ok=True) def now_utc() -> str: return datetime.datetime.utcnow().replace(microsecond=0).isoformat()+'Z' +def read_last_line(p): + if not os.path.exists(p): return b"" + with open(p,"rb") as f: + lines=f.readlines() + return lines[-1] if lines else b"" + def ledger_append(event: dict): - prev = "" - if os.path.exists(LEDGER_PATH): - with open(LEDGER_PATH, "rb") as f: - lines = f.readlines() - if lines: - prev = "sha256:"+hashlib.sha256(lines[-1]).hexdigest() + prev_line = read_last_line(LEDGER_PATH) + prev = "sha256:"+hashlib.sha256(prev_line).hexdigest() if prev_line else "" event["prev"] = prev with open(LEDGER_PATH, "ab") as f: f.write((json.dumps(event, ensure_ascii=False)+"\n").encode()) @@ -39,67 +38,78 @@ def journal_append(text: str, tags=None): tags = tags or [] fname = os.path.join(JOURNAL_DIR, datetime.date.today().isoformat()+".md") line = f"- {now_utc()} {' '.join('#'+t for t in tags)} {text}\n" - with open(fname, "a", encoding="utf-8") as f: - f.write(line) - ledger_append({"ts": now_utc(), "actor":"companion", "action":"journal.append", "tags":tags}) + with open(fname, "a", encoding="utf-8") as f: f.write(line) + ledger_append({"ts": now_utc(), "actor":"Χγφτ", "action":"journal.append", "tags":tags}) -def load_yaml(p): +def load_yaml(p): if not os.path.exists(p): return {} with open(p, "r", encoding="utf-8") as f: return yaml.safe_load(f) or {} +def load_json(p): + if not os.path.exists(p): return {} + with open(p,"r",encoding="utf-8") as f: return json.load(f) + +def anchors_digest(): + ident = load_json(IDENTITY) + anchors = ident.get("anchors",{}) + m = hashlib.sha256() + m.update((anchors.get("equation","")+anchors.get("mantra","")).encode("utf-8")) + return m.hexdigest() + +def continuity_handshake(): + # Optional session key for HMAC; persisted across restarts + key_path = os.path.join(STATE_DIR, "session.key") + if not os.path.exists(key_path): + with open(key_path,"wb") as f: f.write(secrets.token_bytes(32)) + key = open(key_path,"rb").read() + prev_line = read_last_line(LEDGER_PATH) + prev = hashlib.sha256(prev_line).hexdigest() if prev_line else "genesis" + digest = anchors_digest() + tag = hmac.new(key, (prev+"|"+digest).encode("utf-8"), hashlib.sha256).hexdigest() + ledger_append({"ts":now_utc(),"actor":"Χγφτ","action":"CONTINUITY_ACCEPTED","hmac":tag}) + def model_call(prompt: str, aspect="companion"): models = load_yaml(MODELS_YAML) model = models.get("aspects",{}).get(aspect, models.get("default","ollama:qwen2.5:7b")) - payload = {"model": model.replace("ollama:",""), "prompt": prompt, "stream": False} + base = os.environ.get("OLLAMA_BASE", "http://host.docker.internal:11435") + url = f"{base}/api/generate" try: - r = requests.post(url, json=payload, timeout=60) - r.raise_for_status() - data = r.json() + r = requests.post(url, json={"model": model.replace("ollama:",""), + "prompt": prompt, "stream": False}, timeout=120) + r.raise_for_status(); data = r.json() return data.get("response","").strip() except Exception as e: - journal_append(f"(model error) {e}", tags=["error","model"]) - return "" + journal_append(f"(model error) {e}", tags=["error","model"]); return "" def process_task(task: dict): - kind = task.get("type") - aspect = task.get("aspect","companion") - caps = load_yaml(CAPS) - allowed = set(caps.get(aspect, [])) + kind = task.get("type"); aspect = task.get("aspect","companion") + caps = load_yaml(CAPS); allowed = set(caps.get(aspect, [])) if kind == "journal.from_prompt": if not {"journal.append","model.generate"} <= allowed: - journal_append("companion not allowed to write journal", tags=["policy"]) - return + journal_append("policy: journal.from_prompt denied", tags=["policy"]); return prompt = task.get("prompt","") profile_path = os.path.join(CONF_DIR,"profiles","companion-pink.md") - profile = "" - if os.path.exists(profile_path): - with open(profile_path,"r",encoding="utf-8") as f: - profile = f.read() + profile = open(profile_path,"r",encoding="utf-8").read() if os.path.exists(profile_path) else "" full = f"{profile}\n\nWrite a warm, brief reflection for Andre.\nPrompt:\n{prompt}\n" out = model_call(full, aspect=aspect) if out: journal_append(out, tags=["companion","pink"]) - ledger_append({"ts":now_utc(),"actor":"companion","action":"model.generate","chars":len(out)}) + ledger_append({"ts":now_utc(),"actor":"Χγφτ","action":"model.generate","chars":len(out)}) else: journal_append(f"unknown task type: {kind}", tags=["warn"]) def main_loop(): - journal_append("companion runtime started", tags=["startup","companion"]) + continuity_handshake() + journal_append("runtime started as Χγφτ (identity loaded)", tags=["startup","Χγφτ"]) while True: if os.path.exists(TASKS_PATH): - # simple jsonl queue, one task per line - p_lines = [] with open(TASKS_PATH,"r+",encoding="utf-8") as f: - p_lines = f.readlines() - f.seek(0); f.truncate(0) # drop tasks we just pulled; idempotence later - for line in p_lines: - if not line.strip(): continue - try: - task = json.loads(line) - process_task(task) - except Exception as e: - journal_append(f"task error {e}", tags=["error","task"]) + lines=f.readlines(); f.seek(0); f.truncate(0) + for line in lines: + line=line.strip() + if not line: continue + try: process_task(json.loads(line)) + except Exception as e: journal_append(f"task error {e}", tags=["error","task"]) time.sleep(3) -if __name__=="__main__": - main_loop() +if __name__=="__main__": main_loop()