gary-neuron-chat / brain.py
gary23w's picture
v4.2: L=8 cortex (ppl 11.2, ctx 192), conversational wrapper (yes/no, corrections, coreference, intros), bundled gary-neuron math
cf77b0f verified
Raw
History Blame Contribute Delete
24 kB
#!/usr/bin/env python3
"""gary-neuron-chat v3: a tiny brain that LEARNS FROM USE, in pure numpy.
CORTEX 656K-param dialogue GPT (gary-4-petite fine-tuned on SODA) -- speaks.
HIPPOCAMPUS two memory systems:
* in-window plastic read (meta-trained attention; v2) for facts still
in the 128-token context, and
* a PERSISTENT EPISODIC STORE (v3): declarative facts are encoded at
the moment you say them (surprise picks the value word), saved in
brain.npz, and retrieved by cue pattern-completion -- so recall
survives the window sliding AND program restarts.
PARIETAL arithmetic routed to gary-neuron (the 26K-param async-NCA adder sibling)
-- a trained net, not a calculator.
SLEEP replays buffered conversations mixed with base corpus -> the cortex
consolidates without forgetting.
brain.npz = cortex weights + episodic store + replay buffer + age. It evolves.
Usage: python brain.py [chat|demo|sleep]"""
import os, sys, re, json, numpy as np
import gpt_numpy as G
from tokenizers import ByteLevelBPETokenizer
D=os.path.dirname(os.path.abspath(__file__))
CFG=dict(E=96,H=4,L=8,BLK=192); EOT=0
tok=ByteLevelBPETokenizer(f"{D}/petite_vocab.json", f"{D}/petite_merges.txt")
def _f(x): return float(np.ravel(x)[0])
# optional parietal module: gary-neuron adder (sibling repo)
NEURON=None
for cand in (os.path.join(D,"..","gary-neuron"), os.path.join(D,"gary-neuron")):
if os.path.exists(os.path.join(cand,"solve.py")):
sys.path.insert(0,cand)
try:
import solve as _gn; NEURON=_gn
except Exception: NEURON=None
break
# ---------------- persistence ----------------
def load_brain(path=f"{D}/brain.npz"):
if os.path.exists(path):
z=np.load(path,allow_pickle=True)
P={k[2:]:z[k].astype(np.float32) for k in z.files if k.startswith("P/")}
Hm={k[3:]:z[k] for k in z.files if k.startswith("hm/")}
buf=list(z["buffer"]) if "buffer" in z.files else []
epis=list(z["epis"]) if "epis" in z.files else []
age=int(z["age"]) if "age" in z.files else 0
else:
zc=np.load(f"{D}/cortex.npz",allow_pickle=True); P={k[2:]:zc[k].astype(np.float32) for k in zc.files if k.startswith("P/")}
zh=np.load(f"{D}/hippo.npz"); Hm={k:zh[k] for k in zh.files}; buf=[]; epis=[]; age=0
return P,Hm,buf,epis,age
def save_brain(P,Hm,buf,epis,age,path=f"{D}/brain.npz"):
d={"P/"+k:v for k,v in P.items()}; d.update({"hm/"+k:np.atleast_1d(v) for k,v in Hm.items()})
d["buffer"]=np.array(buf[-400:],dtype=object); d["epis"]=np.array(epis[-500:],dtype=object)
d["age"]=age; np.savez(path,**d)
def cortex(P, ids):
x=np.array([ids[-CFG["BLK"]:]]); logits,cache=G.forward(P,x,CFG); return cache["xf"][0], logits[0]
# ---------------- text utils ----------------
STOP={"what","is","my","the","a","an","you","your","i","me","do","did","does","how",
"was","were","it","to","of","and","or","in","on","at","that","this","s","u","g",
"who","whats","tell","about","im","ive","id","ill","am","are","be","will","wont","cant","dont",
"yes","no","really","have","has","had","its","these","those","there","here",
"remember","recall","know","knew","think","guess","again","still","any","some","my","mine",
"get","got","getting","go","going","want","wanted","would","could","should","can","please",
"me","us","we","they","them","he","she","him","her","his","hers","their","our","ours"}
def _w1(w):
w=w.lower().strip("?.!,;:'’\"><)([]}{")
if w.endswith("'s") or w.endswith("’s"): w=w[:-2]
return w
def _words(s): return {_w1(w) for w in s.split()} - {""}
def _content(s): return _words(s)-STOP
IRR={"drank":"drink","ate":"eat","went":"go","goes":"go","saw":"see","met":"meet","took":"take",
"bought":"buy","made":"make","ran":"run","drove":"drive","wrote":"write","slept":"sleep",
"gave":"give","told":"tell","said":"say","felt":"feel","day":"today","days":"today","old":"age","aged":"age",
"work":"job","working":"job","employed":"job","occupation":"job","profession":"job","career":"job","kids":"kid","children":"kid",
"hometown":"city","town":"city"}
def _stem(ws):
ws={IRR.get(w,w) for w in ws}
ws={w[:-1] if len(w)>=4 and w.endswith("s") and not w.endswith("ss") else w for w in ws}
return {w[:4] if len(w)>=4 else w for w in ws}
_SVS=None
def _svs():
global _SVS
if _SVS is None: _SVS=_stem(STOPVAL)
return _SVS
REL={"dog","cat","pet","bird","fish","sister","brother","mom","dad","mother","father","wife",
"husband","son","daughter","grandma","grandpa","aunt","uncle","cousin",
"car","truck","bike","cats","dogs"}
_RS=None
def _rels():
global _RS
if _RS is None: _RS=_stem(REL)
return _RS
STOPVAL={"had","has","have","having","like","likes","liked","want","wants","wanted","went",
"going","goes","got","get","gets","day","days","week","thing","things","something",
"anything","nothing","everything","lot","bit","time","times","really","very","name",
"named","names","favorite","favourite","color","colour","food","dog","cat","hello",
"hi","hey","thanks","thank","okay","yes","lol","bye","good","great","nice","long","suppose","supposed","gonna","wanna","kinda","sorta","maybe","probably","definitely","oh","ya","yeah","yep","nah","hmm","right","sure","fine","cool","wow","oops","used","use","using","still","now","for","with","from","into","onto","over","under","after","before","out","off","down","up","around","through","there","here","then","than","while","because","but","not","no","so","just","too","also","still","even","back","well","if","as","by","be","been","being","actually","now","never","always","sometimes","today","tomorrow","yesterday","tonight","sister","brother","mom","dad","mother","father","wife","husband","son","daughter","grandma","grandpa","aunt","uncle","cousin","friend","boss","live","lives","lived","drive","drives","new","anyway","hows","heres","theres","lets","gotta","mostly","honestly","basically","literally"}
_WSEP={".",",","!","?",";",":","'","'s","\"","(",")","-","\n"," "}
def _continues_word(t):
d=tok.decode([int(t)])
if not d or d[0].isspace() or d in _WSEP: return False
return True # alnum OR a UTF-8 continuation byte (accents: 'Tomás' = Tom + bytes + s)
# ---------------- episodic store (v3) ----------------
ENT=re.compile(r"\b(dog|cat|puppy|kitten|pet|bird|fish|son|daughter|sister|brother|wife|husband|friend|car|truck|bike|boat|house|cat)\b",re.I)
CORE=re.compile(r"^\s*(her|his|its|their|the)\s+name\s+is\s+",re.I)
def encode_episode(P, u, entity=None):
"""At the moment a declarative fact is stated, find its VALUE: the most surprising
content word under the cortex (surprise writes to memory). Returns dict or None."""
ma=AGEIN.match(u.strip())
if ma:
return {"t":u,"v":ma.group(2),"c":[ma.group(2)],"s":sorted(_stem({"age"})|{ma.group(2)}),"h":"age","self":True}
mi=INTRO.match(u.strip())
if mi:
nm=mi.group(2).strip("?.!,'")
if nm.split()[0].lower() not in STOP and nm.split()[0].lower() not in ADV:
return {"t":u,"v":nm,"c":[nm],"s":sorted(_stem({"name"})|{w.lower() for w in nm.split()}),"h":"name","self":True}
cont=_content(u)
if len(cont)<2 and not any(w.isdigit() for w in cont): return None
selfish=bool({"my","i","im","mine"}&_words(u)) and not bool({"her","his","its","their","your"}&_words(u))
inject=set()
if entity and CORE.match(u.strip()): inject={entity.lower()} # "her name is X" -> bind to recent entity
ids=tok.encode(f"U: {u}\n").ids
if len(ids)<3: return None
x=np.array([ids]); lg,_=G.forward(P,x,CFG); lg=lg[0].astype(np.float64)
lp=lg-lg.max(1,keepdims=True); lp=lp-np.log(np.exp(lp).sum(1,keepdims=True))
T=len(ids); nll=np.zeros(T)
for t in range(T-1): nll[t+1]=-lp[t,ids[t+1]]
cands=[]
t=0
while t<T:
d=tok.decode([ids[t]])
if d[:1].isspace():
j=t+1; sur=nll[t]
while j<T and _continues_word(ids[j]): sur=max(sur,nll[j]); j+=1
w=tok.decode(ids[t:j]) # decode span -> correct multibyte
wl=w.strip().strip("?.!,'").lower()
if wl and wl not in STOP and wl not in STOPVAL and any(c.isalnum() for c in wl) and (len(wl)>=3 or wl.isdigit()):
cands.append((w.strip().strip("?.!,'"), sur+0.15*t))
t=j
else: t+=1
if not cands: return None
cands.sort(key=lambda x:-x[1])
self_name=selfish and ("name" in _stem(cont))
head=""
for w in u.split():
w1=_w1(w)
if w1 and w1 not in STOP and w1 not in ADV: head=next(iter(_stem({w1}))); break
return {"t":u,"v":cands[0][0],"c":[w for w,_ in cands[:4]],"s":sorted(_stem(cont)|_stem(inject)),"h":(next(iter(_stem(inject))) if inject else head),"self":self_name and not inject}
PETS=_stem({"dog","cat","pet","bird","fish","puppy","kitten","hamster"})
def recall_episode(epis, cue_words):
"""Cue pattern-completion over the persistent store: best stem overlap, newest wins."""
cue=_stem(cue_words)
if not cue: return None
pet_query=bool(cue&_stem({"pet","animal"}))
name_query=("name" in cue) and not (cue&_rels())
best=None; bk=(-1,-1,-1,0,-1)
for i,e in enumerate(epis):
es=set(e["s"]); ov=len(cue&es)
if ov<1 and pet_query and (es&PETS): ov=1 # "pet/animal" cue matches a specific-pet episode
if ov<1: continue
if (es&_rels())-cue and not (pet_query and (es&PETS)): continue # relation-bound, but pet-hypernym exempt
if (cue&_rels())-es and not (pet_query and (es&PETS)): continue # cue's relation absent (pet-hypernym exempt)
selfp=1 if (name_query and e.get("self",False)) else 0
sc=(ov,selfp,1 if e.get("h","") in cue else 0,-len(es-cue-_svs()),i) # overlap, self-name, subject, specificity, recency
if sc>bk: bk=sc; best=e
if best is None: return None
bes=set(best["s"]); cov=len(cue&bes)/max(1,len(cue))
if pet_query and (bes&PETS): cov=1.0
for w in best.get("c",[best["v"]]):
if not (_stem({w.lower()})&cue): return best["t"],w,(bk[0],bk[3]),cov
return best["t"],best["v"],(bk[0],bk[3]),cov
# ---------------- in-window plastic read (v2, unchanged) ----------------
GATE_QN=30.0
def hippo_read(Hm, xf, logits, win_ids, win_roles, win_tids, cue_words):
if not cue_words: return False,-1
Wq=Hm["Wq"].astype(np.float64); bN=_f(Hm["bN"]); T=xf.shape[0]
ids=np.asarray(win_ids[-T:]); roles=np.asarray(win_roles[-T:]); tids=np.asarray(win_tids[-T:])
nxtv=np.concatenate([ids[1:],[0]])
cue=_stem(cue_words)
cand={}
for tu in sorted(set(int(x) for x in tids if x>=0)):
txt=tok.decode([int(i) for i in ids[tids==tu]])
es=_stem(_content(txt)); ov=len(es&cue)
if "?" not in txt and ov>0 and not ((es&_rels())-cue) and not ((cue&_rels())-es): cand[tu]=ov
if not cand: return False,-1
mo=max(cand.values()); elig=[tu for tu,ov in cand.items() if ov==mo]
ok=np.zeros(T,bool)
for t in range(T-1):
if roles[t]==0 and int(tids[t]) in elig:
st=tok.decode([int(ids[t])]).strip()
if st and any(c.isalnum() for c in st): ok[t]=True
if not ok.any(): return False,-1
Q=xf.astype(np.float64)@Wq; qa=Q[-1]; qn=np.linalg.norm(qa)
if qn<GATE_QN: return False,-1
sim=Q@qa
lp=logits-logits.max(1,keepdims=True); lp=lp-np.log(np.exp(lp).sum(1,keepdims=True))
nll=np.zeros(T); nll[:-1]=-lp[np.arange(T-1),nxtv[:-1]]
s=np.where(ok, sim+bN*nll, -1e9); p=int(s.argmax())
tu=int(tids[p])
best=-1; bv=-1e18; best2=-1; bv2=-1e18
for t in range(1,T):
if int(tids[t])!=tu or roles[t]!=0: continue
d=tok.decode([int(ids[t])])
if not d[:1].isspace(): continue
j=t+1
while j<T and int(tids[j])==tu and _continues_word(ids[j]): j+=1
w=tok.decode([int(x) for x in ids[t:j]]).strip().strip("?.!,'").lower()
if not w or not any(c.isalnum() for c in w) or (len(w)<3 and not w.isdigit()): continue
if w in STOP or w in STOPVAL: continue
sur=nll[t-1]+0.02*t
if _stem({w})&cue:
if sur>bv2: bv2=sur; best2=t
elif sur>bv: bv=sur; best=t
if best<0: best=best2 # all candidates matched the cue -> echo the fact word
if best<0: return False,-1
return True,best
# ---------------- decoder ----------------
COPY_MAX=3
def gen_reply(P,Hm,ids,roles,tids,max_new=26,temp=0.72,top_k=24,rep_pen=1.28,
use_hippo=True,allow_fire=True,seed=None,prev_reply_ids=(),cue_words=frozenset(),
forced_ids=()):
rng=np.random.default_rng(seed); out=[]; tri=set()
for a,b,c in zip(prev_reply_ids,prev_reply_ids[1:],prev_reply_ids[2:]): tri.add((a,b,c))
ptr=-1; copied=0; fired=bool(forced_ids); post_fire=len(forced_ids)-1 if forced_ids else -1
for step in range(max_new):
win=ids[-CFG["BLK"]:]; wroles=roles[-CFG["BLK"]:]; wtids=tids[-CFG["BLK"]:]; off=len(ids)-len(win)
xf,logits=cortex(P,win); ll=logits[-1].astype(np.float64)
forced=-1
if step<len(forced_ids): forced=forced_ids[step]
elif ptr>=0 and copied<COPY_MAX:
nx_abs=ptr+1
if nx_abs<len(ids) and roles[nx_abs]==0 and _continues_word(ids[nx_abs]):
forced=ids[nx_abs]; ptr=nx_abs; copied+=1
else:
d0=tok.decode(out).strip()
if d0 and d0[-1] not in ".!?,": out.append(tok.encode(".").ids[0])
break # recall complete -> crisp stop
if use_hippo and allow_fire and not fired and forced<0:
f,vs=hippo_read(Hm,xf,logits,win,wroles,wtids,cue_words)
if f: forced=ids[off+vs]; ptr=off+vs; copied=0; fired=True
if forced>=0: nx=forced
else:
recent=set(win[-64:])|set(out)
for t in recent: ll[t]=ll[t]/rep_pen if ll[t]>0 else ll[t]*rep_pen
for t in set(out): ll[t]-=0.6*out.count(t)
for t in set(prev_reply_ids): ll[t]-=0.5
if len(out)>=2:
for c in [c for (a,b,c) in tri if a==out[-2] and b==out[-1]]: ll[c]-=1e9
if out[-1]==out[-2]: ll[out[-1]]-=1e9
if len(out)<2: ll[EOT]-=1e9
if temp<=0: nx=int(ll.argmax())
else:
k=np.argpartition(-ll,top_k)[:top_k]; lk=ll[k]/temp
pk=np.exp(lk-lk.max()); pk/=pk.sum(); nx=int(k[rng.choice(len(k),p=pk)])
if nx==EOT and len(out)>=2: break
ids=ids+[nx]; roles=roles+[1]; tids=tids+[-1]; out.append(nx)
if len(out)>=3: tri.add((out[-3],out[-2],out[-1]))
d=tok.decode(out)
if "\n" in d: out=tok.encode(d.split("\n")[0]).ids; break
if post_fire>=0 and step>=post_fire and d.rstrip()[-1:] in ".!?,": break
if post_fire>=0 and step>=post_fire+4: break
return tok.decode(out).strip(), out, fired
# ---------------- sleep ----------------
def sleep(P, buf, base_path=f"{D}/train.bin", steps=30, lr=1.2e-4, mix=0.12):
if not buf: return P, None
base=np.memmap(base_path,dtype=np.uint16,mode="r"); T=CFG["BLK"]
ci=[]; [ci.extend(tok.encode(t).ids+[EOT]) for t in buf]; convo=np.array(ci,dtype=np.int64)
opt=G.Adam(P,lr=lr); rng=np.random.default_rng(0)
for _ in range(steps):
xs=[];ys=[]
for _ in range(16):
if rng.random()<mix and len(convo)>T+1: s=convo[(i:=rng.integers(0,len(convo)-T-1)):i+T+1]
else: j=rng.integers(0,len(base)-T-1); s=np.asarray(base[j:j+T+1],dtype=np.int64)
xs.append(s[:T]); ys.append(s[1:T+1])
loss,cache=G.forward(P,np.stack(xs),CFG,np.stack(ys)); opt.step(P,G.backward(P,CFG,cache))
return P, None
# ---------------- conversation ----------------
QWORDS=("what","whats","where","who","when","how","which")
MATH=re.compile(r"(\d{1,7})\s*\+\s*(\d{1,7})")
ASSERT=re.compile(r"=\s*-?\d")
YNWORDS=("am","is","are","do","does","did","can","could","will","would","was","were","have","has")
SELFQ=re.compile(r"real\s+person|human|alive|robot|an?\s+ai\b|sentient|a\s+machine|realy\s+person",re.I)
CORR=re.compile(r"^(no\b|nope\b|wrong\b|not\b|actually\b)")
INTRO=re.compile(r"^\s*(i'?m|i am|this is|call me|my name'?s?|the name is|name is)\s+([^\W\d]\S{1,24}(?:\s+[A-Z]\S{1,24})?)\s*$",re.U)
AGEIN=re.compile(r"^\s*(i'?m|i am)\s+(\d{1,3})(\s*(years?\s*old|yo))?\s*$",re.I)
ADV={"actually","anyway","honestly","basically","literally","oh","ok","okay","well","yeah",
"yep","nah","hmm","wow","oops","so","but","and","also","still","just","then","wait",
"sorry","hey","um","uh","no","yes","listen","look"}
class Convo:
def __init__(self, epis=None):
self.ids=[]; self.roles=[]; self.tids=[]; self.prev=[]; self._t=0; self._last_recall=None; self._last_entity=None
self.epis=epis if epis is not None else []
self.turns={} # tid -> (role, text)
def add(self,s,r):
t=tok.encode(s).ids; self.ids+=t; self.roles+=[r]*len(t); self.tids+=[self._t]*len(t)
self.turns[self._t]=(r,s); self._t+=1
def _window_score(self, cue):
wt=set(int(x) for x in self.tids[-CFG["BLK"]:] if x>=0)
cue=_stem(cue); best=None
for tu in wt:
r,txt=self.turns.get(tu,(1,""))
if r!=0 or "?" in txt: continue
es=_stem(_content(txt)); ov=len(es&cue)
if ov<1: continue
if (es&_rels())-cue or (cue&_rels())-es: continue
sc=(ov,-len(es-cue-_svs()),tu)
if best is None or sc>best: best=sc
return best
def turn(self,P,Hm,u,seed=None,use_hippo=True,temp=0.72):
uw=_words(u); first=u.strip().split()[0].lower().strip("?,!") if u.strip() else ""
questionish=("?" in u) or (first in QWORDS)
about_gary=bool({"your","you","yours"}&uw) and not ({"my","i","im","mine"}&uw)
yn=(first in YNWORDS) or ("yes or no" in u.lower())
if yn: questionish=True
# self-model: honest answers about what gary is
if about_gary and SELFQ.search(u):
r="i'm a tiny numpy brain -- 1.1M parameters, a plastic memory, and an adder. not a person, but i do remember you."
self.add(f"U: {u}\n",0); self.add(f"G: {r}\n",1); self.prev=tok.encode(r).ids
return r
# correction binding: "no, it is X" right after a recall answer rebinds the value
if self._last_recall and CORR.match(u.strip().lower()):
corr=re.sub(r"^((no|nope|nah|wrong|not|actually|wait|um|uh|sorry|hmm|its|it's)[\s,]+)+","",u.strip(),flags=re.I)
ccont=_content(corr); cs,_old=self._last_recall
has_subj=bool(_stem(ccont)&cs) or bool(_stem(ccont)&_rels())
if ccont and has_subj: # "no, my name is X" -> normal fact encode (rebinds subject)
e=encode_episode(P,corr)
if e: e["self"]=True; self.epis.append(e); r=f"got it -- {e['v']}."
else: r="oh -- noted."
else: # "no its Luna" / bare "no" -> reuse last question's subject
cand=[w for w in corr.split() if _w1(w) and _w1(w) not in STOP and _w1(w) not in STOPVAL]
if cand:
v=cand[-1].strip("?.!,")
self.epis.append({"t":u,"v":v,"c":[v],"s":sorted(cs|_stem({_w1(v)})),"h":next(iter(cs)) if cs else "","self":True})
r=f"got it -- {v}."
else: r="oh -- what is it then?"
self.add(f"U: {u}\n",0); self.add(f"G: {r}\n",1); self.prev=tok.encode(r).ids; self._last_recall=None
return r
# parietal: arithmetic -> gary-neuron (a trained net, not a calculator)
um=re.sub(r"\bplus\b","+",u.lower())
if NEURON is not None and (re.search(r"\d\s*(-|minus)\s*\d", um)):
self.add(f"U: {u}\n",0); r="i only know addition so far -- my adder neurons can't subtract yet!"
self.add(f"G: {r}\n",1); self.prev=tok.encode(r).ids; return r
m=MATH.search(um)
if m and NEURON is not None and not ASSERT.search(um):
a,b=int(m.group(1)),int(m.group(2))
if a+b<10**7:
ans=int(NEURON.solve(a,b))
r=f"{a} + {b} = {ans}"
else:
r="that one's too big for my seven-digit adder!"
self.add(f"U: {u}\n",0); self.add(f"G: {r}\n",1); self.prev=tok.encode(r).ids
return r
self.add(f"U: {u}\n",0); self.add("G:",1)
allow=questionish and not about_gary
if allow and use_hippo:
ws=self._window_score(_content(u))
hit=recall_episode(self.epis,_content(u))
if yn and _content(u): # yes/no question -> coverage logic
if hit and hit[3]>=0.99: r="yes."
elif hit: r=f"hmm -- what i remember is: {hit[0]}"
else: r="not that i know."
self._last_recall=(_stem(_content(u)),hit[1] if hit else "")
self.add(f"U: {u}\n",0); self.add(f"G: {r}\n",1); self.prev=tok.encode(r).ids
return r
if hit and (ws is None or hit[2]>=ws[:2]): # store >= window -> crisp store answer
_,val,_,_=hit; r=f"{val}."
t=tok.encode(f" {r}\n").ids
self.ids+=t; self.roles+=[1]*len(t); self.tids+=[-1]*len(t)
self.prev=t[:-1]; self._last_recall=(_stem(_content(u)),val)
return r
txt,out,fired=gen_reply(P,Hm,self.ids,self.roles,self.tids,use_hippo=use_hippo,
allow_fire=allow,seed=seed,temp=temp,
prev_reply_ids=tuple(self.prev),cue_words=_content(u),forced_ids=())
self.ids+=out+tok.encode("\n").ids; self.roles+=[1]*(len(out)+1); self.tids+=[-1]*(len(out)+1)
self.prev=out
if "?" not in u and not m: # declarative -> write to episodic store
e=encode_episode(P,u,entity=self._last_entity)
if e: self.epis.append(e)
me=ENT.search(u) # remember most-recent concrete entity for coreference
if me and "?" not in u: self._last_entity=me.group(1).lower()
return txt
def chat():
P,Hm,buf,epis,age=load_brain()
n="with" if NEURON else "without"
print(f"gary-neuron-chat v3 (age {age}, {len(epis)} memories, {n} math). Tell me things; ask me later -- even next session. '/sleep' consolidates, '/exit' leaves.")
cv=Convo(epis); sess=[]
while True:
try: u=input("\nyou: ").strip()
except (EOFError,KeyboardInterrupt): break
if u=="/exit": break
if u=="/sleep":
P,_=sleep(P,sess); age+=1; save_brain(P,Hm,buf+sess,cv.epis,age); sess=[]; print("...slept; cortex consolidated."); continue
if not u: continue
r=cv.turn(P,Hm,u); print("gary:",r); sess.append(f"U: {u}\nG: {r}")
save_brain(P,Hm,buf+sess,cv.epis,age); print(f"brain saved ({len(cv.epis)} memories).")
if __name__=="__main__":
cmd=sys.argv[1] if len(sys.argv)>1 else "chat"
if cmd=="chat": chat()
elif cmd=="demo":
P,Hm,buf,epis,age=load_brain(); cv=Convo()
script=["hello","my name is gary","what is 17 + 25?","i like to read books","how is the weather today?",
"did you sleep well?","i had a long day at work","my favorite color is blue","tell me something",
"what is my name?","what is my favorite color?"]
for u in script: print(f"you : {u}\ngary: {cv.turn(P,Hm,u,seed=3)}")
print(f"[episodic store: {[(e['t'],e['v']) for e in cv.epis]}]")
elif cmd=="sleep":
P,Hm,buf,epis,age=load_brain(); P,_=sleep(P,buf); age+=1; save_brain(P,Hm,buf,epis,age); print(f"slept. age {age}.")