From d1ec384b27258a3da352858273971b9d1dad01be Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Wed, 29 Apr 2026 03:27:33 -0500 Subject: [PATCH] rename crates/ to engrams/, bindings/ to receptors/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - crates/ β†’ engrams/ (Rust engrams live here) - bindings/ β†’ receptors/ (cross-language access points into the graph) - Cargo.toml workspace paths updated --- Cargo.lock | 1 + Cargo.toml | 24 +- engram-data-tx-log/conf | 4 + engram-data-tx-log/db | Bin 0 -> 524287 bytes engram-data-tx-log/snap.0000000000000060 | Bin 0 -> 69 bytes engram-data/conf | 4 + engram-data/db | Bin 0 -> 524287 bytes engram-data/snap.0000000000035912 | Bin 0 -> 596 bytes {crates => engrams}/engram-core/Cargo.toml | 1 + .../engram-core/src/activation.rs | 0 .../engram-core/src/consolidation.rs | 5 +- {crates => engrams}/engram-core/src/db.rs | 113 +- engrams/engram-core/src/edge_type.rs | 443 +++++ {crates => engrams}/engram-core/src/error.rs | 3 + {crates => engrams}/engram-core/src/graph.rs | 24 +- {crates => engrams}/engram-core/src/lib.rs | 8 +- .../engram-core/src/mem_storage.rs | 0 .../engram-core/src/migration.rs | 39 +- .../engram-core/src/salience.rs | 0 .../engram-core/src/storage.rs | 28 + {crates => engrams}/engram-core/src/types.rs | 77 +- {crates => engrams}/engram-core/src/vector.rs | 0 {crates => engrams}/engram-crypto/Cargo.toml | 0 .../engram-crypto/src/algorithm.rs | 0 .../engram-crypto/src/engine.rs | 0 .../engram-crypto/src/error.rs | 0 {crates => engrams}/engram-crypto/src/lib.rs | 0 .../engram-crypto/src/registry.rs | 0 {crates => engrams}/engram-ffi/Cargo.toml | 0 {crates => engrams}/engram-ffi/src/lib.rs | 0 {crates => engrams}/engram-jni/Cargo.toml | 0 {crates => engrams}/engram-jni/src/lib.rs | 0 {crates => engrams}/engram-migrate/Cargo.toml | 0 .../engram-migrate/src/main.rs | 0 .../engram-projection/Cargo.toml | 0 .../engram-projection/src/engine.rs | 17 +- .../engram-projection/src/error.rs | 0 .../engram-projection/src/lib.rs | 0 .../engram-projection/src/registry.rs | 0 .../engram-projection/src/schema.rs | 0 .../engram-reasoning/Cargo.toml | 0 .../engram-reasoning/src/engine.rs | 35 +- .../engram-reasoning/src/lib.rs | 2 +- .../engram-reasoning/src/tests.rs | 14 +- .../engram-reasoning/src/types.rs | 0 {crates => engrams}/engram-server/Cargo.toml | 0 {crates => engrams}/engram-server/src/auth.rs | 0 {crates => engrams}/engram-server/src/main.rs | 70 +- .../engram-server/src/routes/core.rs | 42 +- .../engram-server/src/routes/mod.rs | 0 .../engram-server/src/routes/projection.rs | 0 .../engram-server/src/routes/reasoning.rs | 0 .../engram-server/src/routes/swarm.rs | 0 .../engram-server/src/routes/sync.rs | 0 .../engram-server/src/routes/tx.rs | 0 .../engram-server/src/state.rs | 0 {crates => engrams}/engram-sync/Cargo.toml | 0 {crates => engrams}/engram-sync/src/client.rs | 0 {crates => engrams}/engram-sync/src/engine.rs | 0 {crates => engrams}/engram-sync/src/lib.rs | 0 {crates => engrams}/engram-sync/src/types.rs | 0 {crates => engrams}/engram-tx/Cargo.toml | 0 {crates => engrams}/engram-tx/src/command.rs | 0 {crates => engrams}/engram-tx/src/engine.rs | 54 +- {crates => engrams}/engram-tx/src/error.rs | 0 {crates => engrams}/engram-tx/src/lib.rs | 0 {crates => engrams}/engram-tx/src/log.rs | 0 examples/basic.rs | 17 +- {bindings => receptors}/go/README.md | 0 {bindings => receptors}/go/engram.go | 0 {bindings => receptors}/go/engram.h | 0 {bindings => receptors}/go/engram_test.go | 0 {bindings => receptors}/go/go.mod | 0 {bindings => receptors}/kotlin/README.md | 0 .../kotlin/build.gradle.kts | 0 .../kotlin/settings.gradle.kts | 0 .../kotlin/ai/neuron/engram/ActivatedNode.kt | 0 .../main/kotlin/ai/neuron/engram/EngramDb.kt | 0 .../kotlin/ai/neuron/engram/EngramEdge.kt | 0 .../kotlin/ai/neuron/engram/EngramNode.kt | 0 .../kotlin/ai/neuron/engram/EngramTypes.kt | 0 {bindings => receptors}/typescript/Cargo.toml | 0 {bindings => receptors}/typescript/README.md | 0 .../typescript/package.json | 0 .../typescript/src/index.ts | 0 {bindings => receptors}/typescript/src/lib.rs | 0 .../typescript/src/types.ts | 0 .../typescript/tsconfig.json | 0 studio/index.html | 1541 ++++++++++++++--- 89 files changed, 2114 insertions(+), 452 deletions(-) create mode 100644 engram-data-tx-log/conf create mode 100644 engram-data-tx-log/db create mode 100644 engram-data-tx-log/snap.0000000000000060 create mode 100644 engram-data/conf create mode 100644 engram-data/db create mode 100644 engram-data/snap.0000000000035912 rename {crates => engrams}/engram-core/Cargo.toml (97%) rename {crates => engrams}/engram-core/src/activation.rs (100%) rename {crates => engrams}/engram-core/src/consolidation.rs (98%) rename {crates => engrams}/engram-core/src/db.rs (79%) create mode 100644 engrams/engram-core/src/edge_type.rs rename {crates => engrams}/engram-core/src/error.rs (83%) rename {crates => engrams}/engram-core/src/graph.rs (75%) rename {crates => engrams}/engram-core/src/lib.rs (83%) rename {crates => engrams}/engram-core/src/mem_storage.rs (100%) rename {crates => engrams}/engram-core/src/migration.rs (92%) rename {crates => engrams}/engram-core/src/salience.rs (100%) rename {crates => engrams}/engram-core/src/storage.rs (84%) rename {crates => engrams}/engram-core/src/types.rs (68%) rename {crates => engrams}/engram-core/src/vector.rs (100%) rename {crates => engrams}/engram-crypto/Cargo.toml (100%) rename {crates => engrams}/engram-crypto/src/algorithm.rs (100%) rename {crates => engrams}/engram-crypto/src/engine.rs (100%) rename {crates => engrams}/engram-crypto/src/error.rs (100%) rename {crates => engrams}/engram-crypto/src/lib.rs (100%) rename {crates => engrams}/engram-crypto/src/registry.rs (100%) rename {crates => engrams}/engram-ffi/Cargo.toml (100%) rename {crates => engrams}/engram-ffi/src/lib.rs (100%) rename {crates => engrams}/engram-jni/Cargo.toml (100%) rename {crates => engrams}/engram-jni/src/lib.rs (100%) rename {crates => engrams}/engram-migrate/Cargo.toml (100%) rename {crates => engrams}/engram-migrate/src/main.rs (100%) rename {crates => engrams}/engram-projection/Cargo.toml (100%) rename {crates => engrams}/engram-projection/src/engine.rs (97%) rename {crates => engrams}/engram-projection/src/error.rs (100%) rename {crates => engrams}/engram-projection/src/lib.rs (100%) rename {crates => engrams}/engram-projection/src/registry.rs (100%) rename {crates => engrams}/engram-projection/src/schema.rs (100%) rename {crates => engrams}/engram-reasoning/Cargo.toml (100%) rename {crates => engrams}/engram-reasoning/src/engine.rs (96%) rename {crates => engrams}/engram-reasoning/src/lib.rs (99%) rename {crates => engrams}/engram-reasoning/src/tests.rs (98%) rename {crates => engrams}/engram-reasoning/src/types.rs (100%) rename {crates => engrams}/engram-server/Cargo.toml (100%) rename {crates => engrams}/engram-server/src/auth.rs (100%) rename {crates => engrams}/engram-server/src/main.rs (74%) rename {crates => engrams}/engram-server/src/routes/core.rs (84%) rename {crates => engrams}/engram-server/src/routes/mod.rs (100%) rename {crates => engrams}/engram-server/src/routes/projection.rs (100%) rename {crates => engrams}/engram-server/src/routes/reasoning.rs (100%) rename {crates => engrams}/engram-server/src/routes/swarm.rs (100%) rename {crates => engrams}/engram-server/src/routes/sync.rs (100%) rename {crates => engrams}/engram-server/src/routes/tx.rs (100%) rename {crates => engrams}/engram-server/src/state.rs (100%) rename {crates => engrams}/engram-sync/Cargo.toml (100%) rename {crates => engrams}/engram-sync/src/client.rs (100%) rename {crates => engrams}/engram-sync/src/engine.rs (100%) rename {crates => engrams}/engram-sync/src/lib.rs (100%) rename {crates => engrams}/engram-sync/src/types.rs (100%) rename {crates => engrams}/engram-tx/Cargo.toml (100%) rename {crates => engrams}/engram-tx/src/command.rs (100%) rename {crates => engrams}/engram-tx/src/engine.rs (93%) rename {crates => engrams}/engram-tx/src/error.rs (100%) rename {crates => engrams}/engram-tx/src/lib.rs (100%) rename {crates => engrams}/engram-tx/src/log.rs (100%) rename {bindings => receptors}/go/README.md (100%) rename {bindings => receptors}/go/engram.go (100%) rename {bindings => receptors}/go/engram.h (100%) rename {bindings => receptors}/go/engram_test.go (100%) rename {bindings => receptors}/go/go.mod (100%) rename {bindings => receptors}/kotlin/README.md (100%) rename {bindings => receptors}/kotlin/build.gradle.kts (100%) rename {bindings => receptors}/kotlin/settings.gradle.kts (100%) rename {bindings => receptors}/kotlin/src/main/kotlin/ai/neuron/engram/ActivatedNode.kt (100%) rename {bindings => receptors}/kotlin/src/main/kotlin/ai/neuron/engram/EngramDb.kt (100%) rename {bindings => receptors}/kotlin/src/main/kotlin/ai/neuron/engram/EngramEdge.kt (100%) rename {bindings => receptors}/kotlin/src/main/kotlin/ai/neuron/engram/EngramNode.kt (100%) rename {bindings => receptors}/kotlin/src/main/kotlin/ai/neuron/engram/EngramTypes.kt (100%) rename {bindings => receptors}/typescript/Cargo.toml (100%) rename {bindings => receptors}/typescript/README.md (100%) rename {bindings => receptors}/typescript/package.json (100%) rename {bindings => receptors}/typescript/src/index.ts (100%) rename {bindings => receptors}/typescript/src/lib.rs (100%) rename {bindings => receptors}/typescript/src/types.ts (100%) rename {bindings => receptors}/typescript/tsconfig.json (100%) diff --git a/Cargo.lock b/Cargo.lock index 0ec3e61..9851f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,6 +526,7 @@ dependencies = [ "instant-distance", "rusqlite", "serde", + "serde_json", "sled", "tempfile", "thiserror 1.0.69", diff --git a/Cargo.toml b/Cargo.toml index 8738c4d..0422c6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [workspace] resolver = "2" members = [ - "crates/engram-core", - "crates/engram-ffi", - "crates/engram-jni", - "crates/engram-migrate", - "crates/engram-sync", - "crates/engram-server", - "crates/engram-projection", - "crates/engram-tx", - "crates/engram-crypto", - "crates/engram-reasoning", - # engram-wasm is in bindings/ and compiled separately via wasm-pack + "engrams/engram-core", + "engrams/engram-ffi", + "engrams/engram-jni", + "engrams/engram-migrate", + "engrams/engram-sync", + "engrams/engram-server", + "engrams/engram-projection", + "engrams/engram-tx", + "engrams/engram-crypto", + "engrams/engram-reasoning", + # engram-wasm is in receptors/ and compiled separately via wasm-pack # (wasm targets can't be in the same workspace build as native targets) ] @@ -34,4 +34,4 @@ edition = "2021" migration = ["engram-core/migration"] [dependencies] -engram-core = { path = "crates/engram-core" } +engram-core = { path = "engrams/engram-core" } diff --git a/engram-data-tx-log/conf b/engram-data-tx-log/conf new file mode 100644 index 0000000..4154d7c --- /dev/null +++ b/engram-data-tx-log/conf @@ -0,0 +1,4 @@ +segment_size: 524288 +use_compression: false +version: 0.34 +vQΑ \ No newline at end of file diff --git a/engram-data-tx-log/db b/engram-data-tx-log/db new file mode 100644 index 0000000000000000000000000000000000000000..c4d313c7775b17c65ef6795ff695c1888ed4573d GIT binary patch literal 524287 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

|G5w$b)f?P#zG5=^ z<;G)cktk>OoR97cj!@sE%dk5M$ho2WU{z-kc}#U%}NV zH8F(M?hVD9H&SR|ZREEv>%XylfkEpr8Fp!BcL)kD2(XAio2r&)xI`N5GUA%-Y zn#c}SsDy~&V`$NmLOkj=6IXQmm*)~vq9W03-^Y_Tkc z_U)rf{%OT5;H30!p1*O(&4bII`m2NUm!NbG(D?{To-Ayj>b z2f)f=Z@tf?Df8MY1S%x_)=O*tJ6NsA_h2saO+lXiNZ8!U58v|~sCM^QEXsdNODkUx z1JOqq#lMppPU|BLGb}(Wt&toZeuI|26~{Z>QM?V+Y2@v&K9YL7k;|Ru9A*`6QWaGj z`YF#9%(Uy+qm!aAPH#8!*v|y1avg5{DvzLBMLsv5rs2om(yGhRWQeLNvqZ!S{H)|) zllex0D-9QoO3#og4#APPfNL$RmP4E@6b=(-{AX@ZSZ<2~h6lHDQDq`qYs zn_m(J%vb^Do8$&87^Mpl4;W;+YT=fU9(=AB#R-x};Jn-%2oM>EhH-bPXf?9tVvlII zP%H8=;=+#jhhi(>Tv9t!%!sGu zvZCNBH3?cZI%t8#74qWL5wu<(h`DEC;Z$BR*mEcRkz!S_QY(QwbxLB!qh};rU>x49 zNF|z|u9J-d**!LfYPAyhP!S=i%=@h8fd84oSlezQQhs>N~xX%pDSq4((G zUOU*TKb~2*D4NRw-%a{%1XA%IF1Yf10A?JwWUPuunq&p5l2NUlgqv<@v^L8^_Y>YQ z)U1(IwzuFVy(~C0M2#K2e=gc>-9 zSB1!a>Y<8JJD}P>8jF{=Vo@JU1$XE$yT5a;+iI5BGJYp^-B>{d#WvGoV-eu1-2)HC z9OcbExsD#W^N{+=7*cCxeOM-|1q!}4WXHEFWJHZ1uGf{K&I;nj2|H3~?boeDazz3t zswJSgsVb@Rvd2&5dazz|D}HM`0WI^VV6EU4h_*h0RWmDa%bLCTN-P@3K1rlvW;>XW znt6EUVgPyBrV3ljlF_u}5H#Rb`?nFP^-rjy z-e?f3bcR&rLyeweqe;5C35;+Y4$sqn5Z&?7L^3i8u;MARTH244_W0t(^6Oy5Gh)S5 zTglhc@2SH?6LL2-4l2_1*c=@Jn6Wbo?0vo2l-)g~=Jhi6#<2{l)h~c4eBHc0p^fk{ zR{=LAJ>YF$I}1Pg#<4r!D}W3C0^D(9D)jKBLvQ+SB2Z(8FZ9zJ-Asz`{ZdW(O5q$v z^Pi*-3M<)NJqqAjsmTHPC1$t#ZDz8Ij)}t(1Dq-(#%QgWj<0v8L8^HT=zC<5p`DXV zmaP*6f!2O{TUr&whHpXh3p41Fs8nzNi+T9K4K694*u#%t-|A5us)QF=} zoGdQLCo+Un9QZ47L5Ut-N~^t<+?4*tEH1lO$-EX}@fN!8@ z$lnmfz3tDr-=QFgT8Sz6pQ73Tz{ zf_Tzgn8Mc4brVFX*|MY1E*%IKU}YB%j58lC0siZ0fQKT=ecV zA-m4O&2tXW@q966kb_Kwdj}1e6Hevh(y_Mi0h<$O&bUm}McxPr9RKh+-Q+$7Tdvok z;RGZ(xrUh37Edz;WZ-t5C=EK61z&yvZY(b4^$z(?kB9qWf^sf=v+1bc_)Q$7#}1`; zFYYI26)4qo&xCmjd+A&QX*9}Aq{6-)v~2wtkeJF5q~VG<_w^h~7p?-yd!cx^yo}^6 zPlr8|;($Jp#0Ba{sk_-onDx$xlrH>$cQ{p5&J_zR>HSGQ-dY5{-g&4WWkK&f9SU=X z_=B|V1W@`iAGdrvLJz6;f=6RHxRfi9viK%K^f%yzh&sABoCj~eWr6JNET|(fcy><# z7Zkn;j9aE-xLz}zGj$QWey$^k%gUpX9--=LWAIkgJbcbq%PGos;hO0=aBfO12Hup% z9ZU`_tGP<`F5kwWv--3>qMZ%kUr3H|gUDZ2Ril+Sk6vAqjU%Q_rD{&MskWjagZ!(w zlmbDt3q+W%UJtJ~SHqXq#}NJ?4qcBGVjjOb-jshw>*kfi_tqTbfjF%iQU~Yvi%^}Y zBe4Ct+_ia-sJ$G4no~ zfQkPXdv6|%)%*7S3q=}`LKKQAgLDgh@>t6I6zl%PZlzEj9(!SCO&O^Auc7MllX2&TdE~52FnX%T zp_}X~>}VZfO^iwR6fx51G&C(v6f?@=?+LNZEdEeu@L z0wX0S;b{GrJa@sb>K=YGhHFk;@81Y?`$Q7F!IP~QjdOjM4Q+TW4PQE1-ymgv>f`uueFdRb@Ql}Xbq339)<}sCewYoaqwddgUjykhuQ;2S@-O{ut=v8xoUTibRLGj zTPpD4uiX&wHIL@L*2M%3KQ`ci7paN0#nuld*z$f0%$+2UUK59--l#kxRIG^BE{AA# zy&DF3r6CDq$v!$6#rN2fTS}rv0blR3zsoPyRLF_Yv6l95V!RrwvR1J6LVYAoVIo9%A6hjKa>u=vp)&Xq0$ynHa3x~xc~4~JHg8ZE@1QrYnHffc+O zI+Tpxc7evPAB>L|TT*h@g59600xOozq_6DSiG!^+DNmkEPpA#R?s!=o?EQ^utO_S5 z-H)J{lO2>+a4VF>lW_cxA+?!Rqp)>*5N;LSLKBL(U&Pxt5WUyaDcUte_x?`OaiN~Q zzCa(&6}qAR&(T=MhC_1MBa|I~5ZVSs!E=e@uVe!EFA(|Hq`e=8?`l^Z}#doQ^^ z>Ra_|kuRkEYbe=yvY84-2IKhO3rOX4YdZOS3fer%#)NCDk`0T+- z+Z*rcf~TfnD#2}4zLJINCyB(yaxjz*bwgf&1Qy>@Fw#$0j(e{ppr>UavDSFS9^U?t zPFq|=_KZJGo_5HYc>|VKAj^|mq_vQRX5SfN_mtRSITr|zNOktTWRTV0cMz&G``y-fghmtZ*cHh&;m)bVar*%h?ywM-NkwB)MihP2xQJHdHxp}n1+oN2 zL&c|1I`4!bhAo|eUffDxGFK)X6efw5Q(|g@jfC)6qcEJ@7Dk$~W?(G09djhk3vOQW z0&QjpQTV=+qYT&59%p&5m}^b5t>O(6@20^TUK`68te{_L7TjEujOB)xiEK&uAi!uD%pjEn|dg65rh*d=liQoe1# z9@BUyY)mtheHn^9IjZO|8Sv%C6@j}KuB{T)b@Lt2wqqGAWo9s!>$eh3dqtWkWeacbttID0i+B%1lj-ti z9sF{83g*m}C(q6$0X`dvM@9<5ug<|R>cVZ1<8BV!-?zhp26;5IQ6a;-4p8}d9<+8} z3{`j-PvZBEq#v}Z@Y<6K(jiz#&J>G-ZnZ3?wHzWNEayY>QD;NzoMn(-8H7>VNjNG| z43h_GqJMf6$xbYxagUqnJ>N`P&Zw|^cI}7Hr$un)Uz4E!4ziTPOrJlg)OYMlrGHL) zCin;H{yQwAA6N?EhcEJsDobR?igg|OgN^FPHT)hVeh(7A2Z`T<#P31k_aOcM+Jn@a zO@GCxvB;s9`v0D9{cr>d?|5;<$hOj>hVQ%O`)>KZTfXm>@4MyuZvRjCZhLd6cLkCA zFkT!n%s{kfq>#YC-a_;~B%zqEox7`(r-KtuS77L0qCi_X<)CX>)1EcS=&zAaJrp_ftI$NhOUm2lZJu5vzCUPqrSeKu9mK& zgOl!GL!ESW4V<0qbv5)f?Hn|84K)ok>>V8)I2D<*rv7vt2j}U|hJ8c(;oH;rk8iv` zVE%uO{;v_m{}|m5^y5E9_hTOWkJ0_OllXgdf20S0kM0kx@gJl6fo<%n?puH^7`Xg; z!dA0abkV#*VbPNo|6G8kE9z*S$!BQI9s+XprOU`i)`TZ~;_8vX)Py#;NF9b zl7?Np({8&-JB&m|`v7?r{)nbIij(*OVbmpbB_8)53?l}wz@(w+uqOBh4m&3TC(d>< z56K56-8>T8KAok{?i$0eY759;vjSdc8U~4dluitv{QC ztG5muFx)_w#@wR!zl?y{&yK>>bBAH4Yyt6FY>l0r?o?goo{E3UxKFNJtLu7p%6t(DBn+3J7mMe z?~=8f3U`xXd%22|eFNC6HzjO>5W*Z`Rim#L8i{+xZQ4HgJgr%{6QsXCBpW8DGq*3l zps|O?QN!hv;l4-{QGc`_^=;bV=Wla*e^(qD%T>d|Va1re+z7mu2hsU$ig3#~6q6!m z;n2dfWX9Am67+il^FC7vO~xJ~n|14%g$Y0x&Q64;?kkWaGaMfTpF#Vldx^}>85kfv z9MTOvSdD?rbjYVubZpLb91}2vZb?moPW3jnKzAiM^>_?>Rre{G^?4Jv*j%O7qkiIJ z)`In2laA%fb)m6_@;)t+Vv9bOgVM_bHBKmv60i5*=#Jm|lzLO9wzp zo`{hwZwnRe%p)`Owi8#qUnF$nXq=x=#>h;JBgZAR&`0q%>PtHKlBul$@$aiFt>25cGJPE*tbMnxQ&vyUu5FwdE}T*4D|^ZjgDq< z)a0Zw!p`6Hex?PSs20J?LJR4HY20r38z+Rb5!IgjRH3S5# zy*ML*tGlaTu!b2|V&kYZ)5*ch!`sW(wHN=v;|f8&{JoXzx!TcQ7!_BTSO5tQ=fi{hS>9{C%9JO8!-h(|7D!mHyufM*jo(dvx+7w}{Cz z-p74B5?CPss~VH?5y6UO13HbK((RTEp6XnGcpQzPoXSqG60tn;G1RJ&6~5Z)0<5B3%~yoj!TDl$_Zh ziaWknk?R|3m@~@1*n~Hhcw?a-*e6R-wNMxODD4*>c)N^j{aFpl?I)n4^(?(~Ukplo zV}V3Y2F7Fn4D(+GuPz0{z9EHhP-Pq}@L7)}+yeEcs?jMPT%nt-7rB!$o<8iB!j7|f zxG;DVDV4fO8;i1d^M`z*<2GKZS#21Hs|S1`LsK8%Fu4S5Gd_hwz8Zp}#3H;C*FnEK ze<5bIZ|Ie{lk`c(4Lp)^5k7M9W)_kGDeou4wAYt$LXs{oYsFV;;BuIGzjZvuxSP?D zGgYC+B$4W^j7G;Y1#rJ{8RlR3KwmCDN|r3Ihl#((A#c({G9x+^MATF0gY{;#@X$)U z5$whk%cyY3*hTocU>_A99!ab^^6BP7w`zVa&cuYQRq%A%Gn#w!2D3d%2I*ExCq6z* zju_Dzk$H5!jU2mk_8n%GxgdS}Z4_8ejiDVAMXFu=f56CrCai{5 z6%1CbC3XWtG4R$*n(1gubQ2c90P$2Z{Cqh1dGa~Y)IJR9siTo?Z=#P>Rmd#Dkx`W#+|{-1uux!%1n5E=qM?Ab(P9= zj)cw}G1``=5Bl2&V)s`sXe-fY#8N!rgXLVha`99mvAh8!Rpy|ww+1WP@{7z2?t+HO z<1}n?Io+ZVjI9GjsbF^#9(Njmalw1>On4kUt~MA}rYuGy<<(SCVtnnF*hToRI2?TT zWx(q*qw&7OcihnYhOBb=!G7C2mVRZdK;lURHFZML>O^_sRUe4L(kL)o_=Cd6bkKS= z8SZ|*L=H~=LC)t_gH*i?mi$06X8bW)8*a(%VV?uraq(0?RuG5pZvhFL8VpjIisOgR zhm5=KHMwKsNi=7ZT%t3EikFMQ3?TSDCxm_QA&91Ltt3)zCy4mmuf)_ljkc~XBsN+D z;99yZF&p;;V{-1(M^>`1>|P*@5te0UePHok*>ko$?=Xse7=%F&72$n^D8T7OZ2MzN z$n}ZBU*7X!hVCdRt_q}UOV8l9SMgA%_>?hCc#aD{TVtc591I>by5@&F|m*EH0~OXmdmPAj36ykfF8zM;k;)|wGpsLK(c z0BWsGVBDK5`uc(o+|kjYqj$!TOB+h*nadkcvjCdr;+apfc;Mb($Xap`lut!sV~-f!x2AVbqF zpTK(8o}gQU#^J}wqww1Nff!e(1>>*p#Nk)t;N0(KYB(nYhqWuRr^e^N`&DDn;JhK8 zs=ZEK-$lU6IpaX1*_xJxeW8B$o>6JJmr(0+6hwmUVCakyaGk!Sfms*H{f|Q+J$)TU z7aRn?Nd@#(c?Z{exCOcs4%3u1HAJ&C0R$7x;lNTk&@1$TZnc@%XBe32V&~&w$EkDH zyZZY11oM;vU7UJTN2zxc)s<^#@V9q&^>uM_ocb^FqMd_-lQ*XuaCch&M@8W4?d9p~ zYX2wfrVjfnp8Ae`@$}D4)2{NqnbdRhU*%SZ=ZfNkksks`S5I$v_R$m#9m>!lp^{cw zhLEbkMo=sgjk&)M)3fR;u>Fw=ozS9<-c#j?*U@N5w-&;f;R+C#=}1o-Aij=10Oz`f z8x0<)1~L{VU@TQll#Sx3`p8=VN7sXIng_1y@qp^%D)jJhAnoB}$k}(Ac=g9a8W8l?B3*5D23tc+342zbp z1{IUFbjut)Gf4Ja%hiykx$*UJ=PV*7bIbG9hTJzYfIC%VG-cblo5 ziy^I(R7d}9ZDg-w4y1Yr665VYbi9rX@h%Y4NBHd}6lP0A;rVt_DMe=0}Q+o|N|-AW>w zAzG_j6#&;4o6%+Ghmr1|(dg|t5E?h_1M&I0>9XA8oCy5{F(}HSWm?bKnWpQ&`i`X0 zG5IR?fUq2dPI7`1nqP=u<7r}=5lMF**@|t+4~e(v5c=8k6MI%ppf>t?Eu4FK1KsKp z;o~}EVm$5(QM;vs30CEpvfvOZDu}`CPdjm(?OW!TzclR0uAyH=0CA(bxrOm0d#9)-9qA=ROjtiOE#Hdthq?H6jxzQy`LqV=J20NB}lHY|xA$#XYaQeN16P9yy#N>ml@liRV zZ1aiOU(sL_2U{7XUgl==gUjfJLt%L6Q4(XQ_W=)aMJ|yFJ5ex7jSkgWg7G>^kgYWi z6IKnSx7KMQStg5yCyl^Kc^upVK_>Nq09jXfo#Nj(3oS7a~zpAsv(NX*fS%taOmpOu@O7dDIbJIZUo|C+ z{HwXT_*ssaIOzl}5#sDC=|Ta7(ApKj!ZjLqrOd+w0kPT zuV=C~TiYecSDj@{hEP15J~Io3jA>+!x~`|!Y8kK<4W=n+W%Q%46=*cMq5SOaFy_)h zl(e5fTz9I_Ba*q7OOd%zI5oq3f;=9-b}=pNK7t7Nv2EULdP4Wzf4z~@9ca^cid zSU$g-mq`Yw6$=`gKdxEb-8IhVp!nDp`%oL}^vzIFdn z^Yug{p|iU2!lx?sO|}V$Zx=@4@+e+wX$(eqa2Ss0-ul?Zue;aM!w6Y?s`eB0aEM{PxNHM8#Y@jLYR>RM1T~arGF#C8>Ck;_m z;)-qyF;=FGCR%RAiXnR-$Y4fI#S#_+N6Lb`ojP?tzm3Gdn+R9GUj(^KZ`6JJ7Eh7WdB(NAembX3ZdnItSyO<0G*9oQz?>-KC(4+hKd4 z2y{2DrkPWHQA|=EoW|K;YIZbCw}>M6a~B;Zs7@+Xp3-fmt3mI{cAR<1k_NctqyMB4 zMj3_DwC9#0RUDIrA9l#&($cLU-(&(yz4h_=o+u2NdXc28Y@{7iSAu5zDCl0CNfoSI zQ1V+5b8O^lsP-{Ki&J~4-S;$7#4&l5*wxVU^(0&Flg1QeSb%xn3V5t1iRU$3=&c3* zc)#Wi8Ojqkx_tZw-K%qrWV=a`6X_FS?Dgq%iE}B{9=Zdk9WI5y)v7ScaG25Th8T?I zCQwfrt4U|ZerO-2$3)cqrkT%flLt+yBj(t6kvb%B>V(-PjTswd%TIVff% z0cn>lp)BSwJ89A=8dYLJ$LY6noh=d&ByyTr?ZPpfeO2IdjRJa$^xzKl32;n#17dO< z5s4asvnp4jLds1tq*w_b-q_A$>+fW@zm+z8G_C-$ssh1WA`?TCCt;^;9361i61xXp zr*%@|P%8M2jDH~jPgipYh&F8ueshy}`c1;HIk}h+luL3#cf$4+(a`J~2u+CE)!Y!q;($7Q04HCb(n4<0j^hCTS>9`nO;ef`^W||6Bk@cz8hS5wC&Z;xZ*TY??b;s; z>A%i;i+BE1-~8*Lmw6)GJN~`Y^8~T3#NJ}l{%oZ2(U5#JBp(gQM?><_kbE@c|2H&b zZw1a@GdbfKyFxGgTQzBa>NJFpMGr8(>2GK()^)!(!1{9ypF+u}Q1U61dLdmC4 z{*R(i_GVe{c73h?sr<>?3v`+paee~;GF*V1>?=Gq+f9kiV^ zbPc8(YS`)P=ySnpuVce;kXp}w<*fq@~{ z%BZcQ>EQg2(f#S8{QI@-`q4`H_vrp~Q~qOgKN>34`+l1J>8Je1;QzXv^6$a@=%)O8 za6g(U{~p|rUdsO%+@Brbf9)yj7PT4)_tx(I*+l-=S#OclpQl|C(dN3?xwR5DG^jB9 zMo*w4=lI~OLnBBH8-n#+_o$zG6D-S7CrfWz;_Kwo)G^bS-cGp*n_p$&WB>iwpr}_< zic^uOj)ByP@>p~!j+MJ>Ky;>PgMsr7G-((H*51<@9kn!=aGLAM*iYDPVN>mp&jmAM2=x+hX!^oF)2RSEH*9~0z(GzCNF29WU0GeEwf6mPHE1<|~7xNQ|@2%oD4 z&=v=6DOu=yNB{)7mSUw}I3Dz!hLfXTKy2A?&>C*WQ=Tn=Puvqp5qvyS|l*g!kv zcaR&*6qIeqVpfL?B7<%Ok=J{C$O-RyGU1R09JCZ?7qjy0>y`p$Ve%0&{!|>?cx#9w zM(&`x7oD)v^#`-&$4SgTQiKj%#aqPHDm=T5vw&HaWBn{)kj}3H&G&Jz&xFA}g;^*& zND|yMGto(V5biqtji^}UGw%ik!<+&RDT`tCG*JWd^TS4GiR z!hzs><0^Bhr525*u4Kz98sXTiN>Fefg)5G#aW&7%xc);i==gRhHpbz7%L<}*p6**`%V@p${we)jU|{h z{)sf+aVKaoToqb)GxaU8qIct(iMw4oF3Kt=BMf*z$6O_O<~DTd%XqR;aUT}NE~gXB zEl{i=oqQ8aVHW#|qt>2EjCLIi(LGLNY={|3=ec2q#7}y5%V^lL`4idN;|JrzBj5oQ zCIaAx&%$5Qr4}D)LrEB}$-f5A-5-$|Qgw8s>tNiUbBbwcNW|23VbCz%$SiHYjw>(4 zkS*Gc)WG-#J7}pIi5|!D9<$}NwaE~NH_5{m`SEU`+3Wr69W;(R(}a_`Lr0jZ4Lk|PM}gGm$j`>;S+4xY#t zv1tVs`1*Q2z9}hXeuP#)ZD=9f+L%cjYhJL4ddn#;*TbyZN2JvymYOw(;e)CeHcWmX zOvxXMHZ`UwGvF&(`E&=>oOl~r&bG7V3#M{R^eIHE;2tBQxCf+7n;D0+Gr0JPB#C+w zO^MVf@^SY$Mso9aV%{?i<}J7ZTiBbH`-%8^ zUp`h}-45jzJ1KAFdI)*$OjUFenV;v?a6eZ8#m3wQ50ha0eZPtQWx9_io-d0t*d!dU zpTTuT=Yw#T8gY73!mgAHNA_7D4%Av$T^82ExIYtuU+ME<)7@ZrU!H=+XR5&|NETv7 zvs~Mr5ms@PY`T{o!p2e$o{F;;c6x~5_3yVy-q9_%{iFou3NFQiLOhz}aEKNdJ|~lE zw?W(YjqviP5Ohh*(vHS8F~T%7^BEjFCq@$5AJU%P*QxBVZnk@98mAX3gs3ZpAl$tS zedhk8>iWAmh~+F|!T}u}!;2yEhdS!ZOTme*hla9ymf`qIR?xJD!S6lUtmdsFRDZ_+ z=J51AaP*KooP9bP+=vVW$=A_CxtE!+q)JAH$py17A>@(C5$2r1J9PJwfzbs;c;o1I za4j4Jp-pR`VaG;t;?yo;FB=L1kK@_P3$t+5X(_sDz)Y;K-oY%&5=8qe)%1ml5R|V@ z#m~YEVf`t4`gy($O1_^%U*`zW#iLTtInxQh%zi;O)DU<*XfnQ1{6^28h{JUUHsLzw z#Z|XE&y$u9%BUGB!>*b=5@wZbr6Vsnvp)CU6W?EppsBr$s2-@Hexb7a}`eNO=TyxYH)(NmG&M zD_L-@{spV>L&9k4pkl_~H4_7e?S#b^0a#gOPQFb3NVF9s@%h#iYUI2dR|RgT8Y`Yq zPpVbxcKi^Xn=Oy_Q|^QNE<0w-pvCNklY_{^b!<(2#w;3`7fHN7&L`F9mk^y?3CwSP zN9OLn2}@QeQIo{Yoa!o?TIU7Oz{ghDx$-T^Gz`JLmX=`X@|4^TS3%p#IcT%T9m}+5 z!G(}}bXC(Ks(+ye+TywXa_+DEfVl-UYI-H_aQ8W86GTfd0fq=yh%t<}NfNb1W~RPJ}wH4Hd(hS7Wg;Xc;Pve#V?l zQpI1>D?rq~6e9}X;$CrGC}b^S0#CgdMP045 z=s?+%@LTmaeYR4?h!%Qb_>BgltTu_F)e)HJ@`^b!GKWLf*)ms5efNyc%BiQv%^RuZ%Y96Kx-N>2)FEnz^Qf|?EyO9- z(2^UvI40HsY$l(iW5=9elh$zWNf#?9^9rWc2MH!9?ZtaC2{1M53voGF#N4m^$_#Uu zjRP}$K)tAiS?JP;1J$yK>>R=tWN)M2r)Yuk_^Z_I^E7C@ejI*(+kuTqn@G)@z4X(N zZQy5IgZ|5JL-??fRNCStjlX;pEq}hHYn!sz9dGK$sQFPa;C&0POG2qRlUBv2(pj(a zaoP8)TzDn~+a78%M1MMjKFGkW%89hjx)w)8##A?H4l?q$nug(ib3tZiIBirO z3VZ6G(q|?HM(&$ek%9gr@xn?iypTJWM2aV(t-l-_EH{KENt|Xpo*0m4<0J5qm^w^# z_n>P|O$0&XbyyvBh8}9#MVfx5K)|9@S|^`_4#wkf&Fs|>HaUz&G#i7#+O-hUnvMsY z%s_MUeQZ>?jJxq7b-(NgPM3m+^(rY`5iL!M5|rTMo z+{sqG{q%5a2)=3DOP&;^5b=eTVDtPah)#;26STHfwQRD1gD;wDRuv3|VB2E)#o;V! zX+|@N&xWJB>~&IpPzIzX{3fYOl8LrK9Ie=$NYEw)m+RVNpim0Qd#nwbS2xnf95))Xm)j+Yd~^if@HXL)J0kvBVgq)n#CV z_(#;JPNs1?&0tSdr|=wPIV)<1fuAb ztLxdFfq->gD_|GApf8t9p&O>xl7{+vFg;tvjNNjP^!O@~v_axDg2(+8+_Mmj(v$H1 zwV^nBPyih`Hwe#K4=}o-$90xO6yiE|DSTXf9gc1tO(W;nBK@w7mYuIi)xI0#x=j|W zYgWUwC26&SHB#!Gj(Ci zkfoqkKLsqV?!@Omm*NJcNF+-d$hfoq%%al8tdP+|Fw5}(tw$2%-WDZ{G&&D4+NUy?Ifyr8#WA4pDQ(=om$XcrLQTaE5KDQM*)dxVAB^4(-voKockEtP&OCw`E2u-* zI7uiqDW`R7_JG`=1nOv!1+%Owc#2Q#AaVObG@Q7LJQuP;t&+>ssaAkSR4#`Z-vq#L z{x8<9`69vclZYQ|aQDk5HbpH2ZXYEwr91#?#)T?64#5r0wpG8VAK!B=mU+7*=1Rmcuh);F&N~w4GWzFVT}dXHZMD zmm1N!>?h<|<_l`MR;;F}J{e^vB-6n!o$;~6Te9O(JQ;iCHl7kqrs;5k+B^7@B!_66 zk}U~YX|WLEQBJI0y(M;`i>VXW#i2iDK2ckki9wp85b!0Am_!`o?w7|9DlijNzHTCy z41(z@{69iYUA- zu?z}+C1BV!Us|eY4v&ZIgLy#>#3Q^F&HY88bk_lVn67}Xv$jBpl@#t8F#|4!u7vyx z1Hch4G3&1<(->P8k9}T^Cm*My-mMk%-Z)jTuuvzCD~rKu%~`TwRUG6t%r>~=DMR)L z9-~&mCt12xlYQZqO7{jS(*aXXtvnZ5_;<2a4 z$qoCRWbD^b@YG5iy-Ev7iN|V8dS8PQ)66k^*BN&6?)~JB#bG#Tw+pgU#X;EUC4H;1 zhiWHOfRtVg=)cyY4&kjd-{U*EC32WLO6`QtKlRYW#EM?Yp9?BGcHn?Z5%^YiAfEEh zho&=E$>b6<~lyV#Yz@MpDV+h0-uNRFxUvtA=}0 zgOF=^h0ZfSFmlN^>buw(51b5Q)~3FqNtPF=>Ab0MabFi*_9P5De=Ub4H+j&&W#Nmf zZ;0rc@z}b53n!*MPNk$|$)macs3awhS&iq(o!XJGMEp4~Q8FGbJV|C}jcda%v5(10 z$M^V3xdxjYPSfoYABfD>7sNd(mSc35OTd*Wx}}CHoRn&m;sf4HyLD(9H49Xc6AJ5gZ@Z6I)jvqZZrlQ)lP9 zAS1n>oiXqst&lPz2g16z9nkq{ z$ZrMaw*vE9f%&b#{8r%qycO7C?&qC<$zJ+XQP#!RMe}vhd|fnO7tPm2^L5exUApMr zV%0uf^eJBar+;f9>QC+RpQ_h*6-g(tuBP4=qW)aNH&gS?)O<5F-%QOnQ}fN#|7XqA z|7;pMe&|W~zXf1_+HwA{CA1Cwy~Vm-3JetJvz+xqh{pG0@%>nQKNjDQ#rI?J{aF74 z{aF9xON{;rEB`$k`_X**XL3l^q)PR22>;H;eq7nb7jp51TznxHU&zH5a`A;+|1*SK z|C}oh&#id#Z+X}syn=A=f@_$d_BgSw*1z+xKUe&4ag8o+*-NdEr!SnLN!=+6@B_TxvE{_nBak1PLnm$N0)IzH*GO z9Q&W39P3>@?Ng53UPMH>SRD8dI&Xi7dBUS>q>b|$E$@qUeGw4t+ga?-m0gW}zG!Lf zCgmwdX2;}7+{!*OUo^AVh}P$e=CkPdEIK}mj?bdwv*`ZsWYMkfowjiwGv>@yY7I|U zn&I!xsW|QYoE(+rIC(nx*tsjs_4IRccXwUy_?45kL(-~fzaN5(K`|RMq$=%ngsL_DzrN=09Y*PG`xRtNO-0gb8_xdDT>qx` zo9W9)KCO>W>*Le<__RJgt?&O%TA#(AgcJ~1>gu89tHg<>9bFy#cuHzMPFySFlex+mm}xb<}y*_UuSj# zV*N@;6|H`JK^3ijK-X2Y`T<+-mu9@n^Z;y-4?d~z|LT+AmI z^U1}0a`FG2g~NoRl1$+@1Vfy*!ly?R1_7O{`MEoXn)JfXjTdBS}KFBlLo0B&55gsga7 zENbk835I&$GIlv^;6TqyciEwz!$rD#j~H|tTj7{xMsQB+APtw9#J)^CN>)#@pk>?B zAf{9bt%prPpXY# z%B{mH^{#j-IeYmit#|UX{quxd!*z9bb+GHrsmr7NujO#r(Rb_%orf>-{?xQ`uVnl; zcui4QNxZb(Z2r!{;;O~8HSC|E!{<3qL=yWNwIA+;-2r(ZJv|+>H&|o9^J%#A*)aU{ z?gHujoj}_3LP?nV7ovW=l6X75Cz0EZ;`HEQc+mDI8P|9Tr@h<-^;KJ;yn7&dy*7uO z3*AbUmnK8|Bq`9pqDKbk7NXD_AJ7mnz_X_Y!T#Z~R8=U6?8-d}>YESY;jFQALB}fm z_*fh7yF`%#nR-~~8jVt;)KF~m75G)r%JwXgMcsf8e4aC(=gj9h^LftyEj(xOze1;X z^H0gn-_iA7(X;XP@aw$+(|7C(m>n;U`~k212^hz|SXsDoy!`YM8NKZbZTd2(R(lacq175i(w@`rRRig*gPezn+l%?QXg)5QkBjExqW^PTwD3OyCi$SmpH%5P{xenn8fN_WhK}CaHYJ-q^S4y4 zH<$mkAz)_jEA^$ypf*{u;dKHLuCss(%XYwSaRbVpUkBcu&q2FWkQ~vuOkRK5jrWYR z=!5I0*o~b$`b^?Bxvam6xg{h?-Sy3|{bU|7z8QfV4ewB^q}lh1H;7ht z15UeFK;9}_AWYl{dGdPLGNKrp?q?ADMTPKjR0+)Wj)U#e=K(n7-m@teiQM-iBz=n^ zJxK3KGUDhnkYmTh;>oX9|X3oZGtt|3Ax5i5jM!zV~-$1!*lX!gn^vV zxAe_0>hVl+M9&8rMDp-xt0GD$R?CayB)y8ncR+7(a2i zapoo-?98EG2aJU3H(^BFcLw?5_LH0{T!1n+nhg)giDO-33z>TCHijI^q76Ao%=zVG z$oMq;7aOi+Gw``IxI%e=cBLlu9P}c#{=R-{`plVXXZHgQf`Lsqhk%Hx#nTUW^J-( zgCl0uh@->Mo9O>25A8Uy#Lc5m=#*MVkh>fY?M05f1L~&axp6Lj2z$V^wmhJw58CL! znOzXq9L0zn90_vo3&@J%EZlx}A{702NH%J6B3l0l8dTv%s$Nzx+t2PKWhGLuT`871 znfIAWIcwnhA)07uxeM-mA{dJ(>kf8;4&22Fav`#b`jlW`e&D98O6Q)xM{bUuMD8cdBP~NL zan-Ghblt5v^v>BB@Q(`z{Xm zChF69BeTe|t!puK=VE5g=``>bo(Xz;<#6(xkaGr%Cgq(xm!by6?W-zt8%8*W+2g z=ULCa)^lBdTzkuLN_*|D_j&H~c)#B5bmaO*wnp>?1z!Q|(@POQAqAiVenMx0~ zSHYaON~pGWE_q>AgF}zD!W*eXbkO#PlcRKT-^z08rdr1=t{Mzmn&aWdHFuQAD8P)e zQgrtS;KnLi!E{$~n)$Yvs`(2Sln9Ig(&OjCl#;K3Q| zF>#z2aca>+=RL<*`e-c?zZFOQEapLpZ8|zD_mD43qv@T1dhC7m6~Z`^DaWCPil>yJ zh)ODqrbDTfxG^SkI-uscNK?!4cTGfxas#pV5^n_?np$RpE7amS%vPdTs0GbgMXz~Jcy zoW8G@xU@Q=mqixZD7DZ_4x*_2W(#VzXyNFeNyO^HFbthmNw1wcOxo*;*<~^3nb)DN zpzXIFo5JsaRV5`w?JBUgn9}H2W7fj12s4i;!tV_o+g036n^|EY5g}e7fgF{HkdQzX zZ~yR6j|eY6?j8+ykH?GKxTxYVahAS{se#U~0&a7^UGUN{k08B35AOH=s`3d}nH~}m z=pU>v{r85>@8fTg+Ly@x%NW;jc7}B?`+Mm_JfoMmZ?h?=_gXY!$%yc_{_5W%#rLp~ zl$>}EUZXv*zPJ^nhQ(4f{#?x5pH2^4uBOvQX48)|_v6b8$LQ@{w}`!&0f|f80Ur4$ zVW~R8kW<&1rkgH9zJ*udSzb6y8aWQn?IqkaY(G@89|H?Fy{Ch@eBAc`Im{@5bwI(m=?47^tQyup%5KRaxME|4w)Gb?!46Z8z8{KehcI_d;b41{kZWmPC zl%X3}<&peRsbs~ZJh+i70Y$OtD8I)QGB;;4X2%9$gPc8#-+r1cf24<(KJKT>^QDN_ zFjaK#7)#RyzcD$Rlc3sK2r}Nu;1pwPs1OVw->vjvXz@gxKj9hff!YjTgT)5yPnf!PON(jGWL#iz~^Gfz)w)W#(I>cx<&QrTWe{5RD^z;;ovoRZ4Tn1xpn{GV@jrsZ zczz}>W!tAOK_?o5@afQHGDIftzU@zVmIu-NO8EJm3@2S$v0Dh$VvGN(-1V zcMG|FW+XG^rOPun7_u zd?42El%QsfC(c%0j{*6~*j6mT&2HR=o^uXT|Mc^)Jsrqt;VM|xRm=ol?jR#p4I^JO zI_bVM_IO~~2l6wl24&R4S^bX@nD=Td)=YnZHC?K(_I3qF*ZV%T6%YaWbvCH5fLOZG}-#NQE}Xfrz1JOP73jPXK9A!v_# zP1|yMn+`<@unHv#7|~lr7yZ4anZs?R_wer5ArG%#Mz#JpsYL$j(C2m`a8)bRPnWwn6rr+wg-=+NzU1Fx z_51i+tp3Ts>^<;XtS0Pg^cy>F=E+KdO6h>rvbz5it0mjKm=3;i$T1xPYuostuh5C= zcgtb%w%4?5_cLN9aUbLO3#hqKFF8DM0_bHmk-9-wXxVEiywM%USyPimUW@dS%0V*zQo2gjG^WxIg>GP@-@w+5jl)Fa?aYG!E2OFonD%89f?+lJ)N+EZJo1`WpN}Vk z8XC+ZaeD}`SA?Ms&*9EnYc&3Olzw&QXXBN()2U{07_Je4Jxw+kxFU!~3%;c(Q)QZN zsb-SgdjiecNh0jFohyKm6JWj$TZ2VI4I%OlgUrKv=oB@EPmK~dX4pYEqc|Oc#7Cle z;!P@1gRHIOecCPBhJ1_^Zjro1?jCo=g7j11ADstw=Bt@~%?x>dV@UIdWH&tX`WxXq zt;YN^1sctrN$o#XL-Z!{!_j4@>3P}3)bqU+>{uCraRN^C_#41IOP-U7%RM=PABK}E z!84fNn~3&y@31p`3Md~5fi))@z`1-b-M3i)DRZ=E`WWX z%*pPBqiE^owKRL}aH4!F044MS;Ov)3$Vhlh)Wo%EgYi4EJ6H!tYwe*oCTijCZ-#V9 z`c*PrZz}_vJ0LgcI;ZyJR&1DBhtp>}TUsUFZNdJ zox?Eu{u7cbFcNQ8W)j^Gmr0C3E=-sa2tgZ@P>#<8A|xW1xjo*Pe6*aIc-M~oW)ln- z^;pXB9m6&W-=YgYIzpQ1XlCyGcqqHLo%CJ_rc&QsaOs&K%syhr*q02k$_djTL)*Ft z*WJ=I#U=+mkNSeJO%vIDtrgE1=Rmul7CU^;40KG}O2h39aGg^x&W(RguPJ1q`Ah^t1IzD> z)0=tR)lL*yKhzCgZhlH;bGkW-Pp?5Eb!=++)`EwBxf6bHWgQn)jsjx+68G(C9ZF7=l;r&Cl-!CgTQl>DcXEnhE?!L>rT+E9irP?EAt z*^)u)zoe02OHx2tD+O(>HAuD3Ec{Sm46Ak1@axH=&^mh@)(c&L_$ddmdQv4OE#HMN zB;!%;Q7VK}Rf!VBo`C>NEQo_y{=RI+_8wCE(w)6>D4Xhi z6Tl3qlG}A?Hj!=s{xXOlF;_-M7k(06I`rosrUE=FlYEII?veyHMV8o zA}?KVzbkao}8HI=KZ=BPt-V_Yt|=4nQ&Y+3T% zUYa`nEmnUmjkrrGZd+WaUsO1^;~-EaBFw|fM~Nt|w4`}FP7lolsq`5zj%}dPVH#65Wu`u-XH8$g(o0$BgdH^U8UJxHIh>sV< z#|z@)1@ZBM`2OWVe1E8wd&g-niyt@fb;`S%$g<(LAN}(IZ@un*oF%XUb>|);ySfSP z96JUlcMFjAPX_p4jviR98jm?adT3vs1Yg#Dr?XRzV2Qgl8qF(U8ba>Cp>NB$n~$-K zRrdtAo?MTo!aK2}HiJ4Rf1_Qm=8|IpVz~KRBe@*V#2i!m!LEPdh*xGtfM=Er)r|3@ zcQ^gO@|Vse?fYR+dwLMwJUC9z+!BYX@MIwC#sOo)4}(`Y!?QC{P%N$j+Yw8OO4~D#_>|){%xsozfk_a zl5PGv>#5vsCDacN`RlFUNB1i@H8MtZns{&ZuiTpfY|`-{iZ!dvFeV@siAo~L&mCkX znw(3z+7qG9MG|rx6-iZb8EWm|K@_JXY{GQ@IGicwiDS&w$;#4KBtXdqO|~gxw%SK=aKb@#D~QG_`aCW4*DFUTl8C zD8#&GO7_{Z3*zJGC)!MQ+!tf`_IqMleIr`88qy0_uF@YrC&G&R63rbq0LK?xA(s|O z!+e1vCi_b|>!0fmlaHLh`l?mHgDCPKiadzo|5k`%zh3=qhOnOd&i>SYrr8XjMMTIl zpvr1?lJqz6-n?J>Hh?jIED$_iV-N>G6fA=^3ZPGRdRU7YufRdA8yHyLWyy;G{7&04DK1v7Ec+{v~c$a8u2Y3cAOJz z%8wOB!_Jw+biO~i)JS2cuO*akyt(qcUzt# zD__lHo^R-&n|ZuO9Kt&9lEv5O%lIvIy%33CT5gfI z0y0+9%C`}%&NX=b`dlKndK>H>>V>s#2GBcB1Qidh2hkv1(1=SSr&Di1edUfR{Yr zB@cMX177|sftUT7*8iY$Yr#V6jvvKuxVk9B_s30bV&q}!X<=reXJl%`^YfnQnBd=dAD-(k%wu|@P?AOJCjN!30d2CG{Ta(Au>+5T65j#@qKjhHV+|bTXJp`|BxlTWBo z`L0#w0e!E(dsqfC=1&g`594`)kgccT>S-sq_jDmYK;DH3 zxbx!=^1gFE`1=;3Y1{;Q>#;CQ7YqbhhcTe?c{V0}JxKR!e*~|l3LeIjhw5^|mna)bsW*_T@sjOw9B=ExBwh3kyqcZ!aTb z1213WUrW2LbR9kM-n0EnyMc`15kq;zP#!UqM-1f=LwUr||FguK7)Ja*7v)Nsn6yFYlCQU2Q*}oRj*P2tA_t)4N zuU_EC;Mw?kqz0x%W)lq?LzwpS6ggCuK*#rOr(=_LlZK}~G%X?pHN%=|)u|HNuq>X= zSdaro4GpL;w}7&*7NQbUNkissp@RM|iSU*i+{o!I?9)dt$~zV4M9v9Jcj3DD zr)mOpr$G0HLJY6w1HRt5*boti72)G?T;gL$J}?9fhfLt8+4EsnUm=0c*oeKis>m^`MEd3n!;#HxAwRU* z=o`h&#jnS z7a0o63<}W4NCfkaekJM-rOYdlD44u{3Yn3c2Z!@Ezy~&wT&wxSnB{N6dt-Fb#3g|~ z6N-fJD;JqF=bO<=e<6F|P&@3iYXBwR5x8KlCYnxA!=-O`gGt^=FmkLTrP2H_LpFmL z^_r5^E=FMZU@d90{XqgmONrp3S}-3Uk5!p9mM>#i7%eDG*f;6WdA$&n<@Zy2YiY)H zMFvgv&ILne6^x1Npy5@s=#7*P66leGvkU9UFbfXQQ5Q+c6gR5>B$X^zF2>5_`E=|Q z2NW;MAzuYHFisItXt=cj6a5E4;ujw>di4a9Em?+Jq`%YSYe&M`HSbBPkSWm@QP|3SF3=m>MZEd#Tk3W1*Wa%S$+%ee4N z5?O21PA#mjup)CcN#YombC0c~56)WRkh6-gR`E5x<@%k@-?Is@(+-_gHiFaUX4dc1 zRa&|$nHiO^4rB!6n|k_eVa}{URo)7+wjGSureo;THIyWD zB~l_Yg1pN=!30%@-dLGxG!+7LHWQkCLBHfOydUk+xI%~fr5@=-UY?^Xsr zFD}Ky7dJwkLoVekTnein`BHV`4CebuP24F|M%koJ2(pR7pSRAkKWvLRQl;`ZknUmqHjdO>$(lN``BUdStSoi zBUvs>+6o&j2V0q(xdY2(7ldAEJEKH&O%kFp1rK50330Oi=^gqd|1y;y{F(hMxrv*jz6%mA>;j?B z&KNfJJJmJMC!5Rdh)v-{^p4vN>-u!jTu}xN_TI6S-|CF2XJ)}!R|da**~S`NtD)wb z`I$XtTVZdtA{@Uz5|$A;Sf$uPt9P7ZVlx{UIc5h;__&(fwW(oFSiHi(5IGoGR*6^k zegpqqA`o-d71}m0CkKz@5l{IT;JcT~o|{>SE=Ogk3;!f+J-nHjT_}K_7Y@_MHiA&M zC>uWr&4i^#Jn4t&ZaDPyWcs{_k2;OmfW8Gj_|g6`31}klTx1+RQ~pX%9!$X{<!@;g9jSfHK%ZA;2O~QZ7hOLPG!h z>9oQR`z_ers|Y%k8Z1%e?J<2DnF%f{WGbBJIgFR zHk+=}bf=Z?H^O0G8T!^Giuyd^s@WwImNU$hsCup^F=_9}$_+2^jXI#uhC6h*u{t;Y zRvOGlxWK1l>oMn6caw&(5K51TfEMRdbjQOlMEy+`y%3iO(~4a1+_fdpIy#ReiI~Eh z!H39!vqH_&d|F}C?EvhZ;e%c0j7aRboz#1zHVoQ%mpLBj$c9Un;rFBYm>Ez`zqg2? zT+(((n0E**o_dhvZ7ZQiDg#RGhC$MURd}RN9X<#QCWCB^pmdHc-V2were$g1@U;=` z8nxgRo*+7FH8$9sZ0KxN|=y<+F zy5vG3TxxyHD)mWQ>5J@UR`?fSC|nC=&5rH0`okWQ6f(4sd^D|X9?TXy2Xx0maHS8@1~Q(C+8639nx6Z z@rq2%zY22}s8E}XH6-j=BAr*Vl1AQ}g*^*jk^;-sxZTkaEdB12&Ny{k+%Os4wg%z> zBRe>?`X+TbTTRVReSz*&E4;w{l;@vXMia~$ID0;yV1`etB4_3Hf%PhDI1^(FE+rc= zdiqncs56g<@0vg-?=IuS44X+W$lbw1Ez9Yvof&kC-hNj1%1TUewZNO})6rN^1|v1q z@u_Y$Iw+=5h08{8=A~@ zvka-Qd_DZs_(>lwRJWqLLNM-18&T8LrZ}qxwEUhiNBM_ha7!i;zAZ;CaC6(og>S|q z&1R(KiVXQ*J%z7k(su^_9%6hEL33 zFMAZ;5(c`J=b4#)?I^5SNaQCIwrpEE{ib6G)~XljvJc~-{qlbJ`E@h4XGW8z7u)H3 ziFAmtZo(DwJ0WiHa4PHYgr=U`i;mx4Qunik?B*9OWW@9Y;D3Fd(<`mgQ9v70v#H&) zQgr@ykvxu8!I{TaWA_~chM1c{%z{M*R*eC@Fnkjvh3F@*S4^`vwuDCAf z2L@5sBU&I}y#x;@9HZ4|^T^rn8(`(^Y}%q&gkIKT(A9ns#Ey%l@g3G+;qDHrALL-U z&jc_ScMIE<&S5^Drh(_Y!RO2>GS5W@7bMD(%JnMn?w%z03G(67-EL%Qi5mzjQFigd zR$^Ks3h_@_`0%EfHTh}@U(ADGSYjIq3^1h1A9bKceKj6)Iu3q4>ru$I00sr5)7NWr zpj<4DesQQH4@0?{`HcgqAw9UxmP7U}lViUY2tm786nO740sSdgiK}BS<(p{$f#u!A z=U_Z)wbnpr;&SThoCrzFteIhZpRqoxTF8Q@3yIj<`5@R415IQm#3&oXx{*jTU#MZI zQXok)-AVU6SdA~*x09~Q4Mb{Y1Gqif3u4;wbgW@|(@o)$R%3mgR6>=am_9w}nlSPayQs|-l41#VOaK4ErMhb2qCHIWL;No(6uZ}=K z{tDE{iNnsL(?M$8EA)K$94B9^Bq`3ebkxfsq-2O8s@xYvp*R7uq;WKqP8tiPwv#dM zfm;N$xjW2;- zxVV(fjRb7zT>yFTm_C`KLj%m3Nn2|x*dA|WMz1|hzJ#lgO(Ie>p2PhV+&UAiax(GO zB}udwSxJSbuEOK<_^mFOE=8I6UATmu3-6pR!``%!blv2|NWU4OW6v|vSbT+Cb}NJ> z9h$f)!Gik9jiJRu)u6Ec60>JQ5j0M1htkAKnDE35TD-d8b>J?#d`~iLH?KpZ0uvY| zF&9i*b->|bEoI@;p86%<6)Q*Vbtu$y&=quk{I85?J!rB)t!BsdEVtIkoMWbMLmRzJAtpK7_QN=z@uGWRN~qU zyx#PH*i6+!?(K`5#>Ig97SfsqH;j5_Mn`It6N{*I9Jl$8sB%>mZk#4TP6{taq1}1p z&0H69uFV4Z1kz~N_b;UT#^xq3M>gX=sbhb4M<;P~xK~H?~p7cwy`EDv1eW4SNh-J|nI7mIcR*+1uMAX?f z6bd&b!|I?qGV9q(;t}ISeZ&`_`Kakcb7ldqG7y86A5(};{66k{c@&|1lR*7bG&y4t zMWdzbQ0Br9yqf0&&1c2&=nH!+58a9iO~a_u`o%Qh&35{M8wCGw?>eAKi7@X_0(MF} zL)nk@7<(z4)+$eddlJPkZB-iyihF=lR)|4uUOC>$Q9^&awXk}Y4CW1+2&ZEfLg^`f z@WwOD(#u&iX)%lYJ}koedpT%&Z2`SGMgtribcy%E-7w4bIGN#+0z2C5Ev^U4k)4tI z=q#anmZll7kC$cB?W@!%zfL|>NeYoZo14IH_Jd_(kI)afqE;cRCSvqVKN5O=FVSt^ zOI7Y%pbowb*eiXwVk@(HX$0-DI85^8S3&;v!T3?9i2khnK^j_pa3^5uq|5(gsh&>m+D)o z(fUImW10l!&kd9#L^g%)rlA6J=hsUPmu_^z1Hf680 z&Cwn@Y+?mjMzYDhnX)i0MuXuG*Tk*atB`J30zJq2aNV4*G~CG-%j;J$?%B_1rsFAU zJ53)>7xz-|-_7v{?Ba5G|{5gY0+egC9yF*FtqmiW8uZ(VdUrrwgYm;4y;_OAyy-f9MKJ3d4 zfH~);$zZ{*P% z|Ci7kd!_w+GNDy|2N?4CGnLmgQN1+;M=0OsO!8QQwug$aP-r}{AMnd@`jRX-pFvhD30konBcd?o2m5?x3S?~1CkE-0dY0 zeqA06itjT``9Y`@-AX2YPiA&#E5VX3AfgY00TfKxQwt0UXUQ;lRS=917Ve`C>9?q_ z?+uWX-N}yEmx5WJMPbg)mGr!26}p_C19~S9(1dNHVWHgyjGb|mDt<_V4a15cwwJpD zI(-wq<@={}=X$M+wCi zK3L&wjiq``=)cY!mgkn!Jhw>L?B#*g@0FUPV^)y>Nim{Qy%%macax>pQi$G3KG2#6Es??1i<^x1Nh59LTTz9mX?6Tot2ABE*PVEAkt-1u;Y zRE+B*Crb~5OsgDL^&uIhx{o%;ITF?M6R>zpDm70Qz#%)&gS1-{u2R=W)gjYi%ZUVA$9NQ#suPEaK=519YWDWqRWv89fygX7NTjBIBDSHM=z~SOh?^llT*_HY zCXDIAq@r8&?kst5z8MLlgyb2!w=CW~@QAG|*@NP5MR3&}Wq2Jg25@vX`}Ce8>qR9x@-55#TYmedAXQ|Mle4nvh{|IM(n1}7k3NT1yWYb${p#n$tVqd2xZR2~# z3s2^SC;vOblmF>S@;W>v>OVY51L->c1(?YG^0ZUD_rV`M9LUH&Ihee(W?ot|FRhuE z*33(5=A||N8`7HleIQ)#(JxlTB1pdfpRzard(AJa?w)s9Z~x!2IDnCVWRVvS&5MWT z#Y6Mrp?UGpym;t;Lp*f9DYBnh-%scLqEzc4p8E^1>uv69W@_N2XJ+Efr4Sl;auF6r z#y)xm-loRpW?aGZ_V5mt?w3>U%VDjLr1xE$gX^pY&sq64sHXqN{eCI^eX?40aYR(8 zk19ts!Y4dJRae#D`|oo-L)Dm_=Q=x!`1$yLAGxQ$Uh-jvzgUTUd~w!eUvt)rkLMHN z`!5fu$pC&yKKQ<{hy%;>s~yW`_HSQ{y)AdkzxK3KK<9G z`N3b;=J)ac>)NzWY%b(p8=*hfW+1;R-;x|@74EeW{s)bDAme^r8;6R;u3}#pKR!`m zK0c8_!6Dv0;T%IV3(>#MJoqHlxnFsB7#ni`73L;f=82(!xt^twr;nbOiI=Ibucf7# zxsRv1;Q;)^>V^aG5vv;xz&|{oh`a&C5UCpvAe&)8plbDj+>h!eKIR@izMfo`nvbag z7bU}e^Dy%C(6cb`@iaCwHZ}6H@KPU$6MkTXwVL0zv26NIB{^GL(M`>5$A1Yf&N-fQj^~`?Ip_ZGbj~^aJ&l>`AEX(sveGBa+utjKqoNt+;}NLR|E8(0 zG9k?0d#R6#pO61izld;^{@I_t%GBWS2>)QO2$j$KJ z&jo)Uf6sUNw>A3D`?=YFG63ZQ1266!JaN)azN|p$AIbZ;lac?DC!Vd3XY1qH`gpcJ zo~`fyPFvrczvXG_96OH6eE+~e6`xh1fgb+BT)m0#^HK5k3G|8Z4+&O@^au;{2Pa=V2?^b?7O3x<~;uJ{+Q_UW@?A zwL(^YJ#M&S_I`{HTZS#J%h786P;|Ol$=>}spZ)IEMSuPXhFeM@z`v3ou3TORg{dZ3 z+1>+VElt61^n3{5x*+G~d0>RsX_~)P96npm!copvaKf;H#>r{3PcrtBMcNMZz{X9G zR4aq?2J2whqd_FP))2ksUm$O6a@d2*$HHvwMHudL14Wagv13Xx$+C-rDsEbQ-%LYz z9_fJ%gR-gkwJG>TdJn_zMCpN=v#`^3xYd|1TDU`)=RCA9&ZXmDJ)>hEFNMR8dZ@rC zBlscQhI7LN@Zr;2WPVgP-8Y`+Je(yeLItnvX1RMExL`Ht^;klr z&^pxp%3u?pBeoA~fs5yZaZ-B>?GBJ=j@vL96z*>Wn`gxs>=j4c`s}Hvodngs;(%>Y z8__jv7Ix3j;5WL(w%laP*rK@Ta$7%seN|e7FpfB@*!M$xUQX=MH>4ZYNk?RE3yV z4a|bwDImBqo;-Z!0$qoH5T5h!zjq$0{Vh^+xeFte{%5Buz9C^MOMN01|N4TvsPgyq z_wwl1sdFP&{8}0PKK>S+p33e26~z80=0{mbMXL7cgz341q%?NZ2eJPY9iG`y3rX^G zG`f=u`7296)+`6N1hkH%x}!@>Ce)hW{Rb3N%cjUlnRABpb%1`_K1nygFTi)K-S zv10LeGN%0uj(?H|t&M3=_gR=ccP}C*V$!JE+$?yiEd%BkObNfqE);wb2700vc>IV6 z>>QFzH3TzB-i~_ET~m#F3P;l!Z(Q)*JtMs3mq5x3OtHm35oJbbqWGE%@Z-<}_RAc3 zG+FtU9J*hCS0|KP?D=Q`Ho-2I1&(!e>=MCdiSPwr`b?EoShNn>cd(GZCK9)(xAfwHz2`(Ey!l}1dpl6m z)PV0tYGLw$9J;m389Jm>n3~(_RCHNAopREccySa_a&H3)*olJg*%s9{#{v>( zc3^AY6nLLpf@>>;apo;CxKuS4GK!-~1fR{XYH{7|VdFUok)2QP#cQzgIP5%s+$--#O`N;-(qSFHd%e z_W#V^s~Vn{QUlkRXoA9wW>j5N!nS5EL^+3UHoIXSJ$BNaW7S>;vJH)-(R&l9xO`@J zbE!u=W|tH5QpM)wijwS>U6SNnP70p8e};@q|47e%6lpe^%}{tS4@EaUqTd>Y>9v_@ zP_0qVG%B8K@;XyRK6jtM=Sr#I7#@ggP8}uUu93LKZ5|H#QA4XoTmVI_kuZIy5O!BY z(G9I@(Ean9rWxD5(dAJ}@a;nmPJOkBDqXk0))S+#RB04wT)M+lM)JYR#C34ObqyTd zv;{vMdqFy#j`6&daqp>txi5Xqw!_q>o0zPXOAFp{8Jh2g;=_DNP+C?;<__g~DYJH2 zP_@sMbkVqKm@qOAc6g44ZaRj9-t~s!=v4N|;S*%e@_po)**2#8O&uKCeH1=V-3Xh0 zT*PmQr=ZV(`&8iaaoXTk-6Zfh3Pe7}!Ol7cR6IU0do#Xqu2h=A){J5{RKpINu8ZMu zn~$vRkB#KRs?YGmWeY|nT_hjB6|Uu_R7b-coGkT_=cWAb zy_7=#5SXkA>0hey`}j{)`Mc~N8#<=$-70Qdr>|{TIz{n)z{*MeKk2ush;+-7famLp zP>Tbca^4L2QWlgwxdcLc9)VGh0I4xPN1ngW$D7uL^!DW=?D8HCeJI^Y&Y8O~*93>s zK=TRsw7!H`UyaA*me(osh0EHuu7=%``Xpi1R+^b`kjBm*kIH=v${bcFc8BMJ*s*+^ z)aA&`cJ`)Ce%2^2E=z|O@ZqNyqrlo#mVK>!g&1yY!|^xE$V)W`1g+&zqG*cehwa9* zx3&<^*}LG~h$@&GngSbTPXcgxbPsh-6NPUzBxkK9lzQZY^iq40-8BXGPg+6kv;@fv z@m?y{M1V(=ccgEy2AHlu=r_ zfody$(NQKJi)nyd-&W)QFI5ZXIIH2MM%MwV^^`Fr-**y9}b6K#1g6SiR9z5 z@8rm?87Oz9!?IjK3R~LG6a9UixVpNKwiRVEC+CkMw;m+oo8_fUoSPEf*WLlo$Jvs3 zI~?fxL-Sybgd18lB+(;ZHi5HW1D$&8G}U&?ftcwIO%f)%VZQHV2wHRt&+ErxmEJRs z-84bqT)9j;j3V((@JknxvnjFWW595#@=QgoWD(NZ+BDSNxhKLk-&&n3btW=Vj`jQV+##axQN*JJ{V;k=zu*Y%}$f8IWOUeF4s=eCD$g?>&G9U>m+-!Pi7^~ znUaG7C0+DLRwg#?bA^{BBdkuRWsogD3_-yz2ERKkhSt<%HuDmfcXRk4mDF88X4}St z^9(+0-*=2tmn+W}h33PkbSR^4kyTq_qUHQJy|~ z6c3XpPeM=G^AP3Zin}yBA$h9y%SK6y|2DP7-ur&IMh(_lrj`iNrlmLD76wd#$-+h z)I1kyYB~O{iRe%+EqX8UR7-ox%FLcHxY4@chf!F%hAIgPR}$f;}l z@WYUIc+u)ORb1hWS0Zmvr!I$x|gSbHYuSZ;?Ie0dleC24g&+Y9U$#iK;^9B$4- zC7ltPNv!9HnRS(k3Xz4=KlYNo`Y$+EtPnS8?2LA`5Ml zTIeMQQPh631vOi=aP-e4Vs&8{hEA)b*G?TK?RCZMvY7MC>rhwF_FIol;dj8Ql9Hl! z6_bHYhhP}nMV}i_lAz`D(y#IxtdNJnN2^=+>b9W9OGg&ZV`Jk0}_|G13dCi!cujFA*Zf2P3MM! z@GZOo&+@`y(#UamZZF}YqWw_Gehe(!^qvmZo(77lA{bKCNZx*LCG(BP6Gh8dJnM~Y zcXu(^y?x8cO0!_}vP^OBs5VNRIcPmGf{C2)9mdQ&1pI|MDDu96RdBun&GPr@kVgqP zqv0YM9uNfPCl<1a<}7FVc5RT(&9c}punvdr3r7nRN+)*sGq&?4bCLKFaIZEUM_Y!& zkhNtXwBHdN$_|s=o410N`EI=aCJlt#hk2^WIO&PjzRUXM7l}c7j z%7YuZ5>OPIj`Dk4A#-y!V|Hv1Hptn-`0c0J@<)1j>EnL7JYR}<4O2z;j}CTQFz4{g$oya zB$FR6L5csTy*mx7dHwzeo(xGLk|9b$rXr%$=Uyc_9b@KXRwQXc$xJDP3K=3q357Z+ zr9StHLxwov7!!#&rsJ4}aQrrh|MCAm_+8&ezen%=VBZh+-d(#_SJ%4N=d;$jtG`f# z_S!qR7p{`4GhE5Ss}0R=Y(TF2Ux{UVcJR~OmE?b?LLS+;gXf!97uiv)X1p0D>vTzD z_m-FC-*Howmmf3Ldg8QZ47nnPl++NlS7~wFY7%~q#^mIBj=jnf`8WN3TsR|=u4)2J zqp}usWLqR%Yx{|t-RP^Vm}VnJ_N-JN>loAgg*C+UkWF&-@<6h@+D-3j`<@rA z?>7_23p}XmpX=%6yJzH;G>u#vrBm+ogQB8Wth}~sH6Ix_RXH?!4X<&SMk~v<@=w1A ziZdQXH;cn)^cZ)3zsi%3Jh(5q+uF(5x_$WV$NlVF$4C5rcp{PaGK#$XOmqyc!u2w2 zsb#+qT6OC$*9p>)s$Q0!NwhG3g06vY(~WJc;&6Qz1@?i zd-zd9w|udqm5!n%ThiEWnlbs4^Ej<~6*}SfR&*F&D!bQ=q$#1@ssFGI;^x@{!gy)8 zY#*_bODc7#<;~W7Zh0r#7f|1E{LM9#lV6Aa`ei*m9(P;OU%!Dq)+^`ZI+uiR@JBIp z@jf}+zF4&FIiHWOd@PoBzohJ1=Smk($L8jgJ`x4te$uIjBeyaOQR=U4%bufch0~{0 zbzh3RJW={oPVW$-Cii_IdYQ!1Uc+0;xY*l#c(Sh;{%8Wts`*LnI3SK9Jzfi^7p9ax z--`z~U&($OmvX^&JGiRI^XD}Y`OibRR_oo;VD1ufiHwsGwXTb6{vI4}(vHRFJmLOJ zbzz^~kSoSysvrN%m%3g#IeU6ESfrAss6;|#ZqH8)f^!BhGx2T^#=-Ei5-$%3XUSC|zC3zMJcalh+6?7<*TQ%poJUWrdz2^n;##0e=VsjD= z((Lf-=$6PGj-5i8WubJr?;=tBxm2lMc1||Bs*tk0 z7nKQ7ik&D$*Lx7Z-fctkESGCe zmpV$v2e;=#)hLSAfGA<5RXq=(DGd?*=ffC zI<}_;?VkTm4YMf_=6V6dJ}G29a~+v?f1tdN8mLw~w3LjN3N~vM#g`Jcit=nfa@B24 zk2DkI4c1nd$+ctT+?I8yGI1m8Y`m}h)#w`iv2TqqoOe$Sw`oB7X&w|~ejsmBqj0gx z*^z1w*QJN6K8emI;iCSWFyhodl%R%wBITto7wx`8t`6N*eTxF|{^WBxq?My6iHW4t zRW@q8wKlnI3Zp^3lhx?Nmm=fIIQ4S&YH9UBo1?YvJ5=Z_pjR7BdEtsuhqbeR=GVTF z>Za$WG^)x_-f+1Sz0_JoZ&oD=?F=`5)NNIsr{i{hKE_i1X?ljktNbcUx235GFHLD| znx!U?pHmXOij_8_tR07pw&hMb`ij+ruKXl1mSUVUsN2MKqWbODj^pN5Bkh6@vbdoI z>FX|G=igl9=&%?X<&+^Ob@HI04F*Z~;ht=w3 zIPfsyTazJ8BUKTfClKkgvR`5qD7<~~zz4eB{4IWO!_+isT8Kep!id=vS%VNp~2 z7dX*(ny3Hwu0`u_4=c^u`OA~_zfa#@>a2Nhpl86Be#b9`hrXTxW-e1^_y&grO`GC9 z!^}BwlJ^Wvy}S8>_W{LUJ0HKV-jX> zL~PhH#k|TLd2MJvGTHk|ygIv&wiiUw^6^pP#KSCQRqewZ{5FAyIu4)&YC)e5*5*DP z8&FM`cd{bYoa$6`rwP^L<%gc(WHRED+!15QQMyg2vY8=&oLMM6cdVB!th3Yx)e2=& zekblXEr#l@=uhp{967gzp6oF$i|&qfBDayBL~-sowULLR^k0!f6HJVGl*TO8E2CO7_DGyD=4S)|1n+*U`Jr#0!#A9Ntv@D365t@-p*{RN1HO z@Pf*^)UZ)?SyHr56q-tD=@mx!tIMp?7#@%g%Y}eL^uZ0cdhgyd; zg?9-&y=y$3X@7_VuQcNgO1#{a@w>D+U(C}AyUDAwuBri5hKX#=(PQtXrn75Z2l@N- z^<2A4C)w7$Saxh~r*M_2ntS=x*e#H$YrDhrWKlZ3D=4SX(nubgy^S|lX~$QZJ(D>D zchkp$cy^$=a&OffI=ioyv<}OpwWrpKmjj6Z*wr}C$Mm8!zmQHnZ$-;zn-2=#H!FB+ zVJ8Q(A|p1R(~D3w${UTYUC{6|G_!Zkl$G-WmI$tmNpt@eplj$7AIja=)G>sg2>vdzf!IjqX>c|q=v*u1=RCOFxAAFt~)N990>>fDyYrj=p)JfvW zUrp#<$|N#c?8F}eJBpR3I`EM*4av$)rTMpdse0z`se`SvI44#)_&hO_EkB1-(e3?t zp3MuDy7BGAg|T_G_svBPyj7b!IE<#^WH+gO^{A+9HI}1RgvsB}EEjFVKG6N$_k`sX zGv)7}4swlYTe$JmE3#9n59@g^q)j$y>`|hr(HgoU@8i|gBGu80UJX!r-pA6s_66H0 zsl27kSJGs@ZklM^J%z)M#j0B2_2|Q$gYx&!+hpppmO^Ka7EOO=E;n5~FNPlLD?8WB za}XB$sI^ObxxMQ$`q)h2;Wu_r{;pqC|1F6$*d~WHXZ=vU$+bCnQ8u6Xdn1LvO_JLl zcjc8qrwfFUmA;qqZ8SonLr-F?UkJ<%7&P_B*5}UN^bH zGYeZQIk(=+6F(bpub)R#mxeL2!NI!X?jJ|xZzr@J*Y{22g`T5%#Jep#+h(!m*u{Sy zy}Z3<`h4xP`dUs{EhO}7(ct&>dw}^e4(CfRuZ@k3Q$@=6$QZV>xL=7=FW1~fQCn}e z)PMD3fJyQmD`O8ja=Aymth(`_98*|_%r?Yuo1@mMOYl}n6jjZuaqMV)p4VlOI_ORmJ#{)rom=%&v$?U}acQ(BoDSI5;976G(y=J! zdTG9txv467Xk8Lfb^VokCOt`=*jcIzf0au|T@oofyKu?$s< z%|Y}?UtcpUvrtC)=yKT*Hz}?UPD=;Y3+Z1<@NA5_CA_TVjUY`&ZB*NC8d1_x-u{-#`cMe?S}bGdhAmI%r5 zBjYZK;)YRW?qi)d;^Er`V*Q~asl9j(n}6;vaxS^c_NQ0z*jropM0t*^=3vNOK2MQ` zt~PStiQPHlFF%$2?>LjQf##0xqsEkbJ6eo&)1?gksq7GHz-c9>j&>_YaN>oP9O$-F zxLZ9^_pK?Fora`}1oNZf&a+0e|E`mGv2Y7F{O~7j^cyVtwtJ|gz5C2#hUv-3-Zd2O z&+~bosG`28zeO6fE)j=}=JA>6cs|oLL35^S5_QpRiYAS&L2Z)9v)+U=4l^_};C;q# zqCBg?)H#Z_k7**1mjM`kQA8n!icS zy;~%efEb75EvYoiXb5f2XhZJmQVM!?Qgj?@=#XqNSLWn2WrG$O%F9hpWS8HT%Zxf& zO6{PA{8xej>-;rbgjR-g>G?v+oYRag`xcUkM{Hi$N92L%xxl z>B^W`&a%(oWU4s+L% zJ)(L*7xw;OAi1DeY_;qrS{=F|HaLx^dDdw%seenkF)Bx{?yn=pS-)e8VbA5^D%)ty z#b%Cp#}oR> zLC1b$=R6NJdWtHXU&hjJ6ZEOd5myeXGeY(=pQol6SBS}zPtxfHd*%BZrgGca%W_8G zD|*ytlWc!~yPW@Ov;5@uUOYc~lR-^5dyZ`->QwBKP5TDO{OwDmX=#**N^BsXcRtJ)Z)b~V+B?OsX?3YqkYuOtyF5I-HVoW^+WB7&eNo#j!Q4dFHwZu_bzsTvk>jOM*AaETxs25Wj<7 zAJySr-vW%w&wajL$8?$L|Mj}2CI$T2JJ5TECS&Xq7~-wTc6_`8CwhPBrV25eIK?x> zE9h%$m8Q}#I9QVced)BC*lWhusPujP9+iH~4&Rv>mH)@Um;Yi0hF0>)R(t<*;LCsU zxvxV__IaBuua)v;0FIr#oo(kYvrFu~Y$jQC_4KsW%r5C}Z)M%Z&f3%7&c3^st@ncO zQFBhHq3LGoyDM(F_O!G)D+7PbC#}Az{V)0CmnuxF|CeQcnfWsR%s*M-_w_r^k6B^6 z(HpD%|2+TAch*XAwA}L_Jpaw-zVdwgRj@z+0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf oKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2>dJXKS#djO#lD@ literal 0 HcmV?d00001 diff --git a/engram-data/snap.0000000000035912 b/engram-data/snap.0000000000035912 new file mode 100644 index 0000000000000000000000000000000000000000..40126d0d2af02239bae20617660eb1ed705d37b5 GIT binary patch literal 596 zcmX}pze~eF7{>8jRB8q-(nX;|M?s6Ao801*Mmvd!;^^We`XBfM#L3M?sx4S>Ds-sh zkq+5J5J5o~LEY-02rpcO;-l?z8IsSJa4(ljZhgS}yOaw5pSXn+Do^Kw!9vFGRSHR~ z9`+V^DHInX78F+&Sx~H!5w3)`gUS;VvSX{)DI3!JifbW#FwBDVMV1BW-Dwu2kLOsB zo>^c)5}hwIA&G{b-5|*3t{QF>B%Q>~f~2$MJ{8iBYn%mXUshEASRgxjSv_Mz5(V`W zejQ|s-d)0mq}9A(Lh|0eXF~Gv`ptyo?aQ4MBsJrX3X+HFJU0eOZM@8cu!smN59O0n9u+K literal 0 HcmV?d00001 diff --git a/crates/engram-core/Cargo.toml b/engrams/engram-core/Cargo.toml similarity index 97% rename from crates/engram-core/Cargo.toml rename to engrams/engram-core/Cargo.toml index 2eb9ae2..23fd950 100644 --- a/crates/engram-core/Cargo.toml +++ b/engrams/engram-core/Cargo.toml @@ -15,6 +15,7 @@ migration = ["dep:rusqlite"] sled = { version = "0.34", optional = true } uuid = { version = "1", features = ["v4", "serde"] } serde = { version = "1", features = ["derive"] } +serde_json = "1" bincode = "1" anyhow = "1" thiserror = "1" diff --git a/crates/engram-core/src/activation.rs b/engrams/engram-core/src/activation.rs similarity index 100% rename from crates/engram-core/src/activation.rs rename to engrams/engram-core/src/activation.rs diff --git a/crates/engram-core/src/consolidation.rs b/engrams/engram-core/src/consolidation.rs similarity index 98% rename from crates/engram-core/src/consolidation.rs rename to engrams/engram-core/src/consolidation.rs index 231a7d4..8ab02a5 100644 --- a/crates/engram-core/src/consolidation.rs +++ b/engrams/engram-core/src/consolidation.rs @@ -88,8 +88,9 @@ pub fn consolidate(db: &Db, config: &ConsolidationConfig) -> EngramResult= config.salience_floor { // Promote: change tier to Semantic and persist. + // Use overwrite_node β€” this is an internal state update, not a new node. node.tier = MemoryTier::Semantic; - graph::put_node(db, &node)?; + graph::overwrite_node(db, &node)?; promoted_count += 1; if promoted_count >= config.max_promotions_per_run { @@ -108,7 +109,7 @@ pub fn consolidate(db: &Db, config: &ConsolidationConfig) -> EngramResult Self { + Self { db: self.db.clone() } + } + } + impl EngramDb { /// Open (or create) an engram database at the given path. + /// + /// Seeds all built-in edge types on first open. Idempotent β€” existing + /// definitions are not overwritten on subsequent opens. pub fn open(path: &Path) -> EngramResult { let db = sled::open(path)?; + edge_type::seed_builtin_types(&db)?; Ok(Self { db }) } // ── Node operations ─────────────────────────────────────────────────── - /// Persist a node. Returns the node's UUID. + /// Persist a new node. Returns the node's UUID. + /// + /// Returns `EngramError::NodeAlreadyExists` if the ID already exists. + /// Nodes are immutable β€” to update, create a new node and add a + /// `supersedes` edge from new β†’ old. pub fn put_node(&self, node: Node) -> EngramResult { let id = graph::put_node(&self.db, &node)?; // Mark HNSW index dirty so next search rebuilds it. @@ -109,15 +126,90 @@ mod sled_impl { // ── Graph traversal ─────────────────────────────────────────────────── /// BFS traversal from `from`, following edges up to `max_depth` hops. + /// + /// If `relation` is `Some("causes")`, only edges with that relation + /// name are followed. Pass `None` to follow all edges. pub fn traverse( &self, from: Uuid, - relation: Option, + relation: Option<&str>, max_depth: u8, ) -> EngramResult> { graph::traverse(&self.db, from, relation, max_depth) } + // ── Edge type registry (native bindings for el) ─────────────────────── + // + // These are the el-callable surfaces. All intelligence about when to + // create types, how to score confidence, and when to merge/split lives + // in el. Rust stores and retrieves. + + /// Create or update an edge type. If the type already exists its + /// description and confidence are updated; id, first_observed, + /// instance_count, derived_from, supersedes, and deprecated are preserved. + pub fn native_edge_type_put( + &self, + name: &str, + description: &str, + confidence: f32, + ) -> EngramResult<()> { + let confidence = confidence.clamp(0.0, 1.0); + if let Some(mut existing) = edge_type::get_edge_type(&self.db, name)? { + existing.description = description.to_string(); + existing.confidence = confidence; + edge_type::register_edge_type(&self.db, &existing)?; + } else { + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: name.to_string(), + description: description.to_string(), + first_observed: now_ms(), + instance_count: 0, + confidence, + derived_from: None, + supersedes: None, + deprecated: false, + }; + edge_type::register_edge_type(&self.db, &def)?; + } + Ok(()) + } + + /// Retrieve an edge type as a JSON string. Returns empty string if not found. + pub fn native_edge_type_get(&self, name: &str) -> EngramResult { + match edge_type::get_edge_type(&self.db, name)? { + Some(def) => Ok(serde_json::to_string(&def).unwrap_or_default()), + None => Ok(String::new()), + } + } + + /// List all registered edge types as a JSON array string. + pub fn native_edge_type_list(&self) -> EngramResult { + let defs = edge_type::all_edge_types(&self.db)?; + Ok(serde_json::to_string(&defs).unwrap_or_default()) + } + + /// Increment the instance_count for a named edge type by one. + pub fn native_edge_type_increment_count(&self, name: &str) -> EngramResult<()> { + edge_type::increment_edge_type_count(&self.db, name) + } + + /// Update the description and confidence of an existing edge type. + /// instance_count, id, first_observed, and other metadata are preserved. + pub fn native_edge_type_update( + &self, + name: &str, + description: &str, + confidence: f32, + ) -> EngramResult<()> { + if let Some(mut def) = edge_type::get_edge_type(&self.db, name)? { + def.description = description.to_string(); + def.confidence = confidence.clamp(0.0, 1.0); + edge_type::register_edge_type(&self.db, &def)?; + } + Ok(()) + } + // ── Salience management ─────────────────────────────────────────────── /// Mark a node as recently activated β€” update last_activated, increment @@ -132,7 +224,8 @@ mod sled_impl { node.last_activated, node.activation_count, ); - graph::put_node(&self.db, &node)?; + // Use overwrite_node β€” touch is an internal state update, not a new node. + graph::overwrite_node(&self.db, &node)?; Ok(()) } @@ -154,7 +247,7 @@ mod sled_impl { if new_salience != node.salience { node.salience = new_salience; storage::write_salience(&self.db, node.id, new_salience)?; - graph::put_node(&self.db, &node)?; + graph::overwrite_node(&self.db, &node)?; count += 1; } } @@ -226,9 +319,9 @@ mod wasm_impl { use crate::error::{EngramError, EngramResult}; use crate::mem_storage::MemStore; use crate::salience; - use crate::types::{ActivatedNode, Edge, Node, RelationType, ScoredNode}; + use crate::types::{ActivatedNode, Edge, Node, ScoredNode}; use crate::vector; - use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; + use std::collections::{HashMap, HashSet, VecDeque}; use std::sync::RwLock; use uuid::Uuid; @@ -332,7 +425,7 @@ mod wasm_impl { pub fn traverse( &self, from: Uuid, - relation: Option, + relation: Option<&str>, max_depth: u8, ) -> EngramResult> { let store = self @@ -352,8 +445,8 @@ mod wasm_impl { } let edges = store.read_edges_from(current_id)?; for edge in edges { - if let Some(ref rel) = relation { - if &edge.relation != rel { + if let Some(rel) = relation { + if edge.relation != rel { continue; } } diff --git a/engrams/engram-core/src/edge_type.rs b/engrams/engram-core/src/edge_type.rs new file mode 100644 index 0000000..81c9819 --- /dev/null +++ b/engrams/engram-core/src/edge_type.rs @@ -0,0 +1,443 @@ +/// Edge type registry β€” dynamic, first-class edge type management. +/// +/// Edge types are stored under the key prefix `edge_types:` in the sled +/// database. Each value is a bincode-encoded `EdgeTypeDef`. +/// +/// # Operations +/// +/// - `register_edge_type` β€” create or update a type definition +/// - `get_edge_type` β€” look up by name +/// - `update_edge_type_description` β€” change the human-readable description +/// - `increment_edge_type_count` β€” bump the instance counter when a new edge is created +/// - `merge_edge_types` β€” retag all edges from one type to another and deprecate the source +/// - `split_edge_type` β€” record a split operation; the predicate is stored as a description note +/// - `deprecate_edge_type` β€” mark a type as no longer current +/// - `all_edge_types` β€” return every registered type +/// - `edge_types_by_confidence` β€” filter by minimum confidence score + +use crate::error::EngramResult; +use crate::storage; +use crate::types::{now_ms, Edge, EdgeTypeDef}; +use sled::Db; +use uuid::Uuid; + +// ── Key helpers ─────────────────────────────────────────────────────────────── + +fn edge_type_key(name: &str) -> Vec { + format!("edge_types:{}", name).into_bytes() +} + +const EDGE_TYPE_PREFIX: &[u8] = b"edge_types:"; + +// ── Public API ──────────────────────────────────────────────────────────────── + +/// Register a new edge type, or overwrite an existing definition. +/// +/// Returns the UUID of the stored `EdgeTypeDef`. +pub fn register_edge_type(db: &Db, def: &EdgeTypeDef) -> EngramResult { + let key = edge_type_key(&def.name); + let val = bincode::serialize(def)?; + db.insert(key, val)?; + Ok(def.id) +} + +/// Look up an edge type by its canonical name. +pub fn get_edge_type(db: &Db, name: &str) -> EngramResult> { + match db.get(edge_type_key(name))? { + Some(bytes) => Ok(Some(bincode::deserialize(&bytes)?)), + None => Ok(None), + } +} + +/// Update the human-readable description of an existing edge type. +/// +/// No-ops silently if the type does not exist. +pub fn update_edge_type_description(db: &Db, name: &str, description: &str) -> EngramResult<()> { + if let Some(mut def) = get_edge_type(db, name)? { + def.description = description.to_string(); + let val = bincode::serialize(&def)?; + db.insert(edge_type_key(name), val)?; + } + Ok(()) +} + +/// Increment the instance counter for an edge type. +/// +/// Called whenever a new edge with this type is persisted. No-ops if the type +/// is not registered (the counter stays in-registry, not in the edge itself). +pub fn increment_edge_type_count(db: &Db, name: &str) -> EngramResult<()> { + if let Some(mut def) = get_edge_type(db, name)? { + def.instance_count = def.instance_count.saturating_add(1); + let val = bincode::serialize(&def)?; + db.insert(edge_type_key(name), val)?; + } + Ok(()) +} + +/// Merge two edge types: retag all edges from `from_name` to `into_name`, +/// then deprecate `from_name`. +/// +/// After this call every edge that carried `from_name` will carry `into_name` +/// instead. The `from_name` definition is marked deprecated and its `supersedes` +/// field records the merge destination. +pub fn merge_edge_types(db: &Db, from_name: &str, into_name: &str) -> EngramResult<()> { + // Collect and retag every edge carrying from_name + let prefix = b"edges:from:"; + let mut edges_to_retag: Vec = Vec::new(); + for result in db.scan_prefix(prefix) { + let (_k, v) = result?; + let edge: Edge = bincode::deserialize(&v)?; + if edge.relation == from_name { + edges_to_retag.push(edge); + } + } + + for mut edge in edges_to_retag { + edge.relation = into_name.to_string(); + storage::write_edge(db, &edge)?; + } + + // Deprecate the source type and record the merge destination + if let Some(mut def) = get_edge_type(db, from_name)? { + def.deprecated = true; + def.supersedes = Some(into_name.to_string()); + let val = bincode::serialize(&def)?; + db.insert(edge_type_key(from_name), val)?; + } + + // Update instance count on the destination to account for the absorbed edges + if let Some(mut into_def) = get_edge_type(db, into_name)? { + // Recount from graph (simple: scan all edges for into_name) + let mut count = 0u64; + for result in db.scan_prefix(prefix) { + let (_k, v) = result?; + let edge: Edge = bincode::deserialize(&v)?; + if edge.relation == into_name { + count += 1; + } + } + into_def.instance_count = count; + let val = bincode::serialize(&into_def)?; + db.insert(edge_type_key(into_name), val)?; + } + + Ok(()) +} + +/// Record a split of `name` into `new_name_a` and `new_name_b`. +/// +/// The predicate that drives the split is stored as a descriptive note on +/// both new types. This does NOT automatically retag edges β€” the caller is +/// responsible for deciding which edges go to `new_name_a` vs `new_name_b` +/// and calling `storage::write_edge` for each. The split records the intent; +/// the actual retagging is domain-specific. +/// +/// The original type is deprecated with a note referencing the two successors. +pub fn split_edge_type( + db: &Db, + name: &str, + new_name_a: &str, + new_name_b: &str, + predicate: &str, +) -> EngramResult<()> { + let now = now_ms(); + + // Deprecate the original + if let Some(mut original) = get_edge_type(db, name)? { + original.deprecated = true; + original.description = format!( + "{} [SPLIT into '{}' and '{}' via predicate: {}]", + original.description, new_name_a, new_name_b, predicate + ); + let val = bincode::serialize(&original)?; + db.insert(edge_type_key(name), val)?; + } + + // Register new_name_a if it doesn't already exist + if get_edge_type(db, new_name_a)?.is_none() { + let def_a = EdgeTypeDef { + id: Uuid::new_v4(), + name: new_name_a.to_string(), + description: format!( + "Split from '{}' β€” predicate: {}", + name, predicate + ), + first_observed: now, + instance_count: 0, + confidence: 0.0, + derived_from: Some(format!("split from '{}'", name)), + supersedes: None, + deprecated: false, + }; + register_edge_type(db, &def_a)?; + } + + // Register new_name_b if it doesn't already exist + if get_edge_type(db, new_name_b)?.is_none() { + let def_b = EdgeTypeDef { + id: Uuid::new_v4(), + name: new_name_b.to_string(), + description: format!( + "Split from '{}' β€” predicate: {}", + name, predicate + ), + first_observed: now, + instance_count: 0, + confidence: 0.0, + derived_from: Some(format!("split from '{}'", name)), + supersedes: None, + deprecated: false, + }; + register_edge_type(db, &def_b)?; + } + + Ok(()) +} + +/// Mark an edge type as deprecated. Deprecated types should not be used on +/// new edges, but existing edges carrying this type remain valid. +pub fn deprecate_edge_type(db: &Db, name: &str) -> EngramResult<()> { + if let Some(mut def) = get_edge_type(db, name)? { + def.deprecated = true; + let val = bincode::serialize(&def)?; + db.insert(edge_type_key(name), val)?; + } + Ok(()) +} + +/// Return all registered edge type definitions. +pub fn all_edge_types(db: &Db) -> EngramResult> { + let mut types = Vec::new(); + for result in db.scan_prefix(EDGE_TYPE_PREFIX) { + let (_k, v) = result?; + let def: EdgeTypeDef = bincode::deserialize(&v)?; + types.push(def); + } + Ok(types) +} + +/// Return all edge types with a confidence score at or above `min_confidence`. +pub fn edge_types_by_confidence(db: &Db, min_confidence: f32) -> EngramResult> { + let mut types = all_edge_types(db)?; + types.retain(|t| t.confidence >= min_confidence); + types.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal)); + Ok(types) +} + +// ── Built-in type seed ──────────────────────────────────────────────────────── + +/// Register all built-in edge types if they have not already been registered. +/// +/// Called once from `EngramDb::open`. Idempotent β€” existing definitions are +/// not overwritten, so user customisations survive restarts. +pub fn seed_builtin_types(db: &Db) -> EngramResult<()> { + let now = now_ms(); + + // Original relational types β€” confidence 1.0 (canonical, well-established) + let originals: &[(&str, &str)] = &[ + ("supersedes", "This node replaces or obsoletes another"), + ("causes", "This node is a causal precursor to another"), + ("contains", "This node hierarchically contains another"), + ("references", "This node cites another as supporting context"), + ("contradicts", "This node is in logical tension with another"), + ("exemplifies", "This node is a concrete instance of a more abstract node"), + ("activates", "Co-activation: firing this tends to fire the other"), + ("temporally_precedes", "Temporal ordering: this node came before the other"), + ]; + + for (name, description) in originals { + if get_edge_type(db, name)?.is_none() { + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: name.to_string(), + description: description.to_string(), + first_observed: now, + instance_count: 0, + confidence: 1.0, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(db, &def)?; + } + } + + // Personhood / relational types β€” confidence 0.9 (well-understood but newer) + let personhood: &[(&str, &str)] = &[ + ("grounded_in", "This value or belief is rooted in this experience"), + ("reinforced_by", "This pattern kept being confirmed by this"), + ("derives_from", "This preference or belief flows from this value"), + ("in_tension_with", "These two things pull against each other"), + ("expressed_through","This value surfaces in this voice or behavior"), + ("shaped_by", "This pattern was formed by this relationship or experience"), + ("challenged_by", "This belief was tested by this experience"), + ("resonates_with", "This memory echoes this value"), + ]; + + for (name, description) in personhood { + if get_edge_type(db, name)?.is_none() { + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: name.to_string(), + description: description.to_string(), + first_observed: now, + instance_count: 0, + confidence: 0.9, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(db, &def)?; + } + } + + Ok(()) +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + fn open_tmp() -> (Db, tempfile::TempDir) { + let dir = tempfile::tempdir().unwrap(); + let db = sled::open(dir.path()).unwrap(); + (db, dir) + } + + #[test] + fn register_and_retrieve() { + let (db, _dir) = open_tmp(); + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: "causes".to_string(), + description: "A causes B".to_string(), + first_observed: 0, + instance_count: 0, + confidence: 1.0, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(&db, &def).unwrap(); + let got = get_edge_type(&db, "causes").unwrap().unwrap(); + assert_eq!(got.name, "causes"); + assert!((got.confidence - 1.0).abs() < f32::EPSILON); + } + + #[test] + fn update_description() { + let (db, _dir) = open_tmp(); + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: "test_type".to_string(), + description: "old".to_string(), + first_observed: 0, + instance_count: 0, + confidence: 0.5, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(&db, &def).unwrap(); + update_edge_type_description(&db, "test_type", "new description").unwrap(); + let got = get_edge_type(&db, "test_type").unwrap().unwrap(); + assert_eq!(got.description, "new description"); + } + + #[test] + fn increment_count() { + let (db, _dir) = open_tmp(); + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: "references".to_string(), + description: "refs".to_string(), + first_observed: 0, + instance_count: 5, + confidence: 1.0, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(&db, &def).unwrap(); + increment_edge_type_count(&db, "references").unwrap(); + let got = get_edge_type(&db, "references").unwrap().unwrap(); + assert_eq!(got.instance_count, 6); + } + + #[test] + fn deprecate() { + let (db, _dir) = open_tmp(); + let def = EdgeTypeDef { + id: Uuid::new_v4(), + name: "old_type".to_string(), + description: "going away".to_string(), + first_observed: 0, + instance_count: 0, + confidence: 0.3, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(&db, &def).unwrap(); + deprecate_edge_type(&db, "old_type").unwrap(); + let got = get_edge_type(&db, "old_type").unwrap().unwrap(); + assert!(got.deprecated); + } + + #[test] + fn all_types_and_confidence_filter() { + let (db, _dir) = open_tmp(); + seed_builtin_types(&db).unwrap(); + + let all = all_edge_types(&db).unwrap(); + assert!(all.len() >= 16); // 8 originals + 8 personhood + + // All original types have confidence 1.0 + let high = edge_types_by_confidence(&db, 1.0).unwrap(); + assert!(high.len() >= 8); + for t in &high { + assert!((t.confidence - 1.0).abs() < f32::EPSILON); + } + + // personhood types have confidence 0.9 β€” included when threshold is <= 0.9 + let wide = edge_types_by_confidence(&db, 0.9).unwrap(); + assert!(wide.len() >= 16); + } + + #[test] + fn seed_is_idempotent() { + let (db, _dir) = open_tmp(); + seed_builtin_types(&db).unwrap(); + seed_builtin_types(&db).unwrap(); // second call must not panic or duplicate + let all = all_edge_types(&db).unwrap(); + // All names should be distinct + let mut names: Vec = all.iter().map(|t| t.name.clone()).collect(); + names.sort(); + names.dedup(); + assert_eq!(names.len(), all.len()); + } + + #[test] + fn split_type_records_both_halves() { + let (db, _dir) = open_tmp(); + let original = EdgeTypeDef { + id: Uuid::new_v4(), + name: "relates_to".to_string(), + description: "generic relation".to_string(), + first_observed: 0, + instance_count: 0, + confidence: 0.5, + derived_from: None, + supersedes: None, + deprecated: false, + }; + register_edge_type(&db, &original).unwrap(); + split_edge_type(&db, "relates_to", "causes", "references", "directionality").unwrap(); + let orig = get_edge_type(&db, "relates_to").unwrap().unwrap(); + assert!(orig.deprecated); + assert!(get_edge_type(&db, "causes").unwrap().is_some()); + assert!(get_edge_type(&db, "references").unwrap().is_some()); + } +} diff --git a/crates/engram-core/src/error.rs b/engrams/engram-core/src/error.rs similarity index 83% rename from crates/engram-core/src/error.rs rename to engrams/engram-core/src/error.rs index f661ed7..9ba4ce7 100644 --- a/crates/engram-core/src/error.rs +++ b/engrams/engram-core/src/error.rs @@ -11,6 +11,9 @@ pub enum EngramError { #[error("Node not found: {0}")] NotFound(uuid::Uuid), + #[error("Node already exists: {0} β€” nodes are immutable; supersede via edge")] + NodeAlreadyExists(uuid::Uuid), + #[error("Invalid embedding: expected {expected} dimensions, got {got}")] DimensionMismatch { expected: usize, got: usize }, diff --git a/crates/engram-core/src/graph.rs b/engrams/engram-core/src/graph.rs similarity index 75% rename from crates/engram-core/src/graph.rs rename to engrams/engram-core/src/graph.rs index a1ec1d1..e3cce4c 100644 --- a/crates/engram-core/src/graph.rs +++ b/engrams/engram-core/src/graph.rs @@ -4,17 +4,25 @@ /// in both directions so that forward and backward traversals are equally cheap. use crate::error::EngramResult; use crate::storage; -use crate::types::{Edge, Node, RelationType}; +use crate::types::{Edge, Node}; use sled::Db; use std::collections::{HashSet, VecDeque}; use uuid::Uuid; -/// Persist a node and its embedding. Overwrites any existing node with the same id. +/// Persist a new node and its embedding. Enforces node immutability β€” returns +/// `EngramError::NodeAlreadyExists` if the ID is already in the store. pub fn put_node(db: &Db, node: &Node) -> EngramResult { storage::write_node(db, node)?; Ok(node.id) } +/// Overwrite a node unconditionally. Used internally for salience/tier mutations +/// (touch, decay, consolidation). Do NOT call this for user-visible node creation. +pub fn overwrite_node(db: &Db, node: &Node) -> EngramResult { + storage::overwrite_node(db, node)?; + Ok(node.id) +} + /// Retrieve a node by id. Returns None if not found. pub fn get_node(db: &Db, id: Uuid) -> EngramResult> { storage::read_node(db, id) @@ -37,13 +45,13 @@ pub fn edges_to(db: &Db, to_id: Uuid) -> EngramResult> { /// Breadth-first traversal starting from `from`, following forward edges only. /// -/// If `relation` is Some, only edges of that type are followed. -/// The BFS respects `max_depth` hops. The seed node itself is NOT included. -/// Visited nodes are deduplicated. +/// If `relation` is `Some(&str)`, only edges whose `relation` field matches +/// that string are followed. The BFS respects `max_depth` hops. The seed node +/// itself is NOT included. Visited nodes are deduplicated. pub fn traverse( db: &Db, from: Uuid, - relation: Option, + relation: Option<&str>, max_depth: u8, ) -> EngramResult> { let mut visited: HashSet = HashSet::new(); @@ -60,8 +68,8 @@ pub fn traverse( let edges = edges_from(db, current_id)?; for edge in edges { // Filter by relation type if specified - if let Some(ref rel) = relation { - if &edge.relation != rel { + if let Some(rel) = relation { + if edge.relation != rel { continue; } } diff --git a/crates/engram-core/src/lib.rs b/engrams/engram-core/src/lib.rs similarity index 83% rename from crates/engram-core/src/lib.rs rename to engrams/engram-core/src/lib.rs index 0404456..bf86d97 100644 --- a/crates/engram-core/src/lib.rs +++ b/engrams/engram-core/src/lib.rs @@ -8,7 +8,7 @@ /// # Quick Start /// /// ```rust,no_run -/// use engram_core::{EngramDb, Node, Edge, NodeType, MemoryTier, RelationType}; +/// use engram_core::{EngramDb, Node, Edge, NodeType, MemoryTier, EDGE_SUPERSEDES}; /// use std::path::Path; /// /// let db = EngramDb::open(Path::new("/tmp/my-engram")).unwrap(); @@ -32,6 +32,8 @@ pub mod activation; pub mod consolidation; pub mod db; +#[cfg(not(feature = "wasm"))] +pub mod edge_type; pub mod error; pub mod graph; pub mod salience; @@ -48,6 +50,8 @@ pub mod migration; pub use db::EngramDb; pub use error::{EngramError, EngramResult}; pub use types::{ - ActivatedNode, Edge, MemoryTier, Node, NodeType, RelationType, ScoredNode, now_ms, + ActivatedNode, Edge, EdgeTypeDef, MemoryTier, Node, NodeType, ScoredNode, now_ms, + EDGE_ACTIVATES, EDGE_CAUSES, EDGE_CONTAINS, EDGE_CONTRADICTS, EDGE_EXEMPLIFIES, + EDGE_REFERENCES, EDGE_SUPERSEDES, EDGE_TEMPORALLY_PRECEDES, }; pub use consolidation::{ConsolidationConfig, ConsolidationReport}; diff --git a/crates/engram-core/src/mem_storage.rs b/engrams/engram-core/src/mem_storage.rs similarity index 100% rename from crates/engram-core/src/mem_storage.rs rename to engrams/engram-core/src/mem_storage.rs diff --git a/crates/engram-core/src/migration.rs b/engrams/engram-core/src/migration.rs similarity index 92% rename from crates/engram-core/src/migration.rs rename to engrams/engram-core/src/migration.rs index 016e708..1c5aa93 100644 --- a/crates/engram-core/src/migration.rs +++ b/engrams/engram-core/src/migration.rs @@ -10,9 +10,8 @@ /// | `memory_nodes` | `Node { tier: Episodic, node_type: Memory }` | /// | `knowledge_entries` | `Node { tier: Semantic, node_type: Concept }` | /// -/// Edges from `graph_edges` (type = "supersedes" or "Supersedes") are converted -/// to `Edge { relation: RelationType::Supersedes }`. Other edge types become -/// `RelationType::References`. +/// Edges from `graph_edges` are converted using their `edge_type` string directly +/// (normalised to lowercase). Unknown types map to `"references"`. /// /// # Embeddings /// @@ -23,7 +22,7 @@ /// TODO: wire in real embeddings from all-MiniLM-L6-v2 via the ONNX runtime. use crate::error::{EngramError, EngramResult}; -use crate::types::{Edge, MemoryTier, Node, NodeType, RelationType}; +use crate::types::{Edge, MemoryTier, Node, NodeType}; use rusqlite::{Connection, OpenFlags}; use std::collections::HashMap; use std::path::PathBuf; @@ -227,7 +226,7 @@ pub fn migrate_from_neuron(config: &MigrationConfig) -> EngramResult continue, }; - let relation = edge_type_to_relation(&edge_type); + let relation = normalise_edge_type(&edge_type); let edge = Edge::new(from_uuid, to_uuid, relation, weight as f32); match engram_db.put_edge(edge) { @@ -265,18 +264,22 @@ fn importance_string_to_f32(importance: &str) -> f32 { } } -/// Convert a Neuron edge type string to an Engram RelationType. -fn edge_type_to_relation(edge_type: &str) -> RelationType { +/// Normalise a Neuron edge type string to a canonical Engram edge type name. +/// +/// Known variants (including old PascalCase forms from the Rust enum era) are +/// mapped to their lowercase canonical names. Unknown types fall back to +/// `"references"` as a safe, non-destructive default. +fn normalise_edge_type(edge_type: &str) -> &'static str { match edge_type.to_lowercase().as_str() { - "supersedes" | "superseded_by" => RelationType::Supersedes, - "causes" | "caused_by" => RelationType::Causes, - "contains" | "contained_by" => RelationType::Contains, - "references" | "referenced_by" => RelationType::References, - "contradicts" => RelationType::Contradicts, - "exemplifies" | "exemplified_by" => RelationType::Exemplifies, - "activates" => RelationType::Activates, - "temporally_precedes" | "follows" => RelationType::TemporallyPrecedes, - _ => RelationType::References, // safe default + "supersedes" | "superseded_by" => "supersedes", + "causes" | "caused_by" => "causes", + "contains" | "contained_by" => "contains", + "references" | "referenced_by" => "references", + "contradicts" => "contradicts", + "exemplifies" | "exemplified_by" => "exemplifies", + "activates" => "activates", + "temporally_precedes" | "temporallyprecedes" | "follows" => "temporally_precedes", + _ => "references", // safe default } } @@ -340,12 +343,12 @@ mod tests { #[test] fn edge_type_supersedes() { - assert_eq!(edge_type_to_relation("supersedes"), RelationType::Supersedes); + assert_eq!(normalise_edge_type("supersedes"), "supersedes"); } #[test] fn edge_type_unknown_is_references() { - assert_eq!(edge_type_to_relation("foobar"), RelationType::References); + assert_eq!(normalise_edge_type("foobar"), "references"); } #[test] diff --git a/crates/engram-core/src/salience.rs b/engrams/engram-core/src/salience.rs similarity index 100% rename from crates/engram-core/src/salience.rs rename to engrams/engram-core/src/salience.rs diff --git a/crates/engram-core/src/storage.rs b/engrams/engram-core/src/storage.rs similarity index 84% rename from crates/engram-core/src/storage.rs rename to engrams/engram-core/src/storage.rs index f8ea09e..95b368c 100644 --- a/crates/engram-core/src/storage.rs +++ b/engrams/engram-core/src/storage.rs @@ -6,6 +6,7 @@ /// edges:to:{to}:{from} β†’ reverse index (same Edge bytes) /// vectors:{uuid} β†’ raw little-endian f32 bytes /// salience:{uuid} β†’ 4-byte little-endian f32 +/// edge_types:{name} β†’ bincode-encoded EdgeTypeDef (managed by edge_type.rs) use crate::error::{EngramError, EngramResult}; use crate::types::{Edge, Node}; use sled::Db; @@ -35,8 +36,19 @@ pub fn salience_key(id: Uuid) -> Vec { // ── Node storage ───────────────────────────────────────────────────────────── +/// Persist a node. Returns `EngramError::NodeAlreadyExists` if a node with +/// this ID already exists in the store. +/// +/// Nodes are immutable and append-only. To update, create a new node and +/// connect it to the old with a `supersedes` edge. pub fn write_node(db: &Db, node: &Node) -> EngramResult<()> { let key = node_key(node.id); + + // Immutability guard β€” reject writes to existing node IDs. + if db.contains_key(&key)? { + return Err(EngramError::NodeAlreadyExists(node.id)); + } + let val = bincode::serialize(node)?; db.insert(key, val)?; @@ -52,6 +64,22 @@ pub fn write_node(db: &Db, node: &Node) -> EngramResult<()> { Ok(()) } +/// Overwrite a node unconditionally. Used internally for salience/tier updates +/// that must mutate in-place (touch, decay, consolidation). +/// +/// Do NOT expose this in public API β€” callers should use `write_node` which +/// enforces immutability. +pub(crate) fn overwrite_node(db: &Db, node: &Node) -> EngramResult<()> { + let key = node_key(node.id); + let val = bincode::serialize(node)?; + db.insert(key, val)?; + let vkey = vector_key(node.id); + db.insert(vkey, floats_to_bytes(&node.embedding))?; + let skey = salience_key(node.id); + db.insert(skey, f32_to_bytes(node.salience))?; + Ok(()) +} + pub fn read_node(db: &Db, id: Uuid) -> EngramResult> { match db.get(node_key(id))? { Some(bytes) => Ok(Some(bincode::deserialize(&bytes)?)), diff --git a/crates/engram-core/src/types.rs b/engrams/engram-core/src/types.rs similarity index 68% rename from crates/engram-core/src/types.rs rename to engrams/engram-core/src/types.rs index a7125ae..c04da5e 100644 --- a/crates/engram-core/src/types.rs +++ b/engrams/engram-core/src/types.rs @@ -1,6 +1,20 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; +// ── Built-in edge type name constants ───────────────────────────────────────── +// +// String constants for the 8 built-in relation types. Use these on Edge.relation +// to reference known types. Intelligence about when to create new types or how +// to score confidence belongs in el, not here. +pub const EDGE_SUPERSEDES: &str = "supersedes"; +pub const EDGE_CAUSES: &str = "causes"; +pub const EDGE_CONTAINS: &str = "contains"; +pub const EDGE_REFERENCES: &str = "references"; +pub const EDGE_CONTRADICTS: &str = "contradicts"; +pub const EDGE_EXEMPLIFIES: &str = "exemplifies"; +pub const EDGE_ACTIVATES: &str = "activates"; +pub const EDGE_TEMPORALLY_PRECEDES: &str = "temporally_precedes"; + /// The functional role of a node in the memory graph. /// /// Different node types participate in different retrieval patterns: @@ -8,6 +22,7 @@ use uuid::Uuid; /// - Concepts and Entities form the semantic backbone /// - Processes encode procedural knowledge /// - InternalState captures the system's own affective context +/// - Custom(String) is an open extension point; el defines new types freely #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum NodeType { /// A specific remembered experience or observation @@ -22,6 +37,8 @@ pub enum NodeType { Process, /// An internal affective or motivational state InternalState, + /// Caller-defined node type. el uses this for types Rust does not need to know about. + Custom(String), } /// Where in the memory hierarchy a node currently lives. @@ -44,28 +61,35 @@ pub enum MemoryTier { Procedural, } -/// The typed relationship between two nodes. +/// Metadata record for a named edge type. Dumb data container β€” Rust stores and +/// returns it. All decisions about confidence thresholds, when to create new +/// types, merge/split logic, and pattern recognition belong in el, not here. /// -/// Relation types encode causal, temporal, hierarchical, and logical -/// structure into the graph itself β€” not just into metadata. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum RelationType { - /// This node replaces or obsoletes another - Supersedes, - /// This node is a causal precursor to another - Causes, - /// This node hierarchically contains another - Contains, - /// This node cites or points to another as supporting context - References, - /// This node is in logical tension with another - Contradicts, - /// This node is a concrete instance of a more abstract node - Exemplifies, - /// Co-activation: firing this node tends to fire the other - Activates, - /// Temporal ordering: this node came before the other - TemporallyPrecedes, +/// Fields like `deprecated`, `derived_from`, and `supersedes` are data. +/// They are set by the caller (el) and stored verbatim. Rust never inspects +/// or acts on them autonomously. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EdgeTypeDef { + /// Stable unique identifier for this edge type record + pub id: Uuid, + /// The canonical name used on edges (e.g. `"causes"`, `"resonates_with"`) + pub name: String, + /// Human-readable description of what this relation means + pub description: String, + /// Unix milliseconds when this type was first registered + pub first_observed: i64, + /// How many edges currently carry this type + pub instance_count: u64, + /// Caller-supplied confidence, 0.0–1.0. Stored as-is; not computed here. + pub confidence: f32, + /// Free-text note about what observation prompted this type's creation. + /// Set by el; stored verbatim. + pub derived_from: Option, + /// Name of the edge type this one replaced, if any. Set by el; stored verbatim. + pub supersedes: Option, + /// When true, this type should no longer be used for new edges. + /// Set by el; stored verbatim. + pub deprecated: bool, } /// A node in the engram graph β€” the fundamental unit of stored memory. @@ -161,12 +185,17 @@ pub struct ScoredNode { /// /// Edge weights strengthen with co-activation (Hebbian learning). /// The weight directly multiplies activation flow during spreading activation. +/// +/// The `relation` field is a free-form string naming the edge type β€” look up +/// the canonical definition in the `EdgeTypeDef` registry via `edge_type`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Edge { pub id: Uuid, pub from_id: Uuid, pub to_id: Uuid, - pub relation: RelationType, + /// The edge type name (e.g. `"causes"`, `"resonates_with"`). Matches the + /// `name` field of the corresponding `EdgeTypeDef` in the registry. + pub relation: String, /// Connection strength, 0.0–1.0. Increases when both endpoints are /// activated in close temporal proximity (long-term potentiation). pub weight: f32, @@ -176,13 +205,13 @@ pub struct Edge { } impl Edge { - pub fn new(from_id: Uuid, to_id: Uuid, relation: RelationType, weight: f32) -> Self { + pub fn new(from_id: Uuid, to_id: Uuid, relation: impl Into, weight: f32) -> Self { let now = now_ms(); Self { id: Uuid::new_v4(), from_id, to_id, - relation, + relation: relation.into(), weight: weight.clamp(0.0, 1.0), created_at: now, last_fired: now, diff --git a/crates/engram-core/src/vector.rs b/engrams/engram-core/src/vector.rs similarity index 100% rename from crates/engram-core/src/vector.rs rename to engrams/engram-core/src/vector.rs diff --git a/crates/engram-crypto/Cargo.toml b/engrams/engram-crypto/Cargo.toml similarity index 100% rename from crates/engram-crypto/Cargo.toml rename to engrams/engram-crypto/Cargo.toml diff --git a/crates/engram-crypto/src/algorithm.rs b/engrams/engram-crypto/src/algorithm.rs similarity index 100% rename from crates/engram-crypto/src/algorithm.rs rename to engrams/engram-crypto/src/algorithm.rs diff --git a/crates/engram-crypto/src/engine.rs b/engrams/engram-crypto/src/engine.rs similarity index 100% rename from crates/engram-crypto/src/engine.rs rename to engrams/engram-crypto/src/engine.rs diff --git a/crates/engram-crypto/src/error.rs b/engrams/engram-crypto/src/error.rs similarity index 100% rename from crates/engram-crypto/src/error.rs rename to engrams/engram-crypto/src/error.rs diff --git a/crates/engram-crypto/src/lib.rs b/engrams/engram-crypto/src/lib.rs similarity index 100% rename from crates/engram-crypto/src/lib.rs rename to engrams/engram-crypto/src/lib.rs diff --git a/crates/engram-crypto/src/registry.rs b/engrams/engram-crypto/src/registry.rs similarity index 100% rename from crates/engram-crypto/src/registry.rs rename to engrams/engram-crypto/src/registry.rs diff --git a/crates/engram-ffi/Cargo.toml b/engrams/engram-ffi/Cargo.toml similarity index 100% rename from crates/engram-ffi/Cargo.toml rename to engrams/engram-ffi/Cargo.toml diff --git a/crates/engram-ffi/src/lib.rs b/engrams/engram-ffi/src/lib.rs similarity index 100% rename from crates/engram-ffi/src/lib.rs rename to engrams/engram-ffi/src/lib.rs diff --git a/crates/engram-jni/Cargo.toml b/engrams/engram-jni/Cargo.toml similarity index 100% rename from crates/engram-jni/Cargo.toml rename to engrams/engram-jni/Cargo.toml diff --git a/crates/engram-jni/src/lib.rs b/engrams/engram-jni/src/lib.rs similarity index 100% rename from crates/engram-jni/src/lib.rs rename to engrams/engram-jni/src/lib.rs diff --git a/crates/engram-migrate/Cargo.toml b/engrams/engram-migrate/Cargo.toml similarity index 100% rename from crates/engram-migrate/Cargo.toml rename to engrams/engram-migrate/Cargo.toml diff --git a/crates/engram-migrate/src/main.rs b/engrams/engram-migrate/src/main.rs similarity index 100% rename from crates/engram-migrate/src/main.rs rename to engrams/engram-migrate/src/main.rs diff --git a/crates/engram-projection/Cargo.toml b/engrams/engram-projection/Cargo.toml similarity index 100% rename from crates/engram-projection/Cargo.toml rename to engrams/engram-projection/Cargo.toml diff --git a/crates/engram-projection/src/engine.rs b/engrams/engram-projection/src/engine.rs similarity index 97% rename from crates/engram-projection/src/engine.rs rename to engrams/engram-projection/src/engine.rs index dd91d91..bef75e4 100644 --- a/crates/engram-projection/src/engine.rs +++ b/engrams/engram-projection/src/engine.rs @@ -98,7 +98,7 @@ fn matches_filter(a: &ActivatedNode, filter: &NodeFilter) -> bool { NodeFilter::ByType(types) => { let node_type_str = node_type_str(&a.node.node_type); - types.iter().any(|t| t == node_type_str) + types.iter().any(|t| t == &node_type_str) } NodeFilter::ByTier(tiers) => tiers.iter().any(|t| t == &a.node.tier), @@ -195,14 +195,15 @@ fn traverse_json_path<'a>(doc: &'a Value, path: &str) -> Option<&'a Value> { // ── String helpers ──────────────────────────────────────────────────────────── -fn node_type_str(t: &NodeType) -> &'static str { +fn node_type_str(t: &NodeType) -> String { match t { - NodeType::Memory => "Memory", - NodeType::Concept => "Concept", - NodeType::Event => "Event", - NodeType::Entity => "Entity", - NodeType::Process => "Process", - NodeType::InternalState => "InternalState", + NodeType::Memory => "Memory".to_string(), + NodeType::Concept => "Concept".to_string(), + NodeType::Event => "Event".to_string(), + NodeType::Entity => "Entity".to_string(), + NodeType::Process => "Process".to_string(), + NodeType::InternalState => "InternalState".to_string(), + NodeType::Custom(s) => s.clone(), } } diff --git a/crates/engram-projection/src/error.rs b/engrams/engram-projection/src/error.rs similarity index 100% rename from crates/engram-projection/src/error.rs rename to engrams/engram-projection/src/error.rs diff --git a/crates/engram-projection/src/lib.rs b/engrams/engram-projection/src/lib.rs similarity index 100% rename from crates/engram-projection/src/lib.rs rename to engrams/engram-projection/src/lib.rs diff --git a/crates/engram-projection/src/registry.rs b/engrams/engram-projection/src/registry.rs similarity index 100% rename from crates/engram-projection/src/registry.rs rename to engrams/engram-projection/src/registry.rs diff --git a/crates/engram-projection/src/schema.rs b/engrams/engram-projection/src/schema.rs similarity index 100% rename from crates/engram-projection/src/schema.rs rename to engrams/engram-projection/src/schema.rs diff --git a/crates/engram-reasoning/Cargo.toml b/engrams/engram-reasoning/Cargo.toml similarity index 100% rename from crates/engram-reasoning/Cargo.toml rename to engrams/engram-reasoning/Cargo.toml diff --git a/crates/engram-reasoning/src/engine.rs b/engrams/engram-reasoning/src/engine.rs similarity index 96% rename from crates/engram-reasoning/src/engine.rs rename to engrams/engram-reasoning/src/engine.rs index 82a1f97..529a752 100644 --- a/crates/engram-reasoning/src/engine.rs +++ b/engrams/engram-reasoning/src/engine.rs @@ -33,7 +33,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use engram_core::{EngramDb, EngramResult}; -use engram_core::types::{Node, NodeType, RelationType}; +use engram_core::types::{Node, NodeType}; use uuid::Uuid; use crate::types::{ @@ -223,7 +223,7 @@ impl ReasoningEngine { /// Find causal chains: what causes a concept, or what a concept causes. /// - /// Traverses the graph following `RelationType::Causes` edges in the + /// Traverses the graph following `"causes"` edges in the /// requested direction. pub fn causal_chain( &mut self, @@ -252,7 +252,7 @@ impl ReasoningEngine { let db = self.db.lock().map_err(|_| { engram_core::EngramError::InvalidParam("db lock poisoned".into()) })?; - db.traverse(seed.id, Some(RelationType::Causes), self.config.max_depth as u8)? + db.traverse(seed.id, Some("causes"), self.config.max_depth as u8)? }; // Build evidence nodes β€” always start with the seed as the first node @@ -326,7 +326,7 @@ impl ReasoningEngine { /// Find ordered steps for a HowTo query. /// - /// Traverses `RelationType::Causes` and `RelationType::Contains` edges + /// Traverses `"causes"` and `"contains"` edges /// from Process/Procedural nodes matching the goal embedding. pub fn procedural_chain(&mut self, goal_embedding: &[f32]) -> EngramResult> { let process_nodes: Vec = { @@ -363,7 +363,7 @@ impl ReasoningEngine { let db = self.db.lock().map_err(|_| { engram_core::EngramError::InvalidParam("db lock poisoned".into()) })?; - db.traverse(best_process.id, Some(RelationType::Causes), self.config.max_depth as u8)? + db.traverse(best_process.id, Some("causes"), self.config.max_depth as u8)? }; let mut ordered_steps: Vec = Vec::new(); @@ -437,7 +437,7 @@ impl ReasoningEngine { if let Some(from_node) = db.get_node(*node_id)? { let edges = db.get_edges_from(*node_id)?; for edge in edges { - if edge.relation == RelationType::Contradicts { + if edge.relation == "contradicts" { if let Some(target) = db.get_node(edge.to_id)? { let sim_a = cosine_sim(topic_embedding, &from_node.embedding); let sim_b = cosine_sim(topic_embedding, &target.embedding); @@ -976,16 +976,21 @@ impl ReasoningEngine { // ── Edge mapping helpers ────────────────────────────────────────────────────── -fn relation_to_inference_edge(relation: &RelationType) -> InferenceEdgeType { +fn relation_to_inference_edge(relation: &str) -> InferenceEdgeType { match relation { - RelationType::Causes => InferenceEdgeType::Causes, - RelationType::Contradicts => InferenceEdgeType::Contradicts, - RelationType::Supersedes => InferenceEdgeType::Implies, - RelationType::Contains => InferenceEdgeType::Requires, - RelationType::References => InferenceEdgeType::SimilarTo, - RelationType::Exemplifies => InferenceEdgeType::InstanceOf, - RelationType::Activates => InferenceEdgeType::Supports, - RelationType::TemporallyPrecedes => InferenceEdgeType::Causes, + "causes" => InferenceEdgeType::Causes, + "contradicts" => InferenceEdgeType::Contradicts, + "supersedes" => InferenceEdgeType::Implies, + "contains" => InferenceEdgeType::Requires, + "references" => InferenceEdgeType::SimilarTo, + "exemplifies" => InferenceEdgeType::InstanceOf, + "activates" => InferenceEdgeType::Supports, + "temporally_precedes" => InferenceEdgeType::Causes, + // Personhood types map to their closest structural equivalent + "grounded_in" | "reinforced_by" | "derives_from" | "resonates_with" => InferenceEdgeType::Supports, + "in_tension_with" | "challenged_by" => InferenceEdgeType::Contradicts, + "expressed_through" | "shaped_by" => InferenceEdgeType::Implies, + _ => InferenceEdgeType::SimilarTo, } } diff --git a/crates/engram-reasoning/src/lib.rs b/engrams/engram-reasoning/src/lib.rs similarity index 99% rename from crates/engram-reasoning/src/lib.rs rename to engrams/engram-reasoning/src/lib.rs index 62b13e3..c59ffa8 100644 --- a/crates/engram-reasoning/src/lib.rs +++ b/engrams/engram-reasoning/src/lib.rs @@ -15,7 +15,7 @@ /// # Quick Start /// /// ```rust,no_run -/// use engram_core::{EngramDb, Node, Edge, NodeType, MemoryTier, RelationType}; +/// use engram_core::{EngramDb, Node, Edge, NodeType, MemoryTier}; /// use engram_reasoning::{ReasoningEngine, Hypothesis, HypothesisType, ReasoningConfig}; /// use std::path::Path; /// use std::sync::{Arc, Mutex}; diff --git a/crates/engram-reasoning/src/tests.rs b/engrams/engram-reasoning/src/tests.rs similarity index 98% rename from crates/engram-reasoning/src/tests.rs rename to engrams/engram-reasoning/src/tests.rs index b4cd136..9c541d1 100644 --- a/crates/engram-reasoning/src/tests.rs +++ b/engrams/engram-reasoning/src/tests.rs @@ -9,7 +9,7 @@ mod tests { use tempfile::TempDir; use uuid::Uuid; - use engram_core::{Edge, EngramDb, MemoryTier, Node, NodeType, RelationType}; + use engram_core::{Edge, EngramDb, MemoryTier, Node, NodeType}; use crate::{ CausalDirection, EvidenceNode, EvidenceType, Hypothesis, HypothesisType, InferenceEdge, @@ -51,7 +51,7 @@ mod tests { id } - fn make_edge(db: &Arc>, from: Uuid, to: Uuid, relation: RelationType) { + fn make_edge(db: &Arc>, from: Uuid, to: Uuid, relation: &str) { let edge = Edge::new(from, to, relation, 0.8); db.lock().unwrap().put_edge(edge).unwrap(); } @@ -339,8 +339,8 @@ mod tests { let id_b = make_node(&db, "Expansion causes pressure", emb_b, NodeType::Concept, 0.8); let id_c = make_node(&db, "Pressure causes rupture", emb_c, NodeType::Concept, 0.7); - make_edge(&db, id_a, id_b, RelationType::Causes); - make_edge(&db, id_b, id_c, RelationType::Causes); + make_edge(&db, id_a, id_b, "causes"); + make_edge(&db, id_b, id_c, "causes"); let mut engine = make_engine(db.clone()); let chains = engine.causal_chain(&emb_a, CausalDirection::Forward).unwrap(); @@ -373,7 +373,7 @@ mod tests { NodeType::Memory, 0.9, ); - make_edge(&db, id_a, id_b, RelationType::Contradicts); + make_edge(&db, id_a, id_b, "contradicts"); let mut engine = make_engine(db.clone()); let contradictions = engine.find_contradictions(&emb_topic).unwrap(); @@ -482,8 +482,8 @@ mod tests { NodeType::Process, 0.9, ); - make_edge(&db, id_1, id_2, RelationType::Causes); - make_edge(&db, id_2, id_3, RelationType::Causes); + make_edge(&db, id_1, id_2, "causes"); + make_edge(&db, id_2, id_3, "causes"); let mut engine = make_engine(db.clone()); let steps = engine.procedural_chain(&goal_emb).unwrap(); diff --git a/crates/engram-reasoning/src/types.rs b/engrams/engram-reasoning/src/types.rs similarity index 100% rename from crates/engram-reasoning/src/types.rs rename to engrams/engram-reasoning/src/types.rs diff --git a/crates/engram-server/Cargo.toml b/engrams/engram-server/Cargo.toml similarity index 100% rename from crates/engram-server/Cargo.toml rename to engrams/engram-server/Cargo.toml diff --git a/crates/engram-server/src/auth.rs b/engrams/engram-server/src/auth.rs similarity index 100% rename from crates/engram-server/src/auth.rs rename to engrams/engram-server/src/auth.rs diff --git a/crates/engram-server/src/main.rs b/engrams/engram-server/src/main.rs similarity index 74% rename from crates/engram-server/src/main.rs rename to engrams/engram-server/src/main.rs index 6b0c120..34c8a9b 100644 --- a/crates/engram-server/src/main.rs +++ b/engrams/engram-server/src/main.rs @@ -5,9 +5,9 @@ /// ## Core /// GET /stats β€” node/edge counts /// POST /nodes β€” create a node -/// GET /nodes/{id} β€” get a node +/// GET /nodes/:id β€” get a node /// POST /edges β€” create an edge -/// GET /nodes/{id}/edges β€” list edges from a node +/// GET /nodes/:id/edges β€” list edges from a node /// POST /activate β€” spreading activation /// POST /search β€” embedding search /// POST /decay β€” apply salience decay @@ -18,7 +18,7 @@ /// POST /sync/push β€” receive incoming delta /// POST /sync/peers β€” register peer /// GET /sync/peers β€” list peers -/// DELETE /sync/peers/{id} β€” remove peer +/// DELETE /sync/peers/:id β€” remove peer /// /// ## Swarm /// POST /swarm/activate β€” distributed activation (auth required) @@ -188,7 +188,7 @@ async fn main() -> anyhow::Result<()> { .route("/sync/push", post(routes::sync::push_delta)) .route("/sync/peers", get(routes::sync::list_peers)) .route("/sync/peers", post(routes::sync::register_peer)) - .route("/sync/peers/{id}", delete(routes::sync::delete_peer)) + .route("/sync/peers/:id", delete(routes::sync::delete_peer)) .route("/swarm/activate", post(routes::swarm::swarm_activate)) .route("/swarm/status", get(routes::swarm::swarm_status)) .layer(middleware::from_fn_with_state( @@ -200,9 +200,10 @@ async fn main() -> anyhow::Result<()> { let core_routes = Router::new() .route("/stats", get(routes::core::get_stats)) .route("/nodes", post(routes::core::create_node)) - .route("/nodes/{id}", get(routes::core::get_node)) + .route("/nodes/list", get(routes::core::list_nodes)) + .route("/nodes/:id", get(routes::core::get_node)) .route("/edges", post(routes::core::create_edge)) - .route("/nodes/{id}/edges", get(routes::core::get_edges_from)) + .route("/nodes/:id/edges", get(routes::core::get_edges_from)) .route("/activate", post(routes::core::activate)) .route("/search", post(routes::core::search_embedding)) .route("/decay", post(routes::core::decay)) @@ -213,20 +214,20 @@ async fn main() -> anyhow::Result<()> { .route("/projections", post(routes::projection::register_projection)) .route("/projections", get(routes::projection::list_projections)) .route( - "/projections/{name}/schema", + "/projections/:name/schema", get(routes::projection::get_projection_schema), ) .route( - "/projections/{name}/query", + "/projections/:name/query", post(routes::projection::query_projection), ); // Transaction routes (no auth β€” add auth layer if needed) let tx_routes = Router::new() .route("/tx/apply", post(routes::tx::tx_apply)) - .route("/tx/rollback/{command_id}", post(routes::tx::tx_rollback)) + .route("/tx/rollback/:command_id", post(routes::tx::tx_rollback)) .route("/tx/history", get(routes::tx::tx_history)) - .route("/tx/chain/{command_id}", get(routes::tx::tx_causal_chain)); + .route("/tx/chain/:command_id", get(routes::tx::tx_causal_chain)); // Reasoning routes (no auth β€” graph-native inference) let reasoning_routes = Router::new() @@ -234,18 +235,47 @@ async fn main() -> anyhow::Result<()> { .route("/reason/causal", post(routes::reasoning::causal)) .route("/reason/contradictions", post(routes::reasoning::contradictions)); - let studio_routes = Router::new() + let app = Router::new() + // Studio .route("/", get(serve_studio_index)) .route("/studio", get(serve_studio_index)) - .route("/studio/{*path}", get(serve_studio_asset)); - - let app = Router::new() - .merge(studio_routes) - .merge(core_routes) - .merge(sync_routes) - .merge(projection_routes) - .merge(tx_routes) - .merge(reasoning_routes) + // Core API + .route("/stats", get(routes::core::get_stats)) + .route("/nodes", post(routes::core::create_node)) + .route("/nodes/list", get(routes::core::list_nodes)) + .route("/nodes/:id", get(routes::core::get_node)) + .route("/edges", post(routes::core::create_edge)) + .route("/nodes/:id/edges", get(routes::core::get_edges_from)) + .route("/activate", post(routes::core::activate)) + .route("/search", post(routes::core::search_embedding)) + .route("/decay", post(routes::core::decay)) + .route("/consolidate", post(routes::core::consolidate)) + // Projection + .route("/projections", post(routes::projection::register_projection)) + .route("/projections", get(routes::projection::list_projections)) + .route("/projections/:name/schema", get(routes::projection::get_projection_schema)) + .route("/projections/:name/query", post(routes::projection::query_projection)) + // Transactions + .route("/tx/apply", post(routes::tx::tx_apply)) + .route("/tx/rollback/:command_id", post(routes::tx::tx_rollback)) + .route("/tx/history", get(routes::tx::tx_history)) + .route("/tx/chain/:command_id", get(routes::tx::tx_causal_chain)) + // Reasoning + .route("/reason", post(routes::reasoning::reason)) + .route("/reason/causal", post(routes::reasoning::causal)) + .route("/reason/contradictions", post(routes::reasoning::contradictions)) + // Sync + Swarm routes added directly (auth is in the handlers themselves for now) + .route("/sync/delta", get(routes::sync::get_delta)) + .route("/sync/push", post(routes::sync::push_delta)) + .route("/sync/peers", get(routes::sync::list_peers)) + .route("/sync/peers", post(routes::sync::register_peer)) + .route("/sync/peers/:id", delete(routes::sync::delete_peer)) + .route("/swarm/activate", post(routes::swarm::swarm_activate)) + .route("/swarm/status", get(routes::swarm::swarm_status)) + .fallback(|uri: axum::http::Uri| async move { + tracing::warn!("FALLBACK hit for: {}", uri); + (axum::http::StatusCode::NOT_FOUND, format!("FALLBACK: {}", uri)) + }) .layer(CorsLayer::permissive()) .with_state(state); diff --git a/crates/engram-server/src/routes/core.rs b/engrams/engram-server/src/routes/core.rs similarity index 84% rename from crates/engram-server/src/routes/core.rs rename to engrams/engram-server/src/routes/core.rs index d9ecaea..4c63807 100644 --- a/crates/engram-server/src/routes/core.rs +++ b/engrams/engram-server/src/routes/core.rs @@ -4,7 +4,7 @@ use axum::{ http::StatusCode, Json, }; -use engram_core::types::{Edge, MemoryTier, Node, NodeType, RelationType}; +use engram_core::types::{Edge, MemoryTier, Node, NodeType}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; @@ -54,14 +54,41 @@ pub async fn create_node( Ok(Json(CreateNodeResponse { id })) } +/// List all nodes (scan-based, no embedding needed) +pub async fn list_nodes( + State(state): State>, +) -> Result>, StatusCode> { + let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let nodes = db.scan_nodes().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + Ok(Json(nodes)) +} + pub async fn get_node( State(state): State>, - Path(id): Path, + Path(id_str): Path, ) -> Result, StatusCode> { - let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - match db.get_node(id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? { - Some(node) => Ok(Json(node)), - None => Err(StatusCode::NOT_FOUND), + tracing::info!("get_node called with raw id={}", id_str); + let id = id_str.parse::().map_err(|e| { + tracing::warn!("get_node: invalid UUID '{}': {}", id_str, e); + StatusCode::BAD_REQUEST + })?; + let db = state.db.lock().map_err(|_| { + tracing::error!("get_node: mutex poisoned"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + match db.get_node(id) { + Ok(Some(node)) => { + tracing::info!("get_node: found node id={}", id); + Ok(Json(node)) + } + Ok(None) => { + tracing::warn!("get_node: node not found id={}", id); + Err(StatusCode::NOT_FOUND) + } + Err(e) => { + tracing::error!("get_node: db error: {:?}", e); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } } } @@ -71,7 +98,8 @@ pub async fn get_node( pub struct CreateEdgeRequest { pub from_id: Uuid, pub to_id: Uuid, - pub relation: RelationType, + /// Edge type name, e.g. `"causes"`, `"resonates_with"`, or any dynamic type. + pub relation: String, pub weight: f32, } diff --git a/crates/engram-server/src/routes/mod.rs b/engrams/engram-server/src/routes/mod.rs similarity index 100% rename from crates/engram-server/src/routes/mod.rs rename to engrams/engram-server/src/routes/mod.rs diff --git a/crates/engram-server/src/routes/projection.rs b/engrams/engram-server/src/routes/projection.rs similarity index 100% rename from crates/engram-server/src/routes/projection.rs rename to engrams/engram-server/src/routes/projection.rs diff --git a/crates/engram-server/src/routes/reasoning.rs b/engrams/engram-server/src/routes/reasoning.rs similarity index 100% rename from crates/engram-server/src/routes/reasoning.rs rename to engrams/engram-server/src/routes/reasoning.rs diff --git a/crates/engram-server/src/routes/swarm.rs b/engrams/engram-server/src/routes/swarm.rs similarity index 100% rename from crates/engram-server/src/routes/swarm.rs rename to engrams/engram-server/src/routes/swarm.rs diff --git a/crates/engram-server/src/routes/sync.rs b/engrams/engram-server/src/routes/sync.rs similarity index 100% rename from crates/engram-server/src/routes/sync.rs rename to engrams/engram-server/src/routes/sync.rs diff --git a/crates/engram-server/src/routes/tx.rs b/engrams/engram-server/src/routes/tx.rs similarity index 100% rename from crates/engram-server/src/routes/tx.rs rename to engrams/engram-server/src/routes/tx.rs diff --git a/crates/engram-server/src/state.rs b/engrams/engram-server/src/state.rs similarity index 100% rename from crates/engram-server/src/state.rs rename to engrams/engram-server/src/state.rs diff --git a/crates/engram-sync/Cargo.toml b/engrams/engram-sync/Cargo.toml similarity index 100% rename from crates/engram-sync/Cargo.toml rename to engrams/engram-sync/Cargo.toml diff --git a/crates/engram-sync/src/client.rs b/engrams/engram-sync/src/client.rs similarity index 100% rename from crates/engram-sync/src/client.rs rename to engrams/engram-sync/src/client.rs diff --git a/crates/engram-sync/src/engine.rs b/engrams/engram-sync/src/engine.rs similarity index 100% rename from crates/engram-sync/src/engine.rs rename to engrams/engram-sync/src/engine.rs diff --git a/crates/engram-sync/src/lib.rs b/engrams/engram-sync/src/lib.rs similarity index 100% rename from crates/engram-sync/src/lib.rs rename to engrams/engram-sync/src/lib.rs diff --git a/crates/engram-sync/src/types.rs b/engrams/engram-sync/src/types.rs similarity index 100% rename from crates/engram-sync/src/types.rs rename to engrams/engram-sync/src/types.rs diff --git a/crates/engram-tx/Cargo.toml b/engrams/engram-tx/Cargo.toml similarity index 100% rename from crates/engram-tx/Cargo.toml rename to engrams/engram-tx/Cargo.toml diff --git a/crates/engram-tx/src/command.rs b/engrams/engram-tx/src/command.rs similarity index 100% rename from crates/engram-tx/src/command.rs rename to engrams/engram-tx/src/command.rs diff --git a/crates/engram-tx/src/engine.rs b/engrams/engram-tx/src/engine.rs similarity index 93% rename from crates/engram-tx/src/engine.rs rename to engrams/engram-tx/src/engine.rs index fe6691a..3386d44 100644 --- a/crates/engram-tx/src/engine.rs +++ b/engrams/engram-tx/src/engine.rs @@ -3,7 +3,7 @@ /// The engine wraps an `EngramDb` and a `CommandLog`. All mutations go through /// `apply()`. Rollbacks create new inverse commands. Rolling back a rollback /// re-applies the original β€” full undo/redo with causal tracking. -use engram_core::types::{Edge, Node, NodeType, RelationType}; +use engram_core::types::{Edge, Node, NodeType}; use engram_core::EngramDb; use serde_json::Value; use uuid::Uuid; @@ -319,8 +319,7 @@ impl TransactionEngine { fn exec_create_edge(&self, payload: &Value) -> TxResult<()> { let p: CreateEdgePayload = serde_json::from_value(payload.clone())?; - let relation = parse_relation(&p.relation)?; - let edge = Edge::new(p.from_id, p.to_id, relation, p.weight); + let edge = Edge::new(p.from_id, p.to_id, p.relation, p.weight); let db = self.db.lock().map_err(|_| TxError::Invalid("lock poisoned".into()))?; db.put_edge(edge)?; Ok(()) @@ -465,12 +464,11 @@ pub fn build_create_edge_cmd( idempotency_key: impl Into, peer_id: Option, ) -> Command { - let relation_str = relation_to_str(&edge.relation).to_string(); let payload = serde_json::to_value(CreateEdgePayload { edge_id: edge.id, from_id: edge.from_id, to_id: edge.to_id, - relation: relation_str.clone(), + relation: edge.relation.clone(), weight: edge.weight, }) .unwrap_or(Value::Null); @@ -505,45 +503,19 @@ fn parse_node_type(s: &str) -> TxResult { "Entity" => Ok(NodeType::Entity), "Process" => Ok(NodeType::Process), "InternalState" => Ok(NodeType::InternalState), - _ => Err(TxError::Invalid(format!("unknown node type: {}", s))), + other => Ok(NodeType::Custom(other.to_string())), } } -fn node_type_to_str(t: &NodeType) -> &'static str { +fn node_type_to_str(t: &NodeType) -> String { match t { - NodeType::Memory => "Memory", - NodeType::Concept => "Concept", - NodeType::Event => "Event", - NodeType::Entity => "Entity", - NodeType::Process => "Process", - NodeType::InternalState => "InternalState", - } -} - -fn parse_relation(s: &str) -> TxResult { - match s { - "Supersedes" => Ok(RelationType::Supersedes), - "Causes" => Ok(RelationType::Causes), - "Contains" => Ok(RelationType::Contains), - "References" => Ok(RelationType::References), - "Contradicts" => Ok(RelationType::Contradicts), - "Exemplifies" => Ok(RelationType::Exemplifies), - "Activates" => Ok(RelationType::Activates), - "TemporallyPrecedes" => Ok(RelationType::TemporallyPrecedes), - _ => Err(TxError::Invalid(format!("unknown relation: {}", s))), - } -} - -fn relation_to_str(r: &RelationType) -> &'static str { - match r { - RelationType::Supersedes => "Supersedes", - RelationType::Causes => "Causes", - RelationType::Contains => "Contains", - RelationType::References => "References", - RelationType::Contradicts => "Contradicts", - RelationType::Exemplifies => "Exemplifies", - RelationType::Activates => "Activates", - RelationType::TemporallyPrecedes => "TemporallyPrecedes", + NodeType::Memory => "Memory".to_string(), + NodeType::Concept => "Concept".to_string(), + NodeType::Event => "Event".to_string(), + NodeType::Entity => "Entity".to_string(), + NodeType::Process => "Process".to_string(), + NodeType::InternalState => "InternalState".to_string(), + NodeType::Custom(s) => s.clone(), } } @@ -697,7 +669,7 @@ mod tests { engine.apply(build_create_node_cmd(&n2, "edge-n2", None)).unwrap(); // Create edge - let edge = Edge::new(n1.id, n2.id, RelationType::References, 0.7); + let edge = Edge::new(n1.id, n2.id, "references", 0.7); let edge_cmd = build_create_edge_cmd(&edge, "edge-create-1", None); let result = engine.apply(edge_cmd).unwrap(); assert_eq!(result.status, CommandStatus::Applied); diff --git a/crates/engram-tx/src/error.rs b/engrams/engram-tx/src/error.rs similarity index 100% rename from crates/engram-tx/src/error.rs rename to engrams/engram-tx/src/error.rs diff --git a/crates/engram-tx/src/lib.rs b/engrams/engram-tx/src/lib.rs similarity index 100% rename from crates/engram-tx/src/lib.rs rename to engrams/engram-tx/src/lib.rs diff --git a/crates/engram-tx/src/log.rs b/engrams/engram-tx/src/log.rs similarity index 100% rename from crates/engram-tx/src/log.rs rename to engrams/engram-tx/src/log.rs diff --git a/examples/basic.rs b/examples/basic.rs index a66713d..edda60f 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -7,7 +7,8 @@ /// The nodes represent a tiny knowledge graph about the spreading activation /// model itself β€” somewhat recursive, intentionally. use engram_core::{ - ActivatedNode, ConsolidationConfig, Edge, EngramDb, MemoryTier, Node, NodeType, RelationType, + ActivatedNode, ConsolidationConfig, Edge, EngramDb, MemoryTier, Node, NodeType, + EDGE_ACTIVATES, EDGE_CAUSES, EDGE_EXEMPLIFIES, EDGE_REFERENCES, EDGE_TEMPORALLY_PRECEDES, }; use std::path::Path; @@ -98,17 +99,17 @@ fn main() -> Result<(), Box> { // concepts reliably co-activate. Weaker weights mean looser association. // Spreading activation Causes long-term potentiation (strong causal link) - db.put_edge(Edge::new(id0, id1, RelationType::Causes, 0.9))?; + db.put_edge(Edge::new(id0, id1, EDGE_CAUSES, 0.9))?; // LTP is Referenced by Hebbian learning - db.put_edge(Edge::new(id1, id2, RelationType::References, 0.85))?; + db.put_edge(Edge::new(id1, id2, EDGE_REFERENCES, 0.85))?; // Spreading activation Activates associative memory - db.put_edge(Edge::new(id0, id3, RelationType::Activates, 0.88))?; + db.put_edge(Edge::new(id0, id3, EDGE_ACTIVATES, 0.88))?; // Hebbian learning Exemplifies associative memory - db.put_edge(Edge::new(id2, id3, RelationType::Exemplifies, 0.80))?; - // Salience decay Supersedes naive forgetting - db.put_edge(Edge::new(id4, id5, RelationType::TemporallyPrecedes, 0.65))?; + db.put_edge(Edge::new(id2, id3, EDGE_EXEMPLIFIES, 0.80))?; + // Salience decay TemporallyPrecedes naive forgetting + db.put_edge(Edge::new(id4, id5, EDGE_TEMPORALLY_PRECEDES, 0.65))?; // LTP TemporallyPrecedes memory consolidation - db.put_edge(Edge::new(id1, id5, RelationType::TemporallyPrecedes, 0.72))?; + db.put_edge(Edge::new(id1, id5, EDGE_TEMPORALLY_PRECEDES, 0.72))?; println!("Inserted {} edges", db.edge_count()?); println!(" node0 --[Causes]--> node1"); diff --git a/bindings/go/README.md b/receptors/go/README.md similarity index 100% rename from bindings/go/README.md rename to receptors/go/README.md diff --git a/bindings/go/engram.go b/receptors/go/engram.go similarity index 100% rename from bindings/go/engram.go rename to receptors/go/engram.go diff --git a/bindings/go/engram.h b/receptors/go/engram.h similarity index 100% rename from bindings/go/engram.h rename to receptors/go/engram.h diff --git a/bindings/go/engram_test.go b/receptors/go/engram_test.go similarity index 100% rename from bindings/go/engram_test.go rename to receptors/go/engram_test.go diff --git a/bindings/go/go.mod b/receptors/go/go.mod similarity index 100% rename from bindings/go/go.mod rename to receptors/go/go.mod diff --git a/bindings/kotlin/README.md b/receptors/kotlin/README.md similarity index 100% rename from bindings/kotlin/README.md rename to receptors/kotlin/README.md diff --git a/bindings/kotlin/build.gradle.kts b/receptors/kotlin/build.gradle.kts similarity index 100% rename from bindings/kotlin/build.gradle.kts rename to receptors/kotlin/build.gradle.kts diff --git a/bindings/kotlin/settings.gradle.kts b/receptors/kotlin/settings.gradle.kts similarity index 100% rename from bindings/kotlin/settings.gradle.kts rename to receptors/kotlin/settings.gradle.kts diff --git a/bindings/kotlin/src/main/kotlin/ai/neuron/engram/ActivatedNode.kt b/receptors/kotlin/src/main/kotlin/ai/neuron/engram/ActivatedNode.kt similarity index 100% rename from bindings/kotlin/src/main/kotlin/ai/neuron/engram/ActivatedNode.kt rename to receptors/kotlin/src/main/kotlin/ai/neuron/engram/ActivatedNode.kt diff --git a/bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramDb.kt b/receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramDb.kt similarity index 100% rename from bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramDb.kt rename to receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramDb.kt diff --git a/bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramEdge.kt b/receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramEdge.kt similarity index 100% rename from bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramEdge.kt rename to receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramEdge.kt diff --git a/bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramNode.kt b/receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramNode.kt similarity index 100% rename from bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramNode.kt rename to receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramNode.kt diff --git a/bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramTypes.kt b/receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramTypes.kt similarity index 100% rename from bindings/kotlin/src/main/kotlin/ai/neuron/engram/EngramTypes.kt rename to receptors/kotlin/src/main/kotlin/ai/neuron/engram/EngramTypes.kt diff --git a/bindings/typescript/Cargo.toml b/receptors/typescript/Cargo.toml similarity index 100% rename from bindings/typescript/Cargo.toml rename to receptors/typescript/Cargo.toml diff --git a/bindings/typescript/README.md b/receptors/typescript/README.md similarity index 100% rename from bindings/typescript/README.md rename to receptors/typescript/README.md diff --git a/bindings/typescript/package.json b/receptors/typescript/package.json similarity index 100% rename from bindings/typescript/package.json rename to receptors/typescript/package.json diff --git a/bindings/typescript/src/index.ts b/receptors/typescript/src/index.ts similarity index 100% rename from bindings/typescript/src/index.ts rename to receptors/typescript/src/index.ts diff --git a/bindings/typescript/src/lib.rs b/receptors/typescript/src/lib.rs similarity index 100% rename from bindings/typescript/src/lib.rs rename to receptors/typescript/src/lib.rs diff --git a/bindings/typescript/src/types.ts b/receptors/typescript/src/types.ts similarity index 100% rename from bindings/typescript/src/types.ts rename to receptors/typescript/src/types.ts diff --git a/bindings/typescript/tsconfig.json b/receptors/typescript/tsconfig.json similarity index 100% rename from bindings/typescript/tsconfig.json rename to receptors/typescript/tsconfig.json diff --git a/studio/index.html b/studio/index.html index 6854609..d696a44 100644 --- a/studio/index.html +++ b/studio/index.html @@ -1202,6 +1202,424 @@ tr { cursor: pointer; transition: background 0.1s; } } /* Graph canvas: remote peer nodes get dashed overlay β€” drawn via canvas 2d */ + +/* ── CHAT TAB ── */ +#chat-panel { + display: flex; + flex-direction: row; + overflow: hidden; + background: var(--bg); +} + +#chat-sessions-col { + width: 220px; + min-width: 220px; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--bg2); +} + +#chat-sessions-header { + padding: 12px 14px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +#new-chat-btn { + width: 100%; + margin-bottom: 0; +} + +#chat-session-list { + flex: 1; + overflow-y: auto; + padding: 6px 0; +} + +#chat-session-list::-webkit-scrollbar { width: 3px; } +#chat-session-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; } + +.chat-session-item { + padding: 8px 14px; + cursor: pointer; + border-left: 2px solid transparent; + transition: all 0.15s; + font-size: 11px; + color: var(--text3); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chat-session-item:hover { background: var(--bg3); color: var(--text2); } +.chat-session-item.active { + border-left-color: var(--accent); + background: var(--accent-dim); + color: var(--accent); +} + +#chat-main-col { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +#chat-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +#chat-messages::-webkit-scrollbar { width: 4px; } +#chat-messages::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; } + +.chat-msg { + max-width: 85%; + animation: msg-in 0.2s ease; +} + +@keyframes msg-in { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + +.chat-msg.user { align-self: flex-end; } +.chat-msg.assistant { align-self: flex-start; } + +.chat-msg-bubble { + padding: 10px 14px; + border-radius: 8px; + font-size: 12px; + line-height: 1.6; + position: relative; +} + +.chat-msg.user .chat-msg-bubble { + background: var(--accent-dim); + border: 1px solid rgba(56,189,248,0.25); + color: var(--text); +} + +.chat-msg.assistant .chat-msg-bubble { + background: var(--bg2); + border: 1px solid var(--border2); + color: var(--text2); +} + +.chat-msg-meta { + display: flex; + align-items: center; + gap: 8px; + margin-top: 4px; + padding: 0 2px; +} + +.chat-msg-role { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text3); +} + +.chat-tts-btn { + background: none; + border: none; + cursor: pointer; + color: var(--text3); + font-size: 12px; + padding: 0 2px; + transition: color 0.15s; + line-height: 1; +} + +.chat-tts-btn:hover { color: var(--accent); } + +/* Tool call blocks */ +.tool-block { + background: var(--bg3); + border: 1px solid var(--border2); + border-radius: 6px; + margin: 8px 0; + overflow: hidden; + font-family: 'DM Mono', monospace; + font-size: 10px; +} + +.tool-block-header { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + cursor: pointer; + background: var(--bg4); + user-select: none; +} + +.tool-block-name { + color: var(--accent); + font-weight: 500; +} + +.tool-block-toggle { + margin-left: auto; + color: var(--text3); + font-size: 9px; +} + +.tool-block-body { + padding: 8px 10px; + display: none; + border-top: 1px solid var(--border); + white-space: pre-wrap; + word-break: break-all; + color: var(--text2); + max-height: 200px; + overflow-y: auto; +} + +.tool-block-body.open { display: block; } + +.tool-result-content { + padding: 6px 10px; + color: #4ade80; + font-size: 10px; + font-family: 'DM Mono', monospace; + border-top: 1px solid var(--border); + max-height: 120px; + overflow-y: auto; + display: none; + white-space: pre-wrap; + word-break: break-all; +} + +.tool-result-content.show { display: block; } + +/* Markdown rendered in chat */ +.chat-md h1, .chat-md h2, .chat-md h3 { + color: var(--text); + font-family: 'Syne', sans-serif; + margin: 10px 0 5px; +} +.chat-md h1 { font-size: 15px; } +.chat-md h2 { font-size: 13px; } +.chat-md h3 { font-size: 12px; } +.chat-md p { margin: 5px 0; } +.chat-md code { + font-family: 'DM Mono', monospace; + background: var(--bg3); + padding: 1px 5px; + border-radius: 3px; + font-size: 11px; + color: var(--accent); +} +.chat-md pre { + background: var(--bg3); + border: 1px solid var(--border); + border-radius: 5px; + padding: 10px 12px; + overflow-x: auto; + margin: 8px 0; +} +.chat-md pre code { + background: none; + padding: 0; + color: var(--text2); +} +.chat-md ul, .chat-md ol { padding-left: 18px; margin: 5px 0; } +.chat-md li { margin: 2px 0; } +.chat-md strong { color: var(--text); font-weight: 600; } +.chat-md em { font-style: italic; color: var(--text2); } +.chat-md a { color: var(--accent); } +.chat-md hr { border: none; border-top: 1px solid var(--border); margin: 10px 0; } +.chat-md blockquote { + border-left: 3px solid var(--accent); + padding-left: 10px; + color: var(--text3); + margin: 6px 0; +} + +/* Chat input row */ +#chat-input-row { + display: flex; + align-items: flex-end; + gap: 8px; + padding: 12px 16px; + border-top: 1px solid var(--border); + background: var(--bg2); + flex-shrink: 0; +} + +#chat-textarea { + flex: 1; + background: var(--bg3); + border: 1px solid var(--border2); + border-radius: 6px; + color: var(--text); + font-family: 'DM Mono', monospace; + font-size: 12px; + padding: 9px 12px; + outline: none; + resize: none; + min-height: 40px; + max-height: 160px; + overflow-y: auto; + transition: border-color 0.15s; + line-height: 1.5; +} + +#chat-textarea:focus { border-color: rgba(56,189,248,0.4); } +#chat-textarea::placeholder { color: var(--text3); } + +#chat-send-btn { + flex-shrink: 0; + width: 38px; + height: 38px; + background: var(--accent-dim); + border: 1px solid rgba(56,189,248,0.3); + border-radius: 6px; + color: var(--accent); + cursor: pointer; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; +} + +#chat-send-btn:hover { background: rgba(56,189,248,0.2); } +#chat-send-btn:disabled { opacity: 0.4; cursor: not-allowed; } + +#chat-tts-toggle { + flex-shrink: 0; + width: 38px; + height: 38px; + background: var(--bg3); + border: 1px solid var(--border2); + border-radius: 6px; + color: var(--text3); + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; +} + +#chat-tts-toggle.active { + background: var(--accent-dim); + border-color: rgba(56,189,248,0.3); + color: var(--accent); +} + +/* System prompt collapsible */ +#chat-system-row { + padding: 8px 16px; + border-bottom: 1px solid var(--border); + background: var(--bg2); + flex-shrink: 0; +} + +#chat-system-toggle { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text3); + cursor: pointer; + display: flex; + align-items: center; + gap: 6px; + user-select: none; +} + +#chat-system-toggle:hover { color: var(--text2); } + +#chat-system-textarea { + display: none; + width: 100%; + margin-top: 8px; + background: var(--bg3); + border: 1px solid var(--border2); + border-radius: 4px; + color: var(--text2); + font-family: 'DM Mono', monospace; + font-size: 10px; + padding: 7px 10px; + outline: none; + resize: vertical; + min-height: 60px; +} + +#chat-system-textarea.open { display: block; } + +.chat-empty-state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + color: var(--text3); + font-size: 12px; +} + +.chat-empty-icon { + font-size: 32px; + opacity: 0.3; +} + +.chat-thinking { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background: var(--bg2); + border: 1px solid var(--border); + border-radius: 8px; + font-size: 11px; + color: var(--text3); + font-family: 'DM Mono', monospace; + align-self: flex-start; +} + +.chat-thinking-dots { + display: flex; + gap: 3px; +} + +.chat-thinking-dots span { + width: 4px; height: 4px; + border-radius: 50%; + background: var(--text3); + animation: thinking-bounce 1.2s ease-in-out infinite; +} + +.chat-thinking-dots span:nth-child(2) { animation-delay: 0.2s; } +.chat-thinking-dots span:nth-child(3) { animation-delay: 0.4s; } + +@keyframes thinking-bounce { + 0%,80%,100% { transform: scale(0.6); opacity: 0.4; } + 40% { transform: scale(1); opacity: 1; } +} + +#chat-model-select { + background: var(--bg3); + border: 1px solid var(--border2); + border-radius: 4px; + color: var(--text2); + font-family: 'DM Mono', monospace; + font-size: 10px; + padding: 3px 6px; + outline: none; + flex-shrink: 0; +} @@ -1214,7 +1632,7 @@ tr { cursor: pointer; transition: background 0.1s; } Engram

Memory Studio
-
DEMO MODE
+
CONNECTING...
@@ -1379,7 +1798,46 @@ tr { cursor: pointer; transition: background 0.1s; }
engram> - + +
+
+ + + @@ -1508,57 +1966,65 @@ tr { cursor: pointer; transition: background 0.1s; }