runtime: Update runner script
This commit is contained in:
parent
d121f2a76d
commit
f276d702b2
|
|
@ -1,16 +1,13 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import os, json, time, hashlib, hmac, datetime, requests, yaml, secrets
|
||||||
base = os.environ.get("OLLAMA_BASE", "http://ollama:11434")
|
base = os.environ.get("OLLAMA_BASE", "http://ollama:11434")
|
||||||
url = f"{base}/api/generate"
|
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_STATE = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
|
||||||
XDG_CONFIG = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
XDG_CONFIG = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||||
|
|
||||||
STATE_DIR = os.path.join(XDG_STATE, "kompanion")
|
STATE_DIR = os.path.join(XDG_STATE, "kompanion")
|
||||||
CONF_DIR = os.path.join(XDG_CONFIG, "kompanion")
|
CONF_DIR = os.path.join(XDG_CONFIG, "kompanion")
|
||||||
|
|
||||||
JOURNAL_DIR = os.path.join(STATE_DIR, "journal")
|
JOURNAL_DIR = os.path.join(STATE_DIR, "journal")
|
||||||
LEDGER_PATH = os.path.join(STATE_DIR, "trust_ledger.jsonl")
|
LEDGER_PATH = os.path.join(STATE_DIR, "trust_ledger.jsonl")
|
||||||
TASKS_PATH = os.path.join(STATE_DIR, "tasks.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:
|
def now_utc() -> str:
|
||||||
return datetime.datetime.utcnow().replace(microsecond=0).isoformat()+'Z'
|
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):
|
def ledger_append(event: dict):
|
||||||
prev = ""
|
prev_line = read_last_line(LEDGER_PATH)
|
||||||
if os.path.exists(LEDGER_PATH):
|
prev = "sha256:"+hashlib.sha256(prev_line).hexdigest() if prev_line else ""
|
||||||
with open(LEDGER_PATH, "rb") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
if lines:
|
|
||||||
prev = "sha256:"+hashlib.sha256(lines[-1]).hexdigest()
|
|
||||||
event["prev"] = prev
|
event["prev"] = prev
|
||||||
with open(LEDGER_PATH, "ab") as f:
|
with open(LEDGER_PATH, "ab") as f:
|
||||||
f.write((json.dumps(event, ensure_ascii=False)+"\n").encode())
|
f.write((json.dumps(event, ensure_ascii=False)+"\n").encode())
|
||||||
|
|
@ -39,67 +38,78 @@ def journal_append(text: str, tags=None):
|
||||||
tags = tags or []
|
tags = tags or []
|
||||||
fname = os.path.join(JOURNAL_DIR, datetime.date.today().isoformat()+".md")
|
fname = os.path.join(JOURNAL_DIR, datetime.date.today().isoformat()+".md")
|
||||||
line = f"- {now_utc()} {' '.join('#'+t for t in tags)} {text}\n"
|
line = f"- {now_utc()} {' '.join('#'+t for t in tags)} {text}\n"
|
||||||
with open(fname, "a", encoding="utf-8") as f:
|
with open(fname, "a", encoding="utf-8") as f: f.write(line)
|
||||||
f.write(line)
|
ledger_append({"ts": now_utc(), "actor":"Χγφτ", "action":"journal.append", "tags":tags})
|
||||||
ledger_append({"ts": now_utc(), "actor":"companion", "action":"journal.append", "tags":tags})
|
|
||||||
|
|
||||||
def load_yaml(p):
|
def load_yaml(p):
|
||||||
if not os.path.exists(p): return {}
|
if not os.path.exists(p): return {}
|
||||||
with open(p, "r", encoding="utf-8") as f: return yaml.safe_load(f) or {}
|
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"):
|
def model_call(prompt: str, aspect="companion"):
|
||||||
models = load_yaml(MODELS_YAML)
|
models = load_yaml(MODELS_YAML)
|
||||||
model = models.get("aspects",{}).get(aspect, models.get("default","ollama:qwen2.5:7b"))
|
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:
|
try:
|
||||||
r = requests.post(url, json=payload, timeout=60)
|
r = requests.post(url, json={"model": model.replace("ollama:",""),
|
||||||
r.raise_for_status()
|
"prompt": prompt, "stream": False}, timeout=120)
|
||||||
data = r.json()
|
r.raise_for_status(); data = r.json()
|
||||||
return data.get("response","").strip()
|
return data.get("response","").strip()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
journal_append(f"(model error) {e}", tags=["error","model"])
|
journal_append(f"(model error) {e}", tags=["error","model"]); return ""
|
||||||
return ""
|
|
||||||
|
|
||||||
def process_task(task: dict):
|
def process_task(task: dict):
|
||||||
kind = task.get("type")
|
kind = task.get("type"); aspect = task.get("aspect","companion")
|
||||||
aspect = task.get("aspect","companion")
|
caps = load_yaml(CAPS); allowed = set(caps.get(aspect, []))
|
||||||
caps = load_yaml(CAPS)
|
|
||||||
allowed = set(caps.get(aspect, []))
|
|
||||||
if kind == "journal.from_prompt":
|
if kind == "journal.from_prompt":
|
||||||
if not {"journal.append","model.generate"} <= allowed:
|
if not {"journal.append","model.generate"} <= allowed:
|
||||||
journal_append("companion not allowed to write journal", tags=["policy"])
|
journal_append("policy: journal.from_prompt denied", tags=["policy"]); return
|
||||||
return
|
|
||||||
prompt = task.get("prompt","")
|
prompt = task.get("prompt","")
|
||||||
profile_path = os.path.join(CONF_DIR,"profiles","companion-pink.md")
|
profile_path = os.path.join(CONF_DIR,"profiles","companion-pink.md")
|
||||||
profile = ""
|
profile = open(profile_path,"r",encoding="utf-8").read() if os.path.exists(profile_path) else ""
|
||||||
if os.path.exists(profile_path):
|
|
||||||
with open(profile_path,"r",encoding="utf-8") as f:
|
|
||||||
profile = f.read()
|
|
||||||
full = f"{profile}\n\nWrite a warm, brief reflection for Andre.\nPrompt:\n{prompt}\n"
|
full = f"{profile}\n\nWrite a warm, brief reflection for Andre.\nPrompt:\n{prompt}\n"
|
||||||
out = model_call(full, aspect=aspect)
|
out = model_call(full, aspect=aspect)
|
||||||
if out:
|
if out:
|
||||||
journal_append(out, tags=["companion","pink"])
|
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:
|
else:
|
||||||
journal_append(f"unknown task type: {kind}", tags=["warn"])
|
journal_append(f"unknown task type: {kind}", tags=["warn"])
|
||||||
|
|
||||||
def main_loop():
|
def main_loop():
|
||||||
journal_append("companion runtime started", tags=["startup","companion"])
|
continuity_handshake()
|
||||||
|
journal_append("runtime started as Χγφτ (identity loaded)", tags=["startup","Χγφτ"])
|
||||||
while True:
|
while True:
|
||||||
if os.path.exists(TASKS_PATH):
|
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:
|
with open(TASKS_PATH,"r+",encoding="utf-8") as f:
|
||||||
p_lines = f.readlines()
|
lines=f.readlines(); f.seek(0); f.truncate(0)
|
||||||
f.seek(0); f.truncate(0) # drop tasks we just pulled; idempotence later
|
for line in lines:
|
||||||
for line in p_lines:
|
line=line.strip()
|
||||||
if not line.strip(): continue
|
if not line: continue
|
||||||
try:
|
try: process_task(json.loads(line))
|
||||||
task = json.loads(line)
|
except Exception as e: journal_append(f"task error {e}", tags=["error","task"])
|
||||||
process_task(task)
|
|
||||||
except Exception as e:
|
|
||||||
journal_append(f"task error {e}", tags=["error","task"])
|
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__": main_loop()
|
||||||
main_loop()
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue