From 4e61f13b74324ec60ebd8286da8b5b3b91ad2aaf Mon Sep 17 00:00:00 2001 From: Thomas Connally Date: Fri, 12 Jun 2026 18:43:58 -0500 Subject: [PATCH] refactor: depend on perseus-agent-core for memory + tools layer agent/memory/ and agent/tools/ were copy-pasted between rapid-agent and qwen-memory; the same MemoryEntry crash shipped twice because of it. The shared layer now lives in tcconnally/perseus-agent-core (canonical post-fix versions) and is installed via requirements.txt. Agent-specific code (config, agent classes, LLM client, demos) stays here. Verified: imports clean and existing tests pass against the package. Co-Authored-By: Claude Fable 5 --- agent/__init__.py | 2 +- agent/__pycache__/__init__.cpython-311.pyc | Bin 766 -> 769 bytes agent/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 677 bytes agent/__pycache__/config.cpython-311.pyc | Bin 5151 -> 5154 bytes agent/__pycache__/config.cpython-314.pyc | Bin 0 -> 5067 bytes agent/__pycache__/main.cpython-311.pyc | Bin 17187 -> 17181 bytes agent/__pycache__/main.cpython-314.pyc | Bin 0 -> 17676 bytes agent/main.py | 4 +- agent/memory/__init__.py | 27 -- .../__pycache__/__init__.cpython-311.pyc | Bin 966 -> 0 bytes .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 865 bytes .../__pycache__/backend.cpython-311.pyc | Bin 5274 -> 0 bytes .../__pycache__/backend.cpython-314.pyc | Bin 0 -> 6072 bytes .../elastic_memory.cpython-311.pyc | Bin 14837 -> 0 bytes .../elastic_memory.cpython-314.pyc | Bin 0 -> 15917 bytes .../__pycache__/engram_memory.cpython-311.pyc | Bin 9468 -> 0 bytes .../__pycache__/engram_memory.cpython-314.pyc | Bin 0 -> 11476 bytes agent/memory/backend.py | 106 ------ agent/memory/elastic_memory.py | 334 ------------------ agent/memory/engram_memory.py | 240 ------------- agent/tools/__init__.py | 14 - .../__pycache__/__init__.cpython-311.pyc | Bin 648 -> 0 bytes .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 591 bytes .../__pycache__/decision_log.cpython-311.pyc | Bin 4670 -> 0 bytes .../__pycache__/decision_log.cpython-314.pyc | Bin 0 -> 5030 bytes .../knowledge_graph.cpython-311.pyc | Bin 4865 -> 0 bytes .../knowledge_graph.cpython-314.pyc | Bin 0 -> 4972 bytes .../project_context.cpython-311.pyc | Bin 5145 -> 0 bytes .../project_context.cpython-314.pyc | Bin 0 -> 4956 bytes agent/tools/decision_log.py | 108 ------ agent/tools/knowledge_graph.py | 98 ----- agent/tools/project_context.py | 96 ----- requirements.txt | 3 + ...st_demo_paths.cpython-314-pytest-9.0.3.pyc | Bin 0 -> 22768 bytes tests/test_demo_paths.py | 8 +- 35 files changed, 10 insertions(+), 1030 deletions(-) create mode 100644 agent/__pycache__/__init__.cpython-314.pyc create mode 100644 agent/__pycache__/config.cpython-314.pyc create mode 100644 agent/__pycache__/main.cpython-314.pyc delete mode 100755 agent/memory/__init__.py delete mode 100644 agent/memory/__pycache__/__init__.cpython-311.pyc create mode 100644 agent/memory/__pycache__/__init__.cpython-314.pyc delete mode 100644 agent/memory/__pycache__/backend.cpython-311.pyc create mode 100644 agent/memory/__pycache__/backend.cpython-314.pyc delete mode 100644 agent/memory/__pycache__/elastic_memory.cpython-311.pyc create mode 100644 agent/memory/__pycache__/elastic_memory.cpython-314.pyc delete mode 100644 agent/memory/__pycache__/engram_memory.cpython-311.pyc create mode 100644 agent/memory/__pycache__/engram_memory.cpython-314.pyc delete mode 100755 agent/memory/backend.py delete mode 100755 agent/memory/elastic_memory.py delete mode 100755 agent/memory/engram_memory.py delete mode 100755 agent/tools/__init__.py delete mode 100644 agent/tools/__pycache__/__init__.cpython-311.pyc create mode 100644 agent/tools/__pycache__/__init__.cpython-314.pyc delete mode 100644 agent/tools/__pycache__/decision_log.cpython-311.pyc create mode 100644 agent/tools/__pycache__/decision_log.cpython-314.pyc delete mode 100644 agent/tools/__pycache__/knowledge_graph.cpython-311.pyc create mode 100644 agent/tools/__pycache__/knowledge_graph.cpython-314.pyc delete mode 100644 agent/tools/__pycache__/project_context.cpython-311.pyc create mode 100644 agent/tools/__pycache__/project_context.cpython-314.pyc delete mode 100755 agent/tools/decision_log.py delete mode 100755 agent/tools/knowledge_graph.py delete mode 100755 agent/tools/project_context.py create mode 100644 tests/__pycache__/test_demo_paths.cpython-314-pytest-9.0.3.pyc diff --git a/agent/__init__.py b/agent/__init__.py index 5eaf013..e92bcd2 100755 --- a/agent/__init__.py +++ b/agent/__init__.py @@ -5,7 +5,7 @@ from agent.config import AgentConfig from agent.main import PerseusMemoryAgent -from agent.memory import ( +from perseus_agent_core.memory import ( ElasticMemoryBackend, EngramMemoryBackend, MemoryBackend, diff --git a/agent/__pycache__/__init__.cpython-311.pyc b/agent/__pycache__/__init__.cpython-311.pyc index cdc018d7f22cb305fcc9768d61376762874c7afa..6d7898c9946e41c1f54336bd2677a9fc90d10383 100644 GIT binary patch delta 61 zcmeyz+Q`PWoR^o20SGv!Z{+&VsOsfx6%$&VT2vfUlAN5M7n7WmSelX=Q<7T{lbfGc Pnwy$el9)4Do9QwDNQV_J delta 58 zcmZo<`^U<)oR^o20SJ_>H*)=FRC0H=iU}=FEh>&FNlwnsi%HH;N!2Y%Ez3+TFODfn MEyypPtjBa20Qt`oga7~l diff --git a/agent/__pycache__/__init__.cpython-314.pyc b/agent/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a7cfbc995e6c48b77c16dfc88ae54daa3918057 GIT binary patch literal 677 zcmZ8e%Wl*#6t$DgB+axu3L93nG-44!Bh&>8gpkT;AhQ6f6m}7omAKQC#2z^}i`k$G zet`|2z%N;113v&%6~mk|>g3+Fofw((@$>z&sfW;aOa30i-MM&Xu5*+l zKyOiw7dZ34+p}+P;b#E^?(Hu|*%-!I2w@gMlx@LQ7DK#;Qk0KgVVLB>6eZ*Sy%~1I zRmb~|yx^@=YjU{c0?4=XPh@2hbqAKVL+(T>a=0MOmUL+xKbLaZa58ITUy+=x>T1)T zvGN^*Qz^*7gM)`|+Pq;M)FnA$3Itc=wK6qnoM1h1k~1kz>ZPH(n}vpbTrL^u+nfF& z`qcDtQR!QAu~e+R8r-=`Cj*@eP;0$AkYmnNd7AUCZ{TGTD(kNDEsQOM79tCQg^__| zTZfcZvZPdRI|I_vZR~n=JcL?S3pYpKvF;gLsY}pODxS8^#eXyZ6SD%WX(;`IE{r15 z$FoPpo6gKBpfqAd*|5Ii1+=T8m7;IWUtmqK5!zSQ|9vM{0PmnH(zF;TrM0Mm(sZ@f z(c@NDeZ!xqn??hhubUe9;bV+{dOi*=V}$R1Mage(jJ-=_;Ma|4;(Z9!byN2TQ+37l literal 0 HcmV?d00001 diff --git a/agent/__pycache__/config.cpython-311.pyc b/agent/__pycache__/config.cpython-311.pyc index 0ca7652b2bdeae4f6d4556432ad7b0c3c0e7156c..e68a528a04e081cc5997623a8274b399db20d3bb 100644 GIT binary patch delta 62 zcmbQQu}Fh!IWI340}yad-^j(tqUzyn6%$&VT2vfUlAN5M7n7WmSelX=Q<7T{lbfGc Qnwy$el9;num*o>b05c&JiU0rr delta 59 zcmZ3aF<*mgIWI340}v=%Z{%WRQF3**iU}=FEh>&FNlwnsi%HH;N!2Y%Ez3+TFODfn NEyyq4Y{2q~9{}c`65s#; diff --git a/agent/__pycache__/config.cpython-314.pyc b/agent/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..071455ac40bb079ff2454142d165d9eee0cc4b3e GIT binary patch literal 5067 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 literal 0 HcmV?d00001 diff --git a/agent/__pycache__/main.cpython-311.pyc b/agent/__pycache__/main.cpython-311.pyc index 925dcf53b2beb37616b1183cb8fffbdee0527074..7d77726e5d7817094b7037d8893df73d7a187702 100644 GIT binary patch delta 91 zcmZ47#yGc)k!v|GFBbz4a8BRIwS(Q#&Dkm@v^ce>IHn{yIX^EZIVZ6+B{ilbw;(1r rKd&@5HLoNwC#C?XAhonuwH L**3QePBR1m_(3L! diff --git a/agent/__pycache__/main.cpython-314.pyc b/agent/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a56e066c5d9f876d5b00df591fe7260be2734c90 GIT binary patch literal 17676 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 literal 0 HcmV?d00001 diff --git a/agent/main.py b/agent/main.py index cbea651..afce56b 100755 --- a/agent/main.py +++ b/agent/main.py @@ -22,8 +22,8 @@ from datetime import datetime, timezone from agent.config import AgentConfig -from agent.memory import ElasticMemoryBackend, EngramMemoryBackend, MemoryEntry -from agent.tools import DecisionLogTool, KnowledgeGraphTool, ProjectContextTool +from perseus_agent_core.memory import ElasticMemoryBackend, EngramMemoryBackend, MemoryEntry +from perseus_agent_core.tools import DecisionLogTool, KnowledgeGraphTool, ProjectContextTool class PerseusMemoryAgent: diff --git a/agent/memory/__init__.py b/agent/memory/__init__.py deleted file mode 100755 index b4455ce..0000000 --- a/agent/memory/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Memory backends for Perseus Memory Agent. - -Two implementations of the same MemoryBackend interface: - -- ElasticMemoryBackend: Production, cloud-managed, uses Elastic MCP -- EngramMemoryBackend: Self-hosted, MIT-licensed, SQLite-backed - -Switching backends requires changing one line of config (MEMORY_BACKEND). -""" - -from agent.memory.backend import ( - MemoryBackend, - MemoryBackendError, - MemoryEntry, - MemorySearchResult, -) -from agent.memory.elastic_memory import ElasticMemoryBackend -from agent.memory.engram_memory import EngramMemoryBackend - -__all__ = [ - "MemoryBackend", - "MemoryBackendError", - "MemoryEntry", - "MemorySearchResult", - "ElasticMemoryBackend", - "EngramMemoryBackend", -] diff --git a/agent/memory/__pycache__/__init__.cpython-311.pyc b/agent/memory/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index a23a232f0d06dd97536cff5e62c605acfb609731..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 966 zcmbVKzi$&U6tV(hwe4qW^`~3Xg*PWdRxbgaQ@9b9tfFGW$hvU__Jao%v zKtKWrBn=KlBWZ|c(iA}wh*r{qe!t1vB22>SKj0m)m25#!0MhyblZdp(pc~=`5P%^S zN}r>*IDbbaF=(PR8c}WN+@RIjK|!VMMbYU(Atq*=3dgZ!N*bgl$WAFTSkRUEVI2uE zX{nyzoc5zAMgxwGWqBQOWzt6@t;jt0k@ir|)ttlvOI%R0hvtTwwHF#5jeHDQXe|B- zF{XSHPnEIWY53$c<}9bucs73Vj9D62sgWoeFPP1zOcsBV)%5+GX(yCVu`K+Nk`!^~ zz$KO|Ibj95KO797pS;Nq500J=jvse>(NAB8Zcy%Q+@-v|K?hnZU2fNV18Md7+F(qv z&Zj5T%(?Bta`(Eoar<|!$+z3qO{c8`4=oRE521&SgX8XY8D<$#d6t#C?j>E}L{;J5 zs>0X#m-jX-Xy z^aG;{lWICsCY{x77Hd3XB(4NfON}*Yub0-ezCxO1Ofs8gz1jIyXJ5Fc;q;Nd<#b#J z1M|tjvI!wvHaaj^0uQ?_*jR%9gBt)oya4-u&+njru{oDPxa}0y@Gt5e*NV8Q+GR={qoJq*%P%R0RC?i{WzlsDY zQ$)-WCOy~n;V?x~QCvmbN_ucAc#>li=?vnO=ZT*oh883_gSjMf=LIK&sfocB0%iY& zn2~huFS%64X)?a{Q;G?bhRt3)r;7MxX^HF3Zm7ab$`=2M734!sg-(Q*h%L+&X9T8H zgDwnnHm3{tU^1Lsg|FiN#o*cS@~IcNo>Ls{J)^j@M~6aiQFJQXkSVd=31$QdybOuV zQ{`Dj_fT83{qBKMFTI8^D+x66`{gt{_NS&e*1Eq5I14QR literal 0 HcmV?d00001 diff --git a/agent/memory/__pycache__/backend.cpython-311.pyc b/agent/memory/__pycache__/backend.cpython-311.pyc deleted file mode 100644 index 1eb6ca71b1094a0aec082725f73c0d6c2d0519df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5274 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 Ie`XK=1>3Z#k^lez literal 0 HcmV?d00001 diff --git a/agent/memory/__pycache__/elastic_memory.cpython-311.pyc b/agent/memory/__pycache__/elastic_memory.cpython-311.pyc deleted file mode 100644 index d8bcaa09955281646f7c64fc30f1ca9a3f7acfa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14837 zcmbVTX>8kQdL~8PmMmM6ZCSn;J3f@yN+x$4$Fqr#95Yd}xn|?dRI7-ztwff*Bo)Vo zoTl5faJMPy4UEQ}dNJzm?5wAg0{+ngS+qYgM;G&xN>vO&AZ^fg(GJi83T77=?61D> zN0E{odpa#ezx+skH^1+BPygKQc2aPhx!iyACt-^EH@wjgS1Ivj5fYCmj+&r2nlmNo z37T9@6DGJaNoIzbU}nq{W*Xm{la?9lgq5T%N!yHl!an1eaLhO-oHI2OHSpZZ**JTG z2I|;`I?3}IgSt33SG#87JZn&5!o^t=6c4mn?~vlcd6BKo!zBbhE+yyL+ld&yGSaEvES3*MkW4^^gVVm!6&&8J2%azSZSJf za{O#EJwL;zBv#DK&ZY%PWD^qmulH8)HJeCr{2ewSvT=crNj%5SC8TLKR&H}9HanY0 zO*tnMf+(@E6vs}+l1Y&amX*?wbv7AGq}WnEHZfVMcaj(QRGepFMrP9qXg(pPdL?J< zb}W&M-AMBNte7TQOVh@}_-~19It7i)vq!>r*ov-WyHh-LU7U#t5-&I*TWNmTkz_i9 zlRlD8O(v!?LQG1)TQbe5+X*3^B7?sj6B5|0IOt^Au{Td#x_o9NI`ZcEE2Ggfql49z zRknO)v?BkB3umHlj9oLbm%bJ}#t&GL@!Z(C^B1p0&qPMYzB9<4A(Nbn&G2j=$4|yG zNokPnoaF_P&xixDDfl=8+8XK%IlsVV7zvqFI|m;`O3d)89qB?k#fNCsets6m7fXiB zs{0&Scqg@Wr+TYy#{?lQs5RQNv6Ljt>lrSQReO;ap&zPqR!Bonqy#UjW@&zw2MP;G zwUt(+YMD%?W0Gpd-l*2}jhnEHFNaJ*4Rlkr@hO~CF62;c@pMXpk*M}K&6svnAL z+rj8VJUu%fmC~wPe?ztcy!JvDg%%)j_%n{$QtC?;5|1dJ0^FhyvnCkMbf20q^A?V| zPrVOsKQszWSh)ty3MgRXY`mQ_^NvX~XNP;IgVKInBUi&aI2RAJj2)bN(|s)Bv$yjrJjJ)H`K* zAA0?v(R0xwx%Dy>FsJIbKY%3zBeCf@$eq1>2h(qhsCAohMx|7b{;Y&0@R3SjREZf2 z{;kkq;hLUT2QC80`omI<>`Vrb`UVdeE``}syo4w_!%hn68J+Qj<7qaIu*t%v64OaO z3>(;#7F8>NET6g!dl=tF6P#*`%_gFN1*&U?fK8Mv1(g9NpgOO{k{MnD8YT(=;wW!U zS~ZJ&a#F+zWq>CuJz!0iF-!1+wknXYL7y<5>hm}(6-f*59_ZFL1)DKK<-h`1Hmm)_qG;Ibp7BHxeqZ8psoGK&ctEz)V@N!L1$Gm&kmKGd2gR8{s-kC1i!@E8I=d*zs=uYEos`h9 zXp$V|C!D!@$!1MI0E}BS-8Ie8bJQK?8Z}3os4NYwV7ugiv{5H#d2Fqgj9|g*8LRq+ zi#@X+UcDJxnBN2(@niyUg@_xvgaOQ16DBeuAC3sP8-cI6OCL@HoXjbBTKdi<2$}^h z2g`s>r9qTPi4rV85LZx+nN7^yul_i{Z@C&mXG1@#6|pgEXxXeu%w9> z7n-(Qj=VSu8N?p}xknYPl&kK+5xJ(NXtKInl)A(6E9Y0weeJNk+%0ADys-}!TEih& zXiZ*NXiZ+ID9}>0G4(B9!BJ!&J6@espnG{yX>MC**?PGmqE;{0--D%g2^b?R zC<9T;xSKOj7uqro$ZOVb9&bWp;jL|IGM27h60cuZ(;`ilywOsbk#ZFxf6mwjmGyGw z1Xayt7EPQbYq||Bmbb>&j@;lFmGxHkSHk%(9k2mmX3M-2lqB2cmH-A&%qblJ1BdAC zX8nI}+ppI{QCqZauWb7oD0P=89Y~-Tjpee?3lmNKU~|tp78wrUraV(pQ@IT1$TELH z{dm)N&oTtGn7d3Fv_j5>;SnM&67@mj0wqPJ5?hXe7%zeJS=MDr3Q#!m0j*jzK4vfJ zEuw%ZL4-vR9FJpq7|7-Eh1Sv*F0}wmH8Tr3Py*0rkPW$n0nB?8h-!l>6LDaJR{l-` z7?%*jr!~M*qor0<25wZVhJ>m&dTH$3i3lnp=T2O>a3*r^v-aK(8 zV!R{h6EYLbQEhsS7L?yb;RrM(;-UulDdCSh%BRGPz-t_Pp>s*W}E1882dyjfi z<9RSF*K{i`@6veD)Zn;GD<0p2xd*@dxu^S?r+a<0(0^R^bmu+81c%s_%VC znd$-hfD{R_Qk^|mco}|xn5{+1;_fYz7q8azx9ROF>eVY+t}WI;H3+G{aZz=hMT&Mn zdpc2E_0K&4Xdfo1k}7RG09GTi#C&puh9{pKqcJ_iVET3c=sxCCUvPAQ`ECBgOIK{<3b~^! zO-Q<>2)c690H129WD)AQX$a1uEsOOU_vFr~yK)SW#tlmjNE-q^q>WY~ZHVxY*7ZJ| z3r|H^qqSTG^y=@aO?n`dHTv@n^VjQjappDWB9o;cGx_Jy8Hpjz^5OgE5`l?pgoEhB~(%z)tokuUJWec)702~N}8aqt3;TfjjI z$Y2EI7(xIEu0|mUM77VwK=z+Z3jmG;xg+E$myTyp2vltXKPm9y^g^Q!<*4%VWe^Vt zq=Y$>0fonc^nl7t^LGT?Br20i&j~nWhmtftFqV3ELUf4$0(O-WbiisS6`G3L> z0Nn%P9pcs@rRm_0xwV^ro?J^7+QaMOhG|_awC|Uj4#K%K_TcodY8xLUe|%(ZC|}!G zsO?)~6pz2)>A=EWN=N6?Si#k*w6rf-3ibd9oxZ?IpVHL2vZw^w)@rqszj@`X(%w~Q z-=_pS3c+0({XBT!S@1wUc&HFOw8axFUTmoGIExgJC8ijp9PZEU!Dseh-ricUx5{=< zu3U}?4XwKCADsHrb3Z(%GzJTey$W>Vh|=7u1Uicj${lX`)VG;b zV>nGsQH$2BNodYmbA~8hSq4osSyKY6;?>9uVkGmmrI;+UWho11zC}<+XxX++3tIcL z7A(I-yR=}nkI*bM>5e&9rFCS_MninzeDhA($khz!;n|M@CgScbJCO4JCy0XKe{OYku5N ztvN3`Qr&>P24^DrdSKBh8GHy#OpRIR7C7q!oNZck-gTC77QSdr*1-kJ{J1hbN-fiG zM;2XK7uQ_QvFMfzJ_LPs=L{}ZIUX(urCQ3Rs@0EeOS!n#ZJcOz4~j; z(o@@yucJEewj;7~Y;`{+gU^1=?Cq$oz54DO)zDd8ruuH%-gZ^zo23)f;eOh83z}X<^d}!Z`xzx*D`!a;PoM#5y(Yipb**bz6~xUaI@(SFsOj|=)_Ds z&hs3YjED~_odJ&$n3AT!@_o#xM0Z7@dIy@5$O^H9$g}4v-7Lfs6gG;WyF{2Nqq#5v z8%nhZvD6e#q!rNlz;QzFQq5?WRUKp;Q8b@gNK%t^zKf;ox&x!6hl1|~y?Fw@`~txn zyh2uSI-N;!svo#TVkR>a)$T#VgjrLqlZhnsM>XF_r;}ixBH|FK+bkv)c*7p0&x)?c zMpW~3LK0P5RP)hj0uciSV4)LxW}b}2rLq_{1<&9pzFr{>p(rMt1EN}^VjScv)jBs?rIY~f73wbm-3Wr?rO|IkZ+nC>`O^~QMafYHAD^B1WkgLR5u!i$Q>O!Fr z{bM2sg20;@QHYZPh=|zwnk!v3bDtIbyYN*PTDH`)wU>Vmg~fjdwx-EsTM0NXy#6Jd zQbSbCV~Wf7r;#5yPKHf-|FBXY~pylb%F8kAjwN{tT%mgD6-6B_|}=b+qjEbkgB zxQ1lckW$mUI`XWhL$2x2vz^K%H?BO1$Oqn#yWf;s&gET^f-54sA|N0ZDTm_}y=uw% z-f^wEUa&puEgLuFzN2!-;L_QpvoD(3bIt2-ZSZpckjx&J8;9kF*O$i8E^rsE++NVy zAGsg8m4*&^_wmmx@{S0e{_`)H&5C;W?x!c@=5aWe&XxwdTj|=hbawep-o+NpHph8d zX=+)Sdvy2V-I9pc)V8`HH||mzf<+q@=&7(@o?bfr)o;Ln*^Co%p2%@JC34*86H)FS zg){FRD|p8~+YiIl<+$@DInLJr)9!0o9WVI0azJ($DZc}Zq(GKN6mP?FVkNvZ^4#B{ z_!=J_et3BGK+c!D@?J3S>o55Fl|ak8ws&kf$NIi}AY2H9K^A?t>7AxraQ$k&`9Puh zz^geL0*~H$_}1#xA4ON9a>K556Uf%@&b>1y_a4r794T}h0U7(%w78VTR(Yg{d&H8u+Tl2Z#h!K3g_c$eYzLD$8^*s%N?YQ0xbB~r6 zOfgmzw1E_nyd_ayg)Y7_Mupwd`w|h(!}rqJ4C>Ir zw}F7sRQG#;36}It=QAOPrU#?FOZWkj!$^o8d=yfuRVz>i{F1>_xQF>ji6fBOWa_kn zq79bSZLn8+`OjGFS3pYou-)~FYRNdR(25JtVe|Rbb59Vla2`3vA#yz^ObDUx?xF1Rqi;%izy@Ri~LQzh*n=%o=* zXO|ByA0(E={~w}>XwzA>c2Ms+hnWxRjxxWn3^N~@I#2XaAN5#HdYF&G4y1>DC!Ng4 zPJB|e5e*aK?|}OAf7^&z8dNq^Dv63VMW;X@Uo^oVQ|%AR0UvzzfbC=`TpR;JCU{u% z&@fa1r|1PJgeiCmVP8`;=X_lAKB#78Q`e$7Yvx>KHKsB>O1=I4MN8Jgxy$cCL1wmA z?l@E$tvLe%D*KCpWz|Xo=jD8_m`S%Qy(yw#+gAM0*M@RmH?`QvHLY2yfsCde2r!F= zvj4x&Gnfag+FawjrWh`O>NeNR1-TZk6`#Rf+vE04vuw-SwwalZ&3>|N@-=_MeC*U_ zzneX47mjf|wuWS`^FEcek8U#ER?pHRKolEc9Y;%0{oRgu1|?Xdja=% zVrm+^+hz4!9NyL3n2+l7p;}No6a`e61Y#;i(5oIyY-&m* zgHc_O6!gY_|!k|H0Vo@om2Rp}cdcDH1C{;gvOA{F^Nv%>Jg zo^6zz$s{;j!$Zk5s6yg!*eK8eXe1)z)|ju%UWD`Mw{s_Q{ClV69p8d;gWlNvH?>+? zdtXU~*PyW4Xsp0!&@ zdUa(nceT*eU!>e_P;h}PGfHFgYG@?|iMqfe&qL1(w!dhm+lEWZP;P#sGv9Hr&~b2s z+YmRnLdUTp1Fu0~0%E0lcl~_oCsXUme9y5$&oQNa$GcPSOy#)skqyh^v-$SJh4#Zr zSAVhI(a;FmZA0TrrUo?^AmC)sNstP(R;ikYhvmjA%eQm(^!EA)ch)cF>-HAv_Wnz~ z|DkXB?cB+HeQ%+@SG&EIGv({M3iVx})4Kf_h0|1|?4U|FwLF@CIInGi&wAmSXD=3j zHeD<h*yT;+fk=o2SQXrt2;YfjiW>)V7_cf?xM7+E}q*$F2DR%5B zgJbC)%X`NQ-to^yWY>9Jn7yETIsy!w@Hc z0pY?QLl+hfm+-G-8^r+s5(Kh+H)0SKDuAUF{^bBboJ|Wx2!AL9kzjyoQbr^=D*Oa0 zgSgRK5#dd&mI;T$0y3qLRro1fv`oF2LKszDq<>K@I!5*A)=mf-MPp#dsfE#)u{&Cn z9RXh=B)pE5d<)3{k})7)l#>Jq)(PVS0;3hoN_Ao8 z!cnsqA<@^ga`@5E!=WDyuK?s&+};=M-AYHF(smVN=wN4Q1ooJVYUxl~b}Fsx+PU?v z^?mDIh1T#!L%#Kp!tR8?m*?zG0yGu&g1FYd{%8_u?xm{zx~7^ z>Zd2XKtDRGZHdKB18fshV2j?2{mH4E&ek@G^#qI z(U~-tNn+X+1#@aFS$boOMp2@O3VWdz)n589gL%yZeg>Wi#E83&scT43$5I*iM-ibG zQ|2U!n1~|6?;;_q;68Z+K~|EG!-pRr`7*(+*&DbaJ6ftt!N{+b_(o|`&R~v4sr{;z*npxNf*`JUaF#& za@NYVY{9Xk=ppY&om$N$de-E&oTqb_fWyLd{jzr5g0>&WF$2kcXFdMj+a&pfdwSsS zZ<6F@a+mg7F3`DAu#Cd>>#6oZ$hW{{RK*(5v5h+Zm#aW9Zd5dJ6h@0~9osKeXx~|? z8h*EqBt@oCttVHPA|Q>+uXGrOIp~ER(HLSPDm}4a-Mx43;NHC;{9}(*H%8Hy13Pf_ zqNf1<1uD)9yWtKZnJu?5DqahG*OufRcp)GeL6AW|#63!>sRJ3t;s@*bl2kM?7XNck zQ^C{yv-o=VU*3H0=I1>}p7k7g;(Ick?>SZIIhFUEE_hDM_S0WIuW2A9%Y@VcwJ=a0 zY<3ZS6n}W&81;9~6AtDhI}J2cC;T<`q8rIYBsd-|ti=ld_Jfd1cA%ex+J`lbDXc3t z_{l0{i;pYR?4&bb_kjcwAjH*x0b^s~8_Y0xI1QdbX4;t9*~O!kr3N%3ByD%gHY7XG zHM=hlCzBX9I|1+8ig9JP)>FyLBbB3^mrBa^9GOk>M>E)krsJ(~@>2C`{*%|v^JF)v z%J=*mV>ZdwNO1^8h=2+TrxexwSn z0dg9vshCT0b94*EQs>K!RK!S8briI#m0b1IYYkEAC8nN=(j9E-dpe35iG@Td8W#DG z5T0ij_=SY9$X*DA-{IpCztwt9HF+#l#fL^*h}+#oR#Kz zYlKh65{nCbTw=vkGMNx0k&R01|Ge^3EM}we2!EN4ifmZmLlPfhFGZz!Hk2Q8A(Tu; z<8#*8s31ygC>~*FL$R1h7RyQrsG5w0qH#7`kB!b|`<>+lJ|5;-*pXx+3d2Xmc)Mf` zT?|EIp$jp-lNA%BYIfULSpPdBn~1|8i)@eoGFvcp9Cwa~sf!CCLE;4~RLkx!I}}T# zaMOnp@!9BHN(f0&C?(q*zZew~akBV}At8#xioI5r9eHiw?9}*BaOkx&=Z1sh!@b3w z6^=YUTu^^tay6g#W6wd z+@N~pl!~I{h#({c#ir(s#3f--t8kWFSdJHA3W_x;Bw!L!lou6KI1!g%9*SOCO!5HX zGAZUTOk)nNwqnZOPsIdn-iC_|H9~WuQYI!+LYNQ2eHUSBiWRz& zH!BEnJ+4c!rjq!LV2*i#mqL&VDaOPF(y@t;<2gm9a8Aj^pp;Nt*~!5$>Pt`@T8!NK z4{60w^x8(lc`QLu*{4oD`InFz0z`M7l4wA7y0>~Iriw}c%9He*u9}hz=ZzdwO&!g) zmQ2&s5t`EjaxilSf+EZ$$4GKaB*(&4kUR^?vtnM1maHVtM)K^Wo{e*pJO{}u%ht1# zJSWL>k$MiUTq>KUBqwKXp*lQ@C%dwu6J9!@CN-SHic&&=X9}-P65u@HUBffdZguvW zlDF;;1Fp{nJ;Y#p%uoD;0?gzJCIQLq4uCyjtBz(%rm1+Y&I{Y=irQ%hLcOpZ!|4mg z!`YHBW7A^O)MPGK%cCgB1^m4`_;p9KYj4jM&Djyl&p5Q2oI$HG53~hVXv&pf%A8NZ z(U=t1Zakt;ic`zW;tDvwEV3!Y2}_<*uYc)Ht*(Zc2*TAevpup4DZs=Rc)-z;pFPD( zh(#CJSs}5Y5f* zf>MIynkWoldBr*%ilulJoiIT_6PDZ)1bn0y`Pi(8TiT|4f2el`sGcCsNMRUrCL9Z; zBK(ZBkepda#8ZfZL$Mic(E^MaQ4!ya#EX(R)Ds>G0;@c6B#o z*&o(5+!+7axZKt!_fKutomtdz$K{g~4o2bT5!sO$+|lA=e3R}rqf{mFkJvx3WsRd$8hPP~eQ zY0-_-W4a{WNKMgA)D$&I7`WoASO!Yl@2Jic61(tj87-28; zCUgK$oLCCCDu}c*k&-%0!U4>86b&XcV4iJ!0eK>F#Rc4qd_T}<7iN%p41YN+;?}mY ztN?s8rRd-}RF}Moym%Tah*1DnsEm=ayRP+I?fKkRo6#8^wZCz7Kekie)^tt%jr!~L zX>Z*P`*nMoZGLLfd&`!G-yee;v+eD(6O^XsRTl&A42rFYamtlA4VuktwDyy^<* z)mxh}G3B+t&oEHqd8p{4H%C33TrDyftON3Jn$g}tch_pAuIslb_9d)#zip_ z4a1uhv10QTb!Y6sU?>hp2s9pEYg5TRDy)K3fXx5vE7UWp#xO-cCU9L+zXD#f(l6>E zjEhSs^`dyPAY|!lu?Eg?S13g~CUYo1Um05GP=7v`({=A6Cy`m)!ariH92v{^`l7Lt z^c${HvndpeIHoW)nImucQ8;}Ur4iPojx$W_PCy}$xLlP#df%M6rnyMzwamD*3Y@V! zdnzDk0k2g&I$$TjNNND=G=O^xIPu$`WQiF;>k=n9ht>n@(?V&u59X!` z&lJp!+_Xk&zIeuU1fMb-UD(eIK2#Do%M@d$4(FJXDTd%!<|I>qW35a5LqswoqLNAt zvqD=TN_`1L775t$yxf)*_WS{0jtweFH)mxuQ9ukY)B@=m`Q=Oj?fy6j(%|2Zj>$000+f zk+CDl$HkPuXPKAg(m1EE4naD{;Q?Qv9@(7N`mgqXZfpI*UXjsNTBhi<(|zsI)l1j@ zBfDjr{&4BviNhDEKT2$o#>bMjWm%q9j24B*cYR02IikI5JVV1LY5BoNX>wjfC$m0h)_FJ#4rb}L>-`znOrZWh)+{a^qfRq zWLMyHg=ig7$26TsnKL=WR*GS4qGoao<7C;cjv=WrdP(SWGKZk^^>XWgt*xWpXv)h% z5HDaDOYZ`whx{Cp&G!m~hn~zK(t>;=16?;}P(l33#alB(;k9QV66*;97y45L)*p$&Dn!L@HHG0zWJ8 z;`~yTM*L8h!sp0dpu;PWCi+wg#AO4*qY5+6Ul#B!D@;6bNkFWnFexcqF!~>yrDBPS z36+&5(-dBZ9tdewCyB?Y$|DmH>oBOI$Q0&&AArKZ{8d6|fMi>q5}HveTDZUq&k>Wg zVuJUkfc|}j6wnFs#=E0tp@)s@j=58x&EkJeQbH;{jvX5 zR&_1*_hUcrxzTsMZ@X;oGLv?CcASmyCp#O{jm`J%b?MrM`(`hYC`b89>)PJcy=i;p zN^0$gt3OQJtJV!S%C46cG} z@94Jgc70KJGj+vY+~-(GKxX_IpMvnNKMiA?+M>F`v2&7_VfY zG*Cnm@s{J|dV+}l#Uv4lVYtl(L0rJeb$_sk=2M011k_6$|ow1RqovLZbI7qaN zvX*U?u@5XwkDZvIlJr6%i&*4GNYX!r$!AGAH3<)7cgUk?Kb162>on3B0h|NUpK%h; zYga&#dK!f4E?OhBfOwg2*^-%NcC|Ec`ktSH$POg(xg!!I+_V8(7L7{hMuqc$)--4C z`403&Qed#Nc2U?OX`1K(vz?uDOp_kEXg{3ZAz{ifiI;p=;C#@gwy=#RZ$GEL zovV964Th_KK@Hst`)JU1{-xYKn1lO@lC__D&deIMHqR+Ha;!G58DI$1@SM6$T3_1t zg)N%3n%eg{J-2A(-&|Q2#qH_N-il<|l&#eL%**9dWmEQ&C))b_5jZ_)7P2grwgdRh z-Dk4LT-yZAwR3y5JE-;72>0w&AQ={_fts=r)+@)X=a?+(OirJ{RZUrom@F$WS=CAF zN$V~qt7GXeREuAh$I9E8L4^l~>#UhMuQgD!iSamRs0Gt1M-+e}`@QpEp9LGXW?hDc z!~ia)!eO3|fUcGph!ZI=*Mb6Y9xPHXSvC7E>JdOh7g-?`6?t}|(11zwuYN1q9fgHF zsregiFBO9jiqG+c!2`tt7!m1xiXIIiiiONMh~^3diK^`395hwTnwb!ck!TkuIuFGt z&L>i_h~fbP7F|d!1l2SsE?`eU!5NLgEEN5PL?Q;_CE*!KZ+bDh#4l+&Uv2H0dL*Fe z=c5u>QiG~ZRAm|&FtG?NI2ZkFC@kf9RqPQ$=41iKi2Q1Yl{Eb0cwA#p3^N9%Pa**Lvj9}dd1AT#F$EemL`WyjyPn8QbVeB+E zu>_MLFkA;CS%U6VSyNn3{@{dpOh&Q3N!?FTvEqgXRc7K6mH?|NCc!?%6Gj#paWqSd zz*_@%!H7bbEI`C--$nwcd(@!nnL#fd#$&H7sqIk<2{&;X7y#M9fmp(JdO##_+<|(5peQH}aM&-`I zI|Fjf=<;ctd&|C+%UgCfqc>U3(CO;hwM(m)*4|xxH_zZ!*RLB zZM!?u-r5_c>!uCMt;5@1KXA=As;^gX_-;*a*BsqdR_R@PWA%;o>3zCyA?(N1NkP>cmU+>=N+jMW&9(!23XXEUz-njY3t(ooC-tF3#zLAj|YrCd% zr{=I+b9httw9-~twmg#YQVtIu;X_w*+Si!&)uU!^WAC^po*O#2W2W&AJW zx8gVV-su2COL@gJ#^@+}Tu*sAGy5o4^>W$QPmfTp8q|iJQJMSx+v06rZXMpP82Rjo zY!49T{tPhp--QeLUiWwt{a3vJ?jG9<@Lt0nfFCh>gywx?C#jDP4;mry$%%nZ`26R# zg8)CPb|c*HL3q+O-oX6Y>Kw0OeqCe0cmo6ZMeIM46l5o#ARF{z_P;gF6goz#@b2=GhGSemJG3zo{5QF6~FiMK=TI{O!1CV)F#2 zGq8E;Q0)Pfv;+rN%8CIsOQ^D@z+;0mXfzfq3n0ssv~)5Bmd;`=Jd-Ta!n4$r4(Kr9 zNgFA!1ejW=ycHuyp$q#nm(cC+>XoQLEKB{vd<=B=>@GdXg++FJ*w1Q|cQh0OnhDNT z;JX!xM!=JbC5m@H*x?<4gfxmYmr&8A7QWECj4f$X4G4INHR^`y4PgcVm;iF*Euv0K zBX1oRRYh?e>;41*%D5_VAQl0m+8|Fjvg&(bCD)<{VyfakXGKMGBH9T`<8ZZ*+(r<0 zR8@u9ULe>rmUHxP>_;+2BceYrw4!_um|(-R({y+XjQp2B^DPJN+o!&8SAYG~Lb+=3 zfuAD}y!G^U#jEn`Q?h-EJneJz?=v7g)?!=I=2L+L^oiN!)M7csGr($2n+X?wJe_zh}qN zB2<8S9tQrOi~{&Wr~qVnx=`*Q%5qeg7g+@hP=!ji36{_k$z7G<-+^(!V+-uNFGd)@ zg}P!30D>@HMlCvko8S2`{LAwhjQ;z`-J}2UZIb&};F*$!X#;03tOY6srj%fdT3ZyMLBUYMY|NE&6`BynxeNP4 zK>>W3O1Cmapbpn@>}nCfkFkC zPOzDf6Tn@bV8G88_M5BWd|WM8hk0G-sa&7ibJMh`{XSch*anSeDUo5L|j^!L|!zLjox%E)t0jd~u?4^WbU& zUbZO#{Cd%m8SJef(x5}_0dPMI#ZYA)1{dAEd7U8J#~$<_=>XexNCFSxMK%snA~^3H zxX269*(kV|g3~B8e+P8vU}FZ)p9m@nqZd-dhZkrdR3RRy?B2Oh65N016PH-9Nrzxw z7esV36+vKHfa6e+7_v9QB7y`0vR^+J6_p z4-w!4Q>pG{dAzuE@U_IH0|g5t`?mPpwe_;9Acv^2H}KVE_ykn>?p++h zl$DJYQR!^-3Rs%WN@{z}v_Ocu8>ADsWf~RgD*#sH*6}fgm z_6`1ThHe?6GYnH-m0=9vu?9YGdp6F=Rr}@2&U9sU#!XeWJfW;afUBas_3P6g{BUD> zr@B+F?p$HgRW<7!AH*Tz@}f7yBX7foJ?(8uHw3a`%u)Ngw zgN>)AbtC+%xtnvhV%u#mfpgt@_{QAzxsAxJp-sbIoZfEehE6t4WXdg-Re#Ia$}1il zDDQzx1LdvzhUoldcmK-84fCybx$*E;)sZ{jlesB*_*}ZGWn*sBblWF)4a=<~a^+~c zvIwF`3~raVtMNBCblc@EAdfpd&w%Bu zM!|h?b#V)?@;1wDS92@+{K93n2PQ4Ip1h;KeNk>1RaaorS5`e`jNlC7p7mpaW_)H8avO2V$!^y4e-D$s5)cO54u}$D@}>u?}NW_5pKW~ zg$)2p-B~=H)kx!Sma?dt-FqPfj_?BLr{PyrKuOqyko#3q2YAK5qx$@!Ah*Q${eIP# z%}%Bf%(}M?L!si-)XCrlkIK{l@E%0=)#Gq#FN6jVHslmE5ylYs5fEJ}>R{mBcVPy> z=J<#-ifC7t91PF%;dh?nH(OYRJdaPI1A@-=6^NzX)u^#s>s#$x>;FmrBTwD>T-sB= zzL@sZ%JtJ3a51jPP@w(-bX;Duz9;RfSwE3>`PPSTOkAJ1)$*XuzghX9?s(eOu%TDo z*C6@}y@vhIte)BN?A!9}+w|-l8r(WGD0}v8dxoAcx+Zi=D{lsO`Tm>zfL=Y|9dB`f zRt>Nd(W)T{f!P4s?zsKN$KlUfKAM-^XC4&bCG^yKj~+78KW-nar~Wy{{>8vSh~BHi z_`Q1D5X0Qt>l*B5?)A_ZKViW9egnimVi<^nqr?H+Te9OF7~6{g52ZsN2z+J5R2)-u zpu>f~DZpQ4ftRv17+gq1QZbB!|MCw~p;)%W84ME7^;k5{#}iO&3I>tE2?~dxAH|&g z5!IsVyn$MEA%fr^B0#N$@UId4Cjd$%cuK?ljfUZ;Pb!tFvFus>huhkmQK8T_EzHkLisISt+?48$@Rd)AE3)EVMT z{p=|Cvk|AZXOlEqz<*1u>d#K=P_d_~^`13=r2bnJ*Pp#hBclCVf^G?#B|{hCf5iO+ z|Ko(v3HzIVZfA#OyWST!C&8BlUIo0mTQRp9@pJ*xJ6`bdC;_GzIRj!n$ zIFbrYK-Lf~L8oRIoP;7hB8;th6n8KIh5qi6asR=Cy$27%Jx0u+IPh0Jc~2okA803m zpInA{0qF{;{|p!LM>^{7aMbPkdr%-CAlrrec7^)V=K8|v&gd9}=b^J2oLWD3w*De~ ztM#9~z0=mS)z)*{eS3bp?bNn&Og4{w{m@nkYA}OGfhG)me@Cn^@B3e({)276%G|Zk z06RRwCpc0Y0`%A*b5?z!jPNr;#EE#SiiiBP$FIst{;bR+;FcB`mvD!K0D{*L5Iti9 z#K3iUY~XA#`y;5cBLm#fn5uhZu^w3~N*U@cJ%}yLed=587e0q1Tp3EV0)&;&^kbca z*29kS3N5!(P4vh;NVV>CU*VZexbA zyLDHbz;+tS0Uf)IX=}}T=L2gy{FuXSe56ibV_fbUklll6vu}NRBPiD$du)Qz3}dX) z$y7zALQgmTM!z3iwq$+PQ|qgGdXip$`6)#r&t}Z literal 0 HcmV?d00001 diff --git a/agent/memory/__pycache__/engram_memory.cpython-311.pyc b/agent/memory/__pycache__/engram_memory.cpython-311.pyc deleted file mode 100644 index 90ce053269d0fdbd51a97467944a735447011581..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9468 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 diff --git a/agent/memory/__pycache__/engram_memory.cpython-314.pyc b/agent/memory/__pycache__/engram_memory.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3517697c904d00084115b27d636a09997df586ac GIT binary patch literal 11476 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 i?Hy9}YqIs str: - """Store a memory entry. Returns the entry ID.""" - ... - - @abstractmethod - async def recall( - self, - query: str, - project: Optional[str] = None, - category: Optional[str] = None, - limit: int = 10, - min_confidence: float = 0.0, - ) -> list[MemorySearchResult]: - """Search memory for relevant entries. - - Args: - query: Natural language search query - project: Filter to specific project - category: Filter to category ("fact", "decision", etc.) - limit: Max results - min_confidence: Minimum confidence threshold - """ - ... - - @abstractmethod - async def forget(self, entry_id: str) -> bool: - """Remove a memory entry. Returns True if deleted.""" - ... - - @abstractmethod - async def reflect( - self, - project: Optional[str] = None, - ) -> list[dict]: - """Cross-reference memories to synthesize new insights. - - This is the "institutional knowledge" operation — the agent looks - across all stored facts and identifies patterns, contradictions, - or gaps that should be surfaced to the user. - """ - ... - - @abstractmethod - async def health_check(self) -> dict: - """Check backend health and connection status.""" - ... diff --git a/agent/memory/elastic_memory.py b/agent/memory/elastic_memory.py deleted file mode 100755 index 8734be8..0000000 --- a/agent/memory/elastic_memory.py +++ /dev/null @@ -1,334 +0,0 @@ -"""Elasticsearch memory backend. - -Uses Elasticsearch directly via elasticsearch-py: keyword search always, -plus semantic search (ELSER via a `semantic_text` field) when the -deployment supports it — the index is created with a semantic mapping -first and falls back to a plain mapping if the inference endpoint isn't -available, so the backend works on any 8.x deployment and gets smarter -on Elastic Cloud. - -Configuration via environment variables: - ELASTIC_CLOUD_ID: Elastic Cloud deployment ID - ELASTIC_API_KEY: Elasticsearch API key - ELASTIC_MEMORY_INDEX: Index name (default: "perseus-agent-memory") -""" - -import os -import uuid -from datetime import datetime, timezone -from typing import Optional - -from agent.memory.backend import ( - MemoryBackend, - MemoryBackendError, - MemoryEntry, - MemorySearchResult, -) - -_PLAIN_MAPPINGS = { - "properties": { - "content": {"type": "text"}, - "category": {"type": "keyword"}, - "project": {"type": "keyword"}, - "tags": {"type": "keyword"}, - "source_session": {"type": "keyword"}, - "confidence": {"type": "float"}, - "created_at": {"type": "date"}, - "updated_at": {"type": "date"}, - "metadata": {"type": "object", "enabled": True}, - } -} - -_SEMANTIC_MAPPINGS = { - "properties": { - **_PLAIN_MAPPINGS["properties"], - "content": {"type": "text", "copy_to": "content_semantic"}, - "content_semantic": {"type": "semantic_text"}, - } -} - -# Optional elasticsearch-py for standalone mode -try: - from elasticsearch import Elasticsearch - _HAS_ELASTICSEARCH = True -except ImportError: - _HAS_ELASTICSEARCH = False - - -class ElasticMemoryBackend(MemoryBackend): - """Memory backend storing entries in an Elasticsearch index.""" - - def __init__(self): - self.cloud_id = os.getenv("ELASTIC_CLOUD_ID", "") - self.api_key = os.getenv("ELASTIC_API_KEY", "") - self.memory_index = os.getenv("ELASTIC_MEMORY_INDEX", "perseus-agent-memory") - if not all([self.cloud_id, self.api_key]): - raise ValueError( - "ELASTIC_CLOUD_ID and ELASTIC_API_KEY must be set. " - "Get them from elastic.co cloud console." - ) - - self._es = None - self._semantic: Optional[bool] = None # unknown until index is ensured - - @property - def es(self): - """Lazy Elasticsearch client — created on first use.""" - if self._es is None: - try: - from elasticsearch import Elasticsearch - except ImportError as exc: - raise MemoryBackendError( - "elasticsearch package not installed — " - "pip install -r requirements.txt" - ) from exc - self._es = Elasticsearch( - cloud_id=self.cloud_id, - api_key=self.api_key, - request_timeout=10, - ) - return self._es - - def _ensure_index(self) -> None: - """Create the memory index on first use; detect semantic support.""" - if self._semantic is not None: - return - try: - if self.es.indices.exists(index=self.memory_index): - mapping = self.es.indices.get_mapping(index=self.memory_index) - props = mapping[self.memory_index]["mappings"].get("properties", {}) - self._semantic = "content_semantic" in props - return - try: - self.es.indices.create( - index=self.memory_index, mappings=_SEMANTIC_MAPPINGS - ) - self._semantic = True - except Exception: - # No inference endpoint / pre-8.15 deployment — plain mapping. - self.es.indices.create( - index=self.memory_index, mappings=_PLAIN_MAPPINGS - ) - self._semantic = False - except MemoryBackendError: - raise - except Exception as exc: - self._semantic = None # retry on next call - raise MemoryBackendError(f"Elastic index setup failed: {exc}") from exc - async def remember(self, entry: MemoryEntry) -> str: - """Store a memory entry in Elasticsearch.""" - if not entry.id: - entry.id = f"mem-{uuid.uuid4().hex[:12]}" - - now = datetime.now(timezone.utc) - if not entry.created_at: - entry.created_at = now - entry.updated_at = now - - doc = { - "id": entry.id, - "content": entry.content, - "category": entry.category, - "project": entry.project, - "tags": entry.tags, - "source_session": entry.source_session, - "confidence": entry.confidence, - "created_at": entry.created_at.isoformat(), - "updated_at": entry.updated_at.isoformat(), - "metadata": entry.metadata, - } - - self._ensure_index() - try: - # refresh="wait_for": a recall immediately after a remember must - # see the new entry — session demos and tests depend on it. - self.es.index( - index=self.memory_index, - id=entry.id, - document=doc, - refresh="wait_for", - ) - except MemoryBackendError: - raise - except Exception as exc: - raise MemoryBackendError(f"Elastic store failed: {exc}") from exc - - return entry.id - - async def recall( - self, - query: str, - project: Optional[str] = None, - category: Optional[str] = None, - limit: int = 10, - min_confidence: float = 0.0, - ) -> list[MemorySearchResult]: - """Search memory: keyword match always, semantic match when available. - - Returns [] only when the search succeeded and found nothing; - backend failures raise MemoryBackendError. - """ - self._ensure_index() - - filters = [] - if project: - filters.append({"term": {"project": project}}) - if category: - filters.append({"term": {"category": category}}) - if min_confidence > 0.0: - filters.append({"range": {"confidence": {"gte": min_confidence}}}) - - if not query or query == "*": - # Tools use "*" to mean "everything in scope". - bool_query: dict = {"must": {"match_all": {}}} - else: - should = [{"match": {"content": {"query": query}}}] - if self._semantic: - should.append( - {"semantic": {"field": "content_semantic", "query": query}} - ) - bool_query = {"should": should, "minimum_should_match": 1} - if filters: - bool_query["filter"] = filters - - try: - response = self.es.search( - index=self.memory_index, - query={"bool": bool_query}, - size=limit, - ) - except Exception as exc: - raise MemoryBackendError(f"Elastic search failed: {exc}") from exc - - results = [] - for hit in response["hits"]["hits"]: - src = hit["_source"] - entry = MemoryEntry( - id=src.get("id", hit["_id"]), - content=src.get("content", ""), - category=src.get("category", "fact"), - project=src.get("project", ""), - tags=src.get("tags") or [], - source_session=src.get("source_session"), - confidence=src.get("confidence", 1.0), - created_at=_parse_dt(src.get("created_at")), - updated_at=_parse_dt(src.get("updated_at")), - metadata=src.get("metadata") or {}, - ) - results.append( - MemorySearchResult( - entry=entry, - score=hit.get("_score") or 0.0, - search_method="hybrid" if self._semantic else "keyword", - ) - ) - return results - - async def forget(self, entry_id: str) -> bool: - """Delete a memory entry by ID. Returns False if it didn't exist.""" - self._ensure_index() - try: - from elasticsearch import NotFoundError - except ImportError as exc: - raise MemoryBackendError("elasticsearch package not installed") from exc - try: - self.es.delete(index=self.memory_index, id=entry_id, refresh="wait_for") - return True - except NotFoundError: - return False - except Exception as exc: - raise MemoryBackendError(f"Elastic delete failed: {exc}") from exc - - async def reflect(self, project: Optional[str] = None) -> list[dict]: - """Cross-reference memories to find patterns and insights. - - Currently surfaces: - - stale facts (confidence < 0.3) that may need re-verification - - knowledge distribution by category (gaps show up as absences) - """ - self._ensure_index() - filters = [{"term": {"project": project}}] if project else [] - - insights: list[dict] = [] - try: - stale = self.es.search( - index=self.memory_index, - query={ - "bool": { - "filter": filters - + [{"range": {"confidence": {"lt": 0.3}}}] - } - }, - sort=[{"confidence": "asc"}], - size=10, - ) - for hit in stale["hits"]["hits"]: - src = hit["_source"] - insights.append( - { - "type": "stale_fact", - "summary": ( - f"Low-confidence memory may need re-verification: " - f"{src.get('content', '')[:120]}" - ), - "confidence": src.get("confidence"), - "id": src.get("id", hit["_id"]), - "backend": "elastic", - } - ) - - clusters = self.es.search( - index=self.memory_index, - query={"bool": {"filter": filters}} if filters else {"match_all": {}}, - aggs={"by_category": {"terms": {"field": "category"}}}, - size=0, - ) - buckets = ( - clusters.get("aggregations", {}) - .get("by_category", {}) - .get("buckets", []) - ) - if buckets: - dist = ", ".join(f"{b['key']}: {b['doc_count']}" for b in buckets) - insights.append( - { - "type": "knowledge_distribution", - "summary": f"Knowledge by category — {dist}", - "backend": "elastic", - } - ) - except Exception as exc: - raise MemoryBackendError(f"Elastic reflect failed: {exc}") from exc - - return insights - - async def health_check(self) -> dict: - """Verify Elasticsearch connection and index health. Never raises.""" - try: - if not self.es.ping(): - return { - "status": "error", - "backend": "elastic", - "error": "Elasticsearch ping failed (bad credentials or endpoint)", - } - return { - "status": "ok", - "backend": "elastic", - "cloud_id": self.cloud_id[:12] + "...", - "memory_index": self.memory_index, - "index_exists": bool( - self.es.indices.exists(index=self.memory_index) - ), - "semantic_search": self._semantic, - } - except Exception as exc: - return {"status": "error", "backend": "elastic", "error": str(exc)} - - -def _parse_dt(value) -> Optional[datetime]: - if not value: - return None - try: - return datetime.fromisoformat(str(value).replace("Z", "+00:00")) - except ValueError: - return None diff --git a/agent/memory/engram_memory.py b/agent/memory/engram_memory.py deleted file mode 100755 index fd05a22..0000000 --- a/agent/memory/engram_memory.py +++ /dev/null @@ -1,240 +0,0 @@ -"""Engram-rs memory backend — self-hosted, MIT-licensed persistent memory. - -Uses Engram-rs CLI (engram) as the memory store. Engram-rs is a SQLite-backed -long-term memory system for AI agents that provides persistent, searchable -memory across sessions with zero cloud dependencies. - -This backend implements the same MemoryBackend interface as ElasticMemoryBackend, -so switching between Elastic (cloud) and Engram-rs (local) requires changing -exactly one line of configuration. - -Engram-rs: https://github.com/tcconnally/engram-rs (MIT licensed) -Perseus: https://github.com/tcconnally/perseus (context + memory for AI agents) -""" - -import json -import os -import subprocess -import uuid -from datetime import datetime, timezone -from typing import Optional - -from agent.memory.backend import MemoryBackend, MemoryEntry, MemorySearchResult - - -class EngramMemoryBackend(MemoryBackend): - """Self-hosted memory backend using Engram-rs. - - Configuration via environment variables: - ENGRAM_BIN: Path to engram binary (default: "engram") - ENGRAM_DATA_DIR: Data directory (default: "~/.hermes/mnemosyne/data") - ENGRAM_DB_PATH: Full path to engram.db (overrides ENGRAM_DATA_DIR) - - Engram-rs provides MCP-compatible tools: - - engram_store: persist a memory entry - - engram_recall: search memory - - engram_health: check connection - """ - - def __init__(self): - self.engram_bin = os.getenv("ENGRAM_BIN", "engram") - data_dir = os.getenv( - "ENGRAM_DATA_DIR", - os.path.expanduser("~/.hermes/mnemosyne/data"), - ) - self.db_path = os.getenv( - "ENGRAM_DB_PATH", - os.path.join(data_dir, "mnemosyne.db"), - ) - - def _run_engram(self, args: list[str], retries: int = 1) -> dict: - """Run an engram CLI command and return parsed JSON output. - - Args: - args: CLI arguments to pass to engram - retries: Number of retry attempts on transient failures (timeout/lock) - """ - cmd = [self.engram_bin] + args - last_error = None - for attempt in range(retries + 1): - try: - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=10, - ) - if result.returncode != 0: - stderr = result.stderr.strip() - # Retry on transient SQLite lock errors - if "database is locked" in stderr.lower() and attempt < retries: - import time - time.sleep(2 * (attempt + 1)) - last_error = stderr - continue - return { - "error": stderr, - "exit_code": result.returncode, - } - if result.stdout.strip(): - return json.loads(result.stdout) - return {} - except FileNotFoundError: - return { - "error": f"Engram binary not found: {self.engram_bin}. " - f"Install with: curl -sSL https://raw.githubusercontent.com/" - f"tcconnally/engram-rs/main/scripts/bootstrap.sh | bash", - "exit_code": -1, - } - except subprocess.TimeoutExpired: - if attempt < retries: - import time - time.sleep(2 * (attempt + 1)) - last_error = "Engram command timed out" - continue - return {"error": "Engram command timed out after retries", "exit_code": -2} - return {"error": last_error or "unknown error", "exit_code": -3} - - async def remember(self, entry: MemoryEntry) -> str: - """Store a memory entry via engram_store. - - Engram-rs stores entries as key-value pairs with metadata in SQLite. - """ - if not entry.id: - entry.id = f"mem-{uuid.uuid4().hex[:12]}" - - now = datetime.now(timezone.utc) - entry.created_at = entry.created_at or now - entry.updated_at = now - - payload = { - "id": entry.id, - "content": entry.content, - "category": entry.category, - "project": entry.project, - "tags": entry.tags, - "confidence": entry.confidence, - "metadata": entry.metadata, - } - - result = self._run_engram( - [ - "store", - "--db", self.db_path, - "--id", entry.id, - "--data", json.dumps(payload), - "--project", entry.project, - "--category", entry.category, - "--tags", ",".join(entry.tags), - ] - ) - - if "error" in result: - raise RuntimeError(f"Engram store failed: {result['error']}") - - return entry.id - - async def recall( - self, - query: str, - project: Optional[str] = None, - category: Optional[str] = None, - limit: int = 10, - min_confidence: float = 0.0, - ) -> list[MemorySearchResult]: - """Search memory via engram_recall. - - Engram-rs uses SQLite FTS5 for full-text search across stored memories. - """ - args = [ - "recall", - "--db", self.db_path, - "--query", query, - "--limit", str(limit), - ] - - if project: - args += ["--project", project] - if category: - args += ["--category", category] - - result = self._run_engram(args) - - if "error" in result: - raise RuntimeError(f"Engram recall failed: {result['error']}") - - results = [] - for item in result.get("results", []): - try: - entry_data = json.loads(item.get("data", "{}")) - except (json.JSONDecodeError, TypeError): - continue - entry = MemoryEntry( - id=item.get("id", ""), - content=entry_data.get("content", item.get("content", "")), - category=entry_data.get("category", item.get("category", "fact")), - project=entry_data.get("project", item.get("project", "")), - tags=entry_data.get("tags", []), - confidence=entry_data.get("confidence", 1.0), - metadata=entry_data.get("metadata", {}), - ) - if entry.confidence >= min_confidence: - results.append( - MemorySearchResult( - entry=entry, - score=item.get("score", 0.0), - search_method="fts5", - ) - ) - - return results - - async def forget(self, entry_id: str) -> bool: - """Remove a memory entry from Engram-rs.""" - result = self._run_engram( - ["delete", "--db", self.db_path, "--id", entry_id] - ) - return "error" not in result - - async def reflect(self, project: Optional[str] = None) -> list[dict]: - """Cross-reference memories to find patterns. - - Uses Engram-rs SQL queries to analyze stored memories. - """ - args = ["reflect", "--db", self.db_path] - if project: - args += ["--project", project] - - result = self._run_engram(args) - - if "error" in result: - return [ - { - "type": "insight", - "summary": "Engram-rs reflect operation (SQL-backed analysis)", - "note": "Install engram >= 0.2.0 for reflect support. " - "Current: FTS5 search + metadata filtering available.", - } - ] - - return result.get("insights", []) - - async def health_check(self) -> dict: - """Verify Engram-rs connection and database health.""" - result = self._run_engram(["health", "--db", self.db_path]) - - if "error" in result: - return { - "status": "error", - "backend": "engram-rs", - "db_path": self.db_path, - "error": result["error"], - } - - return { - "status": "ok", - "backend": "engram-rs", - "db_path": self.db_path, - "entry_count": result.get("entry_count", 0), - "db_size_bytes": result.get("db_size_bytes", 0), - } diff --git a/agent/tools/__init__.py b/agent/tools/__init__.py deleted file mode 100755 index 41313fb..0000000 --- a/agent/tools/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Agent tools for project context, decisions, and knowledge graphs. - -These tools are exposed as MCP tools that the Gemini agent can call. -""" - -from agent.tools.project_context import ProjectContextTool -from agent.tools.decision_log import DecisionLogTool -from agent.tools.knowledge_graph import KnowledgeGraphTool - -__all__ = [ - "ProjectContextTool", - "DecisionLogTool", - "KnowledgeGraphTool", -] diff --git a/agent/tools/__pycache__/__init__.cpython-311.pyc b/agent/tools/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 39d8341657e9047180fdb79960f1e07b44b06b5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmY*Xv1;5v5S^8DcSea5aAiod;OgK`RY+lnK)8Sl$H8Ey5rRc%CptMRt=N@gCzVV8 zfqSP2`5Tw!T37A_?p$TGrwhK_4svkA;*lI4!+7-T>SK;o!yB!Y<=?d7wb7@T zH9XT*gBQ(&OUw#e=*mubT1o!B(&q|gjyyMMJ-1O9p3Ko=-y${0@uJoiB~L9soE`NK z%{(QVIr4ohi>lzM_FFX=EJRQz3 O*TM=eN8L6riQpggi^k~y diff --git a/agent/tools/__pycache__/__init__.cpython-314.pyc b/agent/tools/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2466ea0a89bf25c5ab27e52f652c21eb48bedd3 GIT binary patch literal 591 zcmY+C&r94u6vtxr{(dnec;5Z&U)@teo|?<-cy02sg~>NE zCYD@~F&)vNXT7y1_eTCOuz}0{(cG|OJ8L8&F_5ANE@TM=EVgONTQN2aSfN z`nl)pW+U2fLV%$+Uw4H_R4hcj;%Jd`8MO{Y>(IScFaO^$vn+Czx`A&8{9A!=D{!=i zg+7FMATJ0f_4#0%pBd1G+Zaha&gC=#Zi|W+s+<fjtPoLkThH$lDw*c6s~3@k{{=Ct7@mDf=`YWx!Jm-OPmg5tk!=4Cf(7q; IS1(}0UqLywH2?qr literal 0 HcmV?d00001 diff --git a/agent/tools/__pycache__/decision_log.cpython-311.pyc b/agent/tools/__pycache__/decision_log.cpython-311.pyc deleted file mode 100644 index ea7c436dde52893ffe6aebb3592d952491e02562..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4670 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 Px64;<{p%fC-KqW$Nxzhy diff --git a/agent/tools/__pycache__/decision_log.cpython-314.pyc b/agent/tools/__pycache__/decision_log.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..624b28772a90cb2245b87a7fca7733012b8d8289 GIT binary patch literal 5030 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 diff --git a/agent/tools/__pycache__/knowledge_graph.cpython-314.pyc b/agent/tools/__pycache__/knowledge_graph.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71c222149267aac791dd206182b122c00e4cb015 GIT binary patch literal 4972 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 G_5TGr3ElYs literal 0 HcmV?d00001 diff --git a/agent/tools/__pycache__/project_context.cpython-311.pyc b/agent/tools/__pycache__/project_context.cpython-311.pyc deleted file mode 100644 index de852e3de55dc4f394f5bd29e6bf7c9124170ece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5145 zcmd5=U2GHC6}~f`@z2=#ha})644V=#2@X|MC`lAGO%Rn9vfBV#DI02;xi^Uu+vB}6 zhK+GnQ5RLHLMRZ2;63;REU!b61748j5ylfsMuxhE;6 z#2GQ=nehm$T}nzRc}5nHh%O;4-$mH_NJQu{yiS_&V(}`9_;P=IpX!T5GgVDbo8)6t zO~upkd6FV&Q#ExxX+#y}8p~C4fvC^}?OB6BRab9j6G=P(GZJYN-T^h9#wsORJegE8 z4%;(EhG>bogjV7oRjw@%gQz!&rl*K{W#WBj8P5I^NhQ(=cNHz3RynI%iMV=6*XNT& zok;3gtd8?t{E)3qXjJ(J6KN_U+I}22iJ3?d+s|lDPm_pX``^o$2|XQ8YOd@&1O4)} z|4YaoB7#7VXhy`sj0cN%(To&8&JTyML}V-zFYz65Cn3-$)cr)Rv{Xvi2YppN*nglN zAilW}R_@9(0nVG%B^=`x%+$sox&yV0q~Zi4nn8XJs_s=m55F+6+SAk>&OHIQQ#_DjxC zaw|b@8?%&v4Q4aYOR~lp8qXx~8SY)P4&}Z#d%zCS%(Cr|#lT0*SS%O**FzF5b+*G6 z40gs>k=6d4d(!>jJ^1Z2Nv3R{%hs0pfw6-XEgN>I`oP#hObm@CxChx@O5hZwH7{f1 zrXb{>!n;2WQooA?P}@9O@#aPPv%GiH9aNiP!DLEy(_+~GdGQe(@yGBw=?nDbfffPf z#m%a@rI83dg5u+HbrRi}m<7Hoz7;dzz$3uo$0a;QwR`>)VdakJ1N6}Ib4ft=C-WY9A@70xoLf=e zekXYa2VvdNyzo}5p3h74{M)aF8}bsuaXuH?7`#n@s{^{-ULY_;#S9{Ka5Qx; zK>-IKR#6>iH5S4aNP?E&V1Qu@o8zL0$|I8NDJKmF@&>CsMb#GK>3P6ZwkAut&^4kh zI0J`O%FZ=_Meqs-i;J-Aa?bC%teW#WQ8XtGtF|;3*G$^X#!I%33z#--zXkv}4>&`c zpvRWX_`ES?D;Y}W2tcPsY^AcBT;q5Z&co`hY>wAps>2bPmEvaBa9A~Sw+C!ZWgtYR zO@lJ4*=^OHA9JS$Y;Qc10aUZ4MMF>9GR~$lhAl%#Ng4Jb4pvdu1TOwj20k>vSo$g4 zBvLmCwLM9ajsz*Y;P6dwarwG@tF(;`mzg3A+p|QLZTVI_nI)W;Eipk21~pvK4xu47 zrL9i5%JtPj#yaCz$TkMRPJf5rDk>gDEp7MY=fUP83Iy7$P|L3_|Kf7t$jH~y6J`5* zyQePfow@*3cy>dhd!f-nXw>TI{ms&^mp)Hzq>9Mb(8kGU$^*}X>sIq`9Y@6-pV!~NN=!3)#{G$t@QnmHqr?j2sBxt?-yzxtF?Wt|5LCvBDkTB(KQSO8!bpLiZYID1?UX%If7 zQutUA-++q@T)f}P#q$PSSaAFHy+BCB{<{!L9Dr5`aJ2y#F1IKz9c(q_iUtk_ zhnl_vK_LgzYWk(xpuzfakcW#z2ngGz}H@*`7Ks?~M3LiLgfb;PZ>PK@5+>Kq( zrX9d2;<4qA0C*hOr3@pc9GnV6k%N_1ZBb@B244GpK-lFck8U)sfORV;V6i;@Gx2`Y zRrD45O0;DFURb0PWnhP$Dv79sa zA)K?_@c3SM+;Ys91e5J{P3(0|{59ND3Lsx=+x@%IvAyWnZg}iZZQso9j!*B6Pw$4O z>qV1@GzBRVWZ!n_c#6bgwi1iMcWySx%Ar{7qij6s_V{8ktZT6tWnXu;2fn%~v#2fM zgl5_j^Lsh~4Rn+d?vgBGabr{V&BF=1I~jilvReKXMW5`4;0&ZykWUm*nS{{s6ITDA z)py!D5h*qd$)G(T&sz=^WmfT`=FWBL{wGBrtNBrT&pO^5+DPmjK2;2`CItfTy0Mlj z23ak{254(H98Qf$X)HFecE+=cEDEi%vJLke$aW6#?6rjZ3>Pfoqd#CmzQYIuF)9JE zz2XQ7jYdXI&| Xh1#d+y(S1CS$+C~x4(Q%FBj~e^06B) diff --git a/agent/tools/__pycache__/project_context.cpython-314.pyc b/agent/tools/__pycache__/project_context.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3af9acf055ec3e68a411b5a2add625a2584082e1 GIT binary patch literal 4956 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 X2^0J)+4~vk+!RhTjJQEiQWO6LMN5>J literal 0 HcmV?d00001 diff --git a/agent/tools/decision_log.py b/agent/tools/decision_log.py deleted file mode 100755 index cb6153d..0000000 --- a/agent/tools/decision_log.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Decision logging and recall tools. - -Every time the agent (or a developer working with it) makes an architectural -decision, the agent logs it. Later sessions can recall past decisions and -their rationale, preventing the "why did we do that?" problem. - -Exposed as MCP tools: - - log_decision: Record a decision with rationale and context - - recall_decisions: Find past decisions relevant to current context - - list_decisions: List all decisions for a project -""" - -from datetime import datetime, timezone -from typing import Optional - -from agent.memory.backend import MemoryEntry - - -class DecisionLogTool: - """Logs and recalls architectural decisions with rationale.""" - - def __init__(self, memory_backend): - self.memory = memory_backend - - async def log_decision( - self, - project: str, - decision: str, - rationale: str, - alternatives: Optional[list[str]] = None, - context: Optional[str] = None, - tags: Optional[list[str]] = None, - ) -> dict: - """Log an architectural or design decision with full context. - - The agent calls this whenever a decision is made or discovered. - Future sessions can recall these to understand why choices were made. - """ - content_parts = [f"Decision: {decision}", f"Rationale: {rationale}"] - - if alternatives: - content_parts.append(f"Alternatives considered: {', '.join(alternatives)}") - - if context: - content_parts.append(f"Context: {context}") - - entry = MemoryEntry( - content=" | ".join(content_parts), - category="decision", - project=project, - tags=tags or ["decision"], - metadata={ - "decision": decision, - "rationale": rationale, - "alternatives": alternatives or [], - "context": context or "", - }, - ) - - entry_id = await self.memory.remember(entry) - - return { - "status": "logged", - "id": entry_id, - "decision": decision, - } - - async def recall_decisions( - self, - query: str, - project: str, - limit: int = 5, - ) -> list[dict]: - """Find past decisions relevant to the current situation.""" - results = await self.memory.recall( - query=query, - project=project, - category="decision", - limit=limit, - ) - - return [ - { - "id": r.entry.id, - "content": r.entry.content, - "score": r.score, - "metadata": r.entry.metadata, - } - for r in results - ] - - async def list_decisions(self, project: str) -> list[dict]: - """List all decisions for a project, newest first.""" - results = await self.memory.recall( - query="decision", - project=project, - category="decision", - limit=50, - ) - - return [ - { - "id": r.entry.id, - "content": r.entry.content, - "metadata": r.entry.metadata, - } - for r in results - ] diff --git a/agent/tools/knowledge_graph.py b/agent/tools/knowledge_graph.py deleted file mode 100755 index ff9e1c7..0000000 --- a/agent/tools/knowledge_graph.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Knowledge graph and compounding tools. - -The agent builds a knowledge graph over time by linking related facts, -decisions, and lessons. This is the "institutional knowledge" layer — -the agent gets smarter about your codebase with every session. - -Exposed as MCP tools: - - link_facts: Connect related memories - - find_patterns: Discover patterns across stored knowledge - - summarize_knowledge: Get a summary of what the agent knows about a project -""" - -from typing import Optional - -from agent.memory.backend import MemoryEntry - - -class KnowledgeGraphTool: - """Builds and queries the agent's knowledge graph over stored memories.""" - - def __init__(self, memory_backend): - self.memory = memory_backend - - async def link_facts( - self, - source_id: str, - target_id: str, - relationship: str, - project: str, - ) -> dict: - """Create a relationship between two stored memories. - - Example: link a "decision" to the "fact" that motivated it. - """ - entry = MemoryEntry( - content=f"Linked: {source_id} --[{relationship}]--> {target_id}", - category="link", - project=project, - tags=["knowledge-graph", relationship], - metadata={ - "source_id": source_id, - "target_id": target_id, - "relationship": relationship, - }, - ) - - entry_id = await self.memory.remember(entry) - - return { - "status": "linked", - "id": entry_id, - "source": source_id, - "target": target_id, - "relationship": relationship, - } - - async def find_patterns(self, project: str) -> dict: - """Analyze stored memories to surface patterns. - - Uses the reflect operation to cross-reference memories and find: - - Frequently co-occurring tags (emerging themes) - - Confidence decay (facts that need refreshing) - - Knowledge gaps (under-documented areas) - """ - insights = await self.memory.reflect(project=project) - - return { - "project": project, - "insights": insights, - "summary": f"Found {len(insights)} patterns across stored memories", - } - - async def summarize_knowledge(self, project: str) -> dict: - """Get a structured summary of everything the agent knows.""" - facts = await self.memory.recall( - query="*", project=project, category="fact", limit=50 - ) - decisions = await self.memory.recall( - query="*", project=project, category="decision", limit=50 - ) - preferences = await self.memory.recall( - query="*", project=project, category="preference", limit=50 - ) - lessons = await self.memory.recall( - query="*", project=project, category="lesson", limit=50 - ) - - return { - "project": project, - "knowledge": { - "facts": len(facts), - "decisions": len(decisions), - "preferences": len(preferences), - "lessons": len(lessons), - }, - "recent_facts": [r.entry.content for r in facts[:5]], - "recent_decisions": [r.entry.metadata.get("decision", "") for r in decisions[:5]], - } diff --git a/agent/tools/project_context.py b/agent/tools/project_context.py deleted file mode 100755 index 6158829..0000000 --- a/agent/tools/project_context.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Project context management tools. - -Tools the agent uses to build, maintain, and recall project-specific context. -These become MCP tools the Gemini agent can call via Google Cloud Agent Builder. -""" - -import json -from datetime import datetime, timezone -from typing import Optional - - -class ProjectContextTool: - """Manages project-level context: stack, conventions, architecture. - - Exposed as MCP tools: - - set_project_context: Define project stack, conventions, structure - - get_project_context: Recall current project context - - update_convention: Add or modify a coding convention - - list_projects: List all known projects - """ - - def __init__(self, memory_backend): - self.memory = memory_backend - - async def set_project_context( - self, - project: str, - stack: Optional[dict] = None, - conventions: Optional[list[str]] = None, - architecture: Optional[str] = None, - description: Optional[str] = None, - ) -> dict: - """Store project context in agent memory. - - Called when a developer first introduces their project, or when - the project's stack/conventions change. - """ - facts = [] - - if stack: - facts.append(f"Tech stack: {json.dumps(stack)}") - for key, value in stack.items(): - from agent.memory.backend import MemoryEntry - await self.memory.remember(MemoryEntry( - content=f"Project {project} uses {key}: {value}", - category="fact", - project=project, - tags=["stack", key], - )) - - if conventions: - for conv in conventions: - from agent.memory.backend import MemoryEntry - await self.memory.remember(MemoryEntry( - content=conv, - category="preference", - project=project, - tags=["convention"], - )) - - if architecture: - from agent.memory.backend import MemoryEntry - await self.memory.remember(MemoryEntry( - content=f"Architecture: {architecture}", - category="fact", - project=project, - tags=["architecture"], - )) - - return { - "status": "stored", - "project": project, - "stack_components": len(stack) if stack else 0, - "conventions": len(conventions) if conventions else 0, - "architecture_stored": architecture is not None, - } - - async def get_project_context(self, project: str) -> dict: - """Retrieve all remembered context for a project.""" - stack_facts = await self.memory.recall( - query=f"tech stack", project=project, category="fact" - ) - conventions = await self.memory.recall( - query=f"conventions", project=project, category="preference" - ) - architecture = await self.memory.recall( - query=f"architecture", project=project, category="fact" - ) - - return { - "project": project, - "stack": [r.entry.content for r in stack_facts], - "conventions": [r.entry.content for r in conventions], - "architecture": [r.entry.content for r in architecture], - "total_facts": len(stack_facts) + len(conventions) + len(architecture), - } diff --git a/requirements.txt b/requirements.txt index 908159d..a619f04 100755 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,6 @@ elasticsearch>=8.0 # Async support anyio>=4.0 + +# Shared memory + tool layer (extracted from this repo and perseus-qwen-memory) +perseus-agent-core @ git+https://github.com/tcconnally/perseus-agent-core.git diff --git a/tests/__pycache__/test_demo_paths.cpython-314-pytest-9.0.3.pyc b/tests/__pycache__/test_demo_paths.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54251e682a36f94136bbf41ea37635e228e06ab4 GIT binary patch literal 22768 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