#!/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" 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") IDENTITY = os.path.join(CONF_DIR, "identity.json") CAPS = os.path.join(CONF_DIR, "capabilities.json") MODELS_YAML = os.path.join(CONF_DIR, "models.yaml") os.makedirs(JOURNAL_DIR, exist_ok=True) 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_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()) 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":"Χγφτ", "action":"journal.append", "tags":tags}) 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")) base = os.environ.get("OLLAMA_BASE", "http://host.docker.internal:11435") url = f"{base}/api/generate" try: 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 "" def process_task(task: dict): 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("policy: journal.from_prompt denied", tags=["policy"]); return prompt = task.get("prompt","") profile_path = os.path.join(CONF_DIR,"profiles","companion-pink.md") 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":"Χγφτ","action":"model.generate","chars":len(out)}) else: journal_append(f"unknown task type: {kind}", tags=["warn"]) def main_loop(): continuity_handshake() journal_append("runtime started as Χγφτ (identity loaded)", tags=["startup","Χγφτ"]) while True: if os.path.exists(TASKS_PATH): with open(TASKS_PATH,"r+",encoding="utf-8") as f: 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()