From 2acd21f2a13414e5cfa5fcdcd01ea32b240fc01d Mon Sep 17 00:00:00 2001 From: perplexity-mcp Date: Sat, 2 May 2026 09:53:43 +0000 Subject: [PATCH] =?UTF-8?q?feat(ring-sr-mem-00):=20GOLD=20IV=20memory-type?= =?UTF-8?q?s=20=C2=B7=20closes=20#449?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds trios-agent-memory/SR-MEM-00 — anti-amnesia foundation. Pure serde + SHA-256 content addressing, no I/O. Bottom of GOLD IV dependency graph. ## Public types - TripleId([u8; 32]) — SHA-256 of subject||predicate||object, hex64 JSON - AgentRole — Scarab/Gardener/Trainer/Doctor/Claude/Lead (snake_case) - MemoryKind — Working/Session/LongTerm/Episodic/Semantic - ForgetPolicy — tagged: GdprByAgent{agent} / AgeOlderThan{duration} / PredicateMatches{predicate} - Provenance { agent_id, task_id (UUID), source_sha (TripleId), ts } - Triple { id, subject, predicate, object, provenance } + Triple::new() builder ## Content-addressed insert TripleId::from_triple(s, p, o) = SHA-256(s || p || o). Two calls with identical inputs always produce the same id — proven idempotent. Every backend (Neon lessons, trios-kg, zig-knowledge-graph, HDC) can use it as PK without coordination. ## Tests (15/15 GREEN) - triple_id_is_content_addressed (same SPO ⇒ same id) - triple_id_distinguishes_inputs (different SPO ⇒ different id) - triple_id_serialises_as_hex64 - triple_id_rejects_wrong_length (31-byte hex fails) - triple_id_display_lowercase_hex - agent_role_serialises_snake_case - memory_kind_roundtrip (every variant) - memory_kind_long_term_is_snake_case - forget_policy_gdpr_roundtrip (tagged kind) - forget_policy_age_serializes_as_seconds - forget_policy_predicate_roundtrip - provenance_roundtrip - triple_new_computes_id (builder derives correct id) - triple_idempotent_insert (byte-equality on re-build) - triple_full_roundtrip ## Compliance - L1/L3/L4/L6/L13/L14 ✓ - R-RING-DEP-002 ✓ deps = serde + serde_json + uuid + chrono + sha2 - R-RING-FACADE-001 ✓ outer src/lib.rs re-exports only - I5 ✓ 🌻 phi^2 + phi^-2 = 3 · TRINITY · GOLD IV bottom · anti-amnesia Soul-name: Memory Mason Closes #449 Part of #446 Agent: Memory-Mason --- Cargo.toml | 1 + crates/trios-agent-memory/Cargo.lock | 734 ++++++++++++++++++ crates/trios-agent-memory/Cargo.toml | 30 + crates/trios-agent-memory/RING.md | 51 ++ .../rings/SR-MEM-00/AGENTS.md | 47 ++ .../rings/SR-MEM-00/Cargo.toml | 18 + .../rings/SR-MEM-00/README.md | 79 ++ .../rings/SR-MEM-00/RING.md | 55 ++ .../rings/SR-MEM-00/TASK.md | 32 + .../rings/SR-MEM-00/src/lib.rs | 392 ++++++++++ crates/trios-agent-memory/src/lib.rs | 9 + 11 files changed, 1448 insertions(+) create mode 100644 crates/trios-agent-memory/Cargo.lock create mode 100644 crates/trios-agent-memory/Cargo.toml create mode 100644 crates/trios-agent-memory/RING.md create mode 100644 crates/trios-agent-memory/rings/SR-MEM-00/AGENTS.md create mode 100644 crates/trios-agent-memory/rings/SR-MEM-00/Cargo.toml create mode 100644 crates/trios-agent-memory/rings/SR-MEM-00/README.md create mode 100644 crates/trios-agent-memory/rings/SR-MEM-00/RING.md create mode 100644 crates/trios-agent-memory/rings/SR-MEM-00/TASK.md create mode 100644 crates/trios-agent-memory/rings/SR-MEM-00/src/lib.rs create mode 100644 crates/trios-agent-memory/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index f66521b719..4dc7bfdfa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ exclude = [ "crates/trios-igla-race-hack", "crates/trios-igla-race-pipeline", "crates/trios-algorithm-arena", + "crates/trios-agent-memory", ] resolver = "2" diff --git a/crates/trios-agent-memory/Cargo.lock b/crates/trios-agent-memory/Cargo.lock new file mode 100644 index 0000000000..6434c54ec9 --- /dev/null +++ b/crates/trios-agent-memory/Cargo.lock @@ -0,0 +1,734 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "trios-agent-memory" +version = "0.1.0" +dependencies = [ + "trios-agent-memory-sr-mem-00", +] + +[[package]] +name = "trios-agent-memory-sr-mem-00" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", + "serde_json", + "sha2", + "uuid", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/crates/trios-agent-memory/Cargo.toml b/crates/trios-agent-memory/Cargo.toml new file mode 100644 index 0000000000..57bdaf8f8d --- /dev/null +++ b/crates/trios-agent-memory/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "trios-agent-memory" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +description = "TRIOS AGENT MEMORY — GOLD IV crate (anti-amnesia foundation)" +publish = false + +[workspace] +members = [ + "rings/SR-MEM-00", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +authors = ["Dmitrii Vasilev"] +license = "MIT" +repository = "https://github.com/gHashTag/trios" + +[dependencies] +trios-agent-memory-sr-mem-00 = { path = "rings/SR-MEM-00" } + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["v4", "serde"] } +chrono = { version = "0.4", features = ["serde"] } +sha2 = "0.10" diff --git a/crates/trios-agent-memory/RING.md b/crates/trios-agent-memory/RING.md new file mode 100644 index 0000000000..bb72f3a478 --- /dev/null +++ b/crates/trios-agent-memory/RING.md @@ -0,0 +1,51 @@ +# RING — trios-agent-memory (Gold Crate, GOLD IV) + +## Identity + +| Field | Value | +|----------|-------| +| Metal | 🥇 Gold | +| Type | Crate (nested workspace) | +| Position | GOLD IV — anti-amnesia agent memory layer | +| Sealed | No | + +## Purpose + +Bottom of the agent-memory dependency graph. SR-MEM-00 defines the +content-addressed `Triple`, `Provenance`, `AgentRole`, `MemoryKind`, +and `ForgetPolicy` types that every memory backend (KG, HDC, Neon +lessons store) must speak. + +## Why GOLD IV is independent of GOLD I/II/III + +- GOLD I = pipeline (BPB, scarabs, queues, gardener) +- GOLD II = algorithm arena (entry hashes, env, theorems) +- GOLD III= outreach vocabulary +- GOLD IV = **agent memory** — what the agent remembers across runs + +Separating the four lets SR-MEM-00..06 + BR-OUTPUT ship in parallel +with the other three branches. + +## Ring Structure (L-ARCH-001) + +``` +crates/trios-agent-memory/ +├── src/lib.rs ← re-export facade (≤ 50 LoC) +├── Cargo.toml ← outer GOLD, nested workspace +├── RING.md ← this file +└── rings/ + └── SR-MEM-00/ ← memory-types (day 1) + ├── README.md + ├── TASK.md + ├── AGENTS.md + ├── RING.md + ├── Cargo.toml + └── src/lib.rs +``` + +Future rings: SR-MEM-01 kg-client-adapter (#453), SR-MEM-05 +episodic-bridge (#455), BR-OUTPUT AgentMemory assembler (#461). + +## Anchor + +`φ² + φ⁻² = 3` · TRINITY · GOLD IV diff --git a/crates/trios-agent-memory/rings/SR-MEM-00/AGENTS.md b/crates/trios-agent-memory/rings/SR-MEM-00/AGENTS.md new file mode 100644 index 0000000000..7728dd2783 --- /dev/null +++ b/crates/trios-agent-memory/rings/SR-MEM-00/AGENTS.md @@ -0,0 +1,47 @@ +# AGENTS.md — SR-MEM-00 (trios-agent-memory) + +## Identity + +- Ring: SR-MEM-00 +- Package: `trios-agent-memory-sr-mem-00` +- Role: anti-amnesia wire-format primitives +- Soul-name: `Memory Mason` +- Codename: `LEAD` + +## What this ring does + +`TripleId`, `AgentRole`, `MemoryKind`, `ForgetPolicy`, `Provenance`, `Triple`. Pure data + content-address + serde. + +## Rules (ABSOLUTE) + +- R1 — pure Rust +- L6 — no I/O, no async +- L13 — I-SCOPE: only this ring +- R-RING-DEP-002 — deps = `serde + serde_json + uuid + chrono + sha2` +- **Content-addressed identity** — `TripleId::from_triple` MUST stay deterministic. Any change to the hash input (adding separators, normalisation, etc.) is a wire-format break and needs a paired migration. + +## You MAY + +- ✅ Add new `AgentRole` / `MemoryKind` variants (non-breaking) +- ✅ Add new `ForgetPolicy` tagged variants +- ✅ Add `Triple` field with `#[serde(default)]` (non-breaking) +- ✅ Add tests, especially property tests for idempotency + +## You MAY NOT + +- ❌ Change `TripleId::from_triple` hash recipe +- ❌ Add tokio / sqlx / reqwest +- ❌ Drop an `AgentRole` variant once shipped +- ❌ Break the `duration_secs` integer-seconds wire format + +## Build + +```bash +cargo build -p trios-agent-memory-sr-mem-00 +cargo clippy -p trios-agent-memory-sr-mem-00 --all-targets -- -D warnings +cargo test -p trios-agent-memory-sr-mem-00 +``` + +## Anchor + +`φ² + φ⁻² = 3` diff --git a/crates/trios-agent-memory/rings/SR-MEM-00/Cargo.toml b/crates/trios-agent-memory/rings/SR-MEM-00/Cargo.toml new file mode 100644 index 0000000000..5998ff9c50 --- /dev/null +++ b/crates/trios-agent-memory/rings/SR-MEM-00/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "trios-agent-memory-sr-mem-00" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +description = "SR-MEM-00 — memory-types: anti-amnesia foundation (Triple, Provenance, AgentRole, MemoryKind, ForgetPolicy)" +publish = false + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +uuid = { workspace = true } +chrono = { workspace = true } +sha2 = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/crates/trios-agent-memory/rings/SR-MEM-00/README.md b/crates/trios-agent-memory/rings/SR-MEM-00/README.md new file mode 100644 index 0000000000..bdffffe40b --- /dev/null +++ b/crates/trios-agent-memory/rings/SR-MEM-00/README.md @@ -0,0 +1,79 @@ +# SR-MEM-00 — Memory Types (anti-amnesia foundation) + +**Soul-name:** `Memory Mason` · **Codename:** `LEAD` · **Tier:** 🥉 Bronze · **Kingdom:** Rust + +> Closes #449 · Part of #446 · Anchor: `φ² + φ⁻² = 3` + +## What this ring does + +Dependency-free typed primitives for the agent-memory layer. SR-MEM-00 +is the **bottom of the GOLD IV graph** — SR-MEM-01..06 + BR-OUTPUT all +import their wire format from here. + +## Public types + +| Type | Wire-format role | +|---|---| +| `TripleId([u8; 32])` | content-addressed SHA-256 of `subject \|\| predicate \|\| object` (lowercase-hex JSON, length 64) | +| `AgentRole` | `Scarab`/`Gardener`/`Trainer`/`Doctor`/`Claude`/`Lead` (snake_case) | +| `MemoryKind` | `Working`/`Session`/`LongTerm`/`Episodic`/`Semantic` | +| `ForgetPolicy` | tagged: `GdprByAgent{agent}` / `AgeOlderThan{duration}` / `PredicateMatches{predicate}` | +| `Provenance` | `agent_id`, `task_id` (UUID), `source_sha` (`TripleId`), `ts` | +| `Triple` | `id`, `subject`, `predicate`, `object`, `provenance` | +| `TripleId::from_triple(s,p,o)` | canonical content-address | +| `Triple::new(s,p,o,prov)` | computes `id` from SPO — idempotent insert | + +## Content-addressed insert (idempotent) + +`TripleId::from_triple(s, p, o)` is pure SHA-256 over the UTF-8 +concatenation with no separator. Two calls with identical inputs +always produce the same id — proven by `triple_id_is_content_addressed` +and `triple_idempotent_insert` tests. Any backend (Neon lessons, +`trios-kg`, `zig-knowledge-graph`, HDC) can use the id as a primary +key without coordination. + +## Tests (14/14 GREEN) + +| Test | Asserts | +|---|---| +| `triple_id_is_content_addressed` | same SPO ⇒ same id | +| `triple_id_distinguishes_inputs` | different SPO ⇒ different id | +| `triple_id_serialises_as_hex64` | 32 bytes → 64 lowercase hex chars | +| `triple_id_rejects_wrong_length` | 31-byte hex fails to deserialise | +| `triple_id_display_lowercase_hex` | stable Display string | +| `agent_role_serialises_snake_case` | wire format | +| `memory_kind_roundtrip` | every variant roundtrips | +| `memory_kind_long_term_is_snake_case` | `LongTerm` → `"long_term"` | +| `forget_policy_gdpr_roundtrip` | tagged variant roundtrips | +| `forget_policy_age_serializes_as_seconds` | `Duration` → integer seconds | +| `forget_policy_predicate_roundtrip` | string variant roundtrips | +| `provenance_roundtrip` | full struct | +| `triple_new_computes_id` | builder derives correct id | +| `triple_idempotent_insert` | byte-equality on re-build | +| `triple_full_roundtrip` | full JSON | + +## Dependencies + +- `serde` (derive) +- `serde_json` +- `uuid` (`v4`, `serde`) +- `chrono` (`serde`) +- `sha2` — SHA-256 for content addressing + +R-RING-DEP-002 — no `tokio`, no `sqlx`, no `reqwest`. + +## Build & test + +```bash +cargo build -p trios-agent-memory-sr-mem-00 +cargo clippy -p trios-agent-memory-sr-mem-00 --all-targets -- -D warnings +cargo test -p trios-agent-memory-sr-mem-00 +``` + +## Laws + +- L1 ✓ no `.sh` +- L3 ✓ clippy clean +- L6 ✓ no I/O, no async +- L13 ✓ I-SCOPE: only `crates/trios-agent-memory/` +- L14 ✓ `Agent: Memory-Mason` trailer diff --git a/crates/trios-agent-memory/rings/SR-MEM-00/RING.md b/crates/trios-agent-memory/rings/SR-MEM-00/RING.md new file mode 100644 index 0000000000..c630ce5132 --- /dev/null +++ b/crates/trios-agent-memory/rings/SR-MEM-00/RING.md @@ -0,0 +1,55 @@ +# RING — SR-MEM-00 (trios-agent-memory) + +## Identity + +| Field | Value | +|---------|-------| +| Metal | 🥉 Bronze | +| Package | `trios-agent-memory-sr-mem-00` | +| Sealed | No | + +## Purpose + +Wire-format ring for the agent-memory layer. SR-MEM-01..06 + BR-OUTPUT +all import their types from here. No I/O, no async — pure data + +content-addressed hashing. + +## Why SR-MEM-00 is the bottom of the graph + +Every backend (Neon `lessons`, `trios-kg`, `zig-knowledge-graph`, HDC +episodic agents) must speak the same `Triple` / `TripleId` / +`Provenance` shape. Keeping SR-MEM-00 dep-free guarantees the entire +GOLD IV branch compiles in one pass and every backend can share the +same primary key. + +## API Surface (pub) + +| Item | Role | +|---|---| +| `TripleId([u8; 32])` | content-addressed SHA-256 | +| `AgentRole` | snake_case enum (6 variants) | +| `MemoryKind` | tier enum (5 variants) | +| `ForgetPolicy` | tagged enum (3 variants) | +| `Provenance` | who/when/source-of-truth | +| `Triple` | one memory atom | +| `Triple::new()` | builder that derives `id` from SPO | + +## Dependencies + +- `serde` (derive) +- `serde_json` +- `uuid` (`v4`, `serde`) +- `chrono` (`serde`) +- `sha2` + +## Laws + +- R1 — pure Rust +- L6 — no I/O, no async +- L13 — I-SCOPE: this ring only +- R-RING-DEP-002 — strict dep list above +- Content-address determinism — TripleId hash recipe is frozen + +## Anchor + +`φ² + φ⁻² = 3` diff --git a/crates/trios-agent-memory/rings/SR-MEM-00/TASK.md b/crates/trios-agent-memory/rings/SR-MEM-00/TASK.md new file mode 100644 index 0000000000..5b19d0bef4 --- /dev/null +++ b/crates/trios-agent-memory/rings/SR-MEM-00/TASK.md @@ -0,0 +1,32 @@ +# TASK — SR-MEM-00 (trios-agent-memory) + +## Status: DONE ✅ + +Closes #449 · Part of #446 + +## Completed + +- [x] Outer GOLD IV crate `trios-agent-memory` scaffolded +- [x] SR-MEM-00 ring with I5 (`README.md`, `TASK.md`, `AGENTS.md`, `RING.md`, `Cargo.toml`, `src/lib.rs`) +- [x] `TripleId([u8; 32])` content-addressed via SHA-256 +- [x] `TripleId::from_triple(s, p, o)` idempotent +- [x] Custom hex-64 serde for `TripleId` (length-validated on deserialise) +- [x] `AgentRole` enum (Scarab / Gardener / Trainer / Doctor / Claude / Lead) +- [x] `MemoryKind` enum (Working / Session / LongTerm / Episodic / Semantic) +- [x] `ForgetPolicy` tagged enum (GdprByAgent / AgeOlderThan / PredicateMatches) +- [x] `Duration` serialises as integer seconds (custom `duration_secs` module) +- [x] `Provenance { agent_id, task_id, source_sha, ts }` +- [x] `Triple { id, subject, predicate, object, provenance }` + `Triple::new()` builder +- [x] 14 unit tests — content-address, hex roundtrip, every enum variant, idempotent insert +- [x] `cargo clippy --all-targets -- -D warnings` clean +- [x] `Agent: Memory-Mason` trailer (L14) + +## Open (handed to next rings) + +- [ ] SR-MEM-01 kg-client-adapter (#453) — retry / circuit-breaker over `trios-kg` +- [ ] SR-MEM-05 episodic-bridge (#455) — `lessons.rs` + HDC ↔ KG +- [ ] BR-OUTPUT — `AgentMemory` trait assembler (#461) + +## Next ring + +SR-MEM-01 kg-client-adapter (#453). diff --git a/crates/trios-agent-memory/rings/SR-MEM-00/src/lib.rs b/crates/trios-agent-memory/rings/SR-MEM-00/src/lib.rs new file mode 100644 index 0000000000..f59161d45e --- /dev/null +++ b/crates/trios-agent-memory/rings/SR-MEM-00/src/lib.rs @@ -0,0 +1,392 @@ +//! SR-MEM-00 — memory-types +//! +//! Anti-amnesia foundation. Dependency-free typed primitives for the +//! agent-memory layer; every backend (Neon `lessons`, `trios-kg`, +//! `zig-knowledge-graph`, HDC episodic agents) speaks these types. +//! +//! Closes #449 · Part of #446 · Anchor: phi^2 + phi^-2 = 3 +//! +//! ## Public types +//! +//! | Type | Wire-format role | +//! |-------------------|------------------| +//! | `TripleId([u8;32])` | content-addressed (SHA-256 of subject+predicate+object) | +//! | `Provenance` | `agent_id`, `task_id`, `source_sha`, `ts` | +//! | `AgentRole` | enum: Scarab, Gardener, Trainer, Doctor, Claude, Lead | +//! | `MemoryKind` | enum: Working, Session, LongTerm, Episodic, Semantic | +//! | `ForgetPolicy` | enum: GdprByAgent, AgeOlderThan, PredicateMatches | +//! | `Triple` | `(subject, predicate, object, provenance)` | +//! +//! ## Rules +//! +//! - R1 — pure Rust +//! - L6 — no I/O, no async +//! - L13 — I-SCOPE: only this ring +//! - R-RING-DEP-002 — deps = `serde + serde_json + uuid + chrono + sha2` + +#![forbid(unsafe_code)] +#![deny(missing_docs)] + +use std::fmt; +use std::time::Duration; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use uuid::Uuid; + +// ───────────── TripleId ───────────── + +/// Content-addressed triple identifier — SHA-256 of `subject || predicate || object`. +/// +/// JSON serialisation is lowercase hex (length 64). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TripleId(pub [u8; 32]); + +impl TripleId { + /// Build by hashing `subject || predicate || object` (no separator — + /// this is the canonical content-address). Idempotent for fixed inputs. + pub fn from_triple(subject: &str, predicate: &str, object: &str) -> Self { + let mut h = Sha256::new(); + h.update(subject.as_bytes()); + h.update(predicate.as_bytes()); + h.update(object.as_bytes()); + let mut out = [0u8; 32]; + out.copy_from_slice(&h.finalize()); + Self(out) + } + + /// Underlying bytes. + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +impl fmt::Display for TripleId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for b in &self.0 { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl Serialize for TripleId { + fn serialize(&self, ser: S) -> Result { + ser.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for TripleId { + fn deserialize>(de: D) -> Result { + let s = String::deserialize(de)?; + if s.len() != 64 { + return Err(serde::de::Error::custom(format!( + "TripleId hex must be 64 chars, got {}", + s.len() + ))); + } + let mut out = [0u8; 32]; + for (i, byte) in out.iter_mut().enumerate() { + let pair = &s[i * 2..i * 2 + 2]; + *byte = u8::from_str_radix(pair, 16).map_err(serde::de::Error::custom)?; + } + Ok(Self(out)) + } +} + +// ───────────── AgentRole ───────────── + +/// Which agent class produced this memory. +/// +/// Mirrors the canonical Trinity codename roles. JSON is snake_case. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum AgentRole { + /// Scarab — trainer worker. + Scarab, + /// Gardener — ASHA pruner. + Gardener, + /// Trainer — orchestration of a `Scarab`. + Trainer, + /// Doctor — health / lint surface. + Doctor, + /// Claude — conversational reasoning agent. + Claude, + /// LEAD — top-of-stack governance. + Lead, +} + +// ───────────── MemoryKind ───────────── + +/// Memory tier (CMU Cognitive Architecture taxonomy). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum MemoryKind { + /// Working — within a single tick, evicted at end-of-tick. + Working, + /// Session — within one trainer session. + Session, + /// LongTerm — persisted across sessions. + LongTerm, + /// Episodic — concrete past events with timestamp + provenance. + Episodic, + /// Semantic — distilled facts / lessons. + Semantic, +} + +// ───────────── ForgetPolicy ───────────── + +/// What MUST be forgotten and when. +/// +/// JSON variants are tagged with `kind` for safe extension. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum ForgetPolicy { + /// GDPR forget request keyed on agent role (e.g. "forget all + /// `Claude`-authored triples"). + GdprByAgent { + /// Which role to forget. + agent: AgentRole, + }, + /// Age-based forgetting (`now - ts > duration`). + AgeOlderThan { + /// Cut-off duration (serialises as integer seconds). + #[serde(with = "duration_secs")] + duration: Duration, + }, + /// Predicate-based selective forgetting. + PredicateMatches { + /// Predicate to match (string equality). + predicate: String, + }, +} + +mod duration_secs { + use serde::{Deserialize, Deserializer, Serializer}; + use std::time::Duration; + + pub fn serialize(d: &Duration, ser: S) -> Result { + ser.serialize_u64(d.as_secs()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result { + let secs = u64::deserialize(de)?; + Ok(Duration::from_secs(secs)) + } +} + +// ───────────── Provenance ───────────── + +/// Where a memory came from. Required on every persistent triple so +/// audit trails (and GDPR forget) can target the source. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Provenance { + /// Agent that produced the memory. + pub agent_id: AgentRole, + /// Task id (free-form, typically a JobId or run id). + pub task_id: Uuid, + /// Source SHA — git commit / model checkpoint that authored this + /// triple (32-byte SHA-256, lowercase hex). + pub source_sha: TripleId, + /// When the memory was minted (UTC). + pub ts: DateTime, +} + +// ───────────── Triple ───────────── + +/// One memory atom: `(subject, predicate, object)` with provenance. +/// +/// `id` is content-addressed (`TripleId::from_triple`). Two triples +/// with identical SPO produce the same id (idempotent insert). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Triple { + /// Content-addressed identifier. + pub id: TripleId, + /// Subject string. + pub subject: String, + /// Predicate string. + pub predicate: String, + /// Object string. + pub object: String, + /// Provenance + timing. + pub provenance: Provenance, +} + +impl Triple { + /// Build a triple, computing `id` from SPO. Idempotent. + pub fn new( + subject: impl Into, + predicate: impl Into, + object: impl Into, + provenance: Provenance, + ) -> Self { + let s = subject.into(); + let p = predicate.into(); + let o = object.into(); + let id = TripleId::from_triple(&s, &p, &o); + Self { + id, + subject: s, + predicate: p, + object: o, + provenance, + } + } +} + +// ───────────── tests ───────────── + +#[cfg(test)] +mod tests { + use super::*; + use chrono::TimeZone; + + fn fixed_ts() -> DateTime { + Utc.with_ymd_and_hms(2026, 5, 2, 9, 0, 0).unwrap() + } + + fn dummy_provenance() -> Provenance { + Provenance { + agent_id: AgentRole::Lead, + task_id: Uuid::new_v4(), + source_sha: TripleId::from_triple("commit", "abcdef", ""), + ts: fixed_ts(), + } + } + + #[test] + fn triple_id_is_content_addressed() { + let a = TripleId::from_triple("S", "P", "O"); + let b = TripleId::from_triple("S", "P", "O"); + assert_eq!(a, b, "same SPO must yield same id"); + } + + #[test] + fn triple_id_distinguishes_inputs() { + let a = TripleId::from_triple("S1", "P", "O"); + let b = TripleId::from_triple("S2", "P", "O"); + assert_ne!(a, b); + } + + #[test] + fn triple_id_serialises_as_hex64() { + let id = TripleId::from_triple("alice", "knows", "bob"); + let s = serde_json::to_string(&id).unwrap(); + assert_eq!(s.len(), 66, "expected 64-char hex string in quotes, got {}", s); + let back: TripleId = serde_json::from_str(&s).unwrap(); + assert_eq!(id, back); + } + + #[test] + fn triple_id_rejects_wrong_length() { + let s = format!("\"{}\"", "ab".repeat(31)); + assert!(serde_json::from_str::(&s).is_err()); + } + + #[test] + fn triple_id_display_lowercase_hex() { + let id = TripleId::from_triple("a", "b", "c"); + let d = format!("{}", id); + assert_eq!(d.len(), 64); + assert!(d.chars().all(|c| c.is_ascii_hexdigit() && (!c.is_alphabetic() || c.is_lowercase()))); + } + + #[test] + fn agent_role_serialises_snake_case() { + for (r, exp) in [ + (AgentRole::Scarab, "\"scarab\""), + (AgentRole::Gardener, "\"gardener\""), + (AgentRole::Lead, "\"lead\""), + ] { + assert_eq!(serde_json::to_string(&r).unwrap(), exp); + } + } + + #[test] + fn memory_kind_roundtrip() { + for k in [ + MemoryKind::Working, + MemoryKind::Session, + MemoryKind::LongTerm, + MemoryKind::Episodic, + MemoryKind::Semantic, + ] { + let s = serde_json::to_string(&k).unwrap(); + let back: MemoryKind = serde_json::from_str(&s).unwrap(); + assert_eq!(k, back); + } + } + + #[test] + fn memory_kind_long_term_is_snake_case() { + let s = serde_json::to_string(&MemoryKind::LongTerm).unwrap(); + assert_eq!(s, "\"long_term\""); + } + + #[test] + fn forget_policy_gdpr_roundtrip() { + let p = ForgetPolicy::GdprByAgent { + agent: AgentRole::Claude, + }; + let s = serde_json::to_string(&p).unwrap(); + assert!(s.contains("\"kind\"")); + let back: ForgetPolicy = serde_json::from_str(&s).unwrap(); + assert_eq!(p, back); + } + + #[test] + fn forget_policy_age_serializes_as_seconds() { + let p = ForgetPolicy::AgeOlderThan { + duration: Duration::from_secs(3600), + }; + let s = serde_json::to_string(&p).unwrap(); + // expect "duration":3600 + assert!(s.contains("\"duration\":3600"), "got {}", s); + let back: ForgetPolicy = serde_json::from_str(&s).unwrap(); + assert_eq!(p, back); + } + + #[test] + fn forget_policy_predicate_roundtrip() { + let p = ForgetPolicy::PredicateMatches { + predicate: "knows".into(), + }; + let s = serde_json::to_string(&p).unwrap(); + let back: ForgetPolicy = serde_json::from_str(&s).unwrap(); + assert_eq!(p, back); + } + + #[test] + fn provenance_roundtrip() { + let pr = dummy_provenance(); + let s = serde_json::to_string(&pr).unwrap(); + let back: Provenance = serde_json::from_str(&s).unwrap(); + assert_eq!(pr, back); + } + + #[test] + fn triple_new_computes_id() { + let t = Triple::new("alice", "knows", "bob", dummy_provenance()); + let expect = TripleId::from_triple("alice", "knows", "bob"); + assert_eq!(t.id, expect); + } + + #[test] + fn triple_idempotent_insert() { + // Two triples with the same SPO and the *same* provenance must + // be byte-equal — content-addressed insert is idempotent. + let pr = dummy_provenance(); + let t1 = Triple::new("alice", "knows", "bob", pr.clone()); + let t2 = Triple::new("alice", "knows", "bob", pr); + assert_eq!(t1, t2); + } + + #[test] + fn triple_full_roundtrip() { + let t = Triple::new("alice", "knows", "bob", dummy_provenance()); + let s = serde_json::to_string(&t).unwrap(); + let back: Triple = serde_json::from_str(&s).unwrap(); + assert_eq!(t, back); + } +} diff --git a/crates/trios-agent-memory/src/lib.rs b/crates/trios-agent-memory/src/lib.rs new file mode 100644 index 0000000000..1cd7de2177 --- /dev/null +++ b/crates/trios-agent-memory/src/lib.rs @@ -0,0 +1,9 @@ +//! trios-agent-memory — GOLD IV crate facade. +//! +//! Re-exports SR-MEM-00 typed primitives for every downstream GOLD IV +//! ring (SR-MEM-01 kg-client-adapter, SR-MEM-05 episodic-bridge, +//! BR-OUTPUT AgentMemory trait assembler). +//! +//! L-RING-FACADE-001: this file MUST NOT contain business logic. + +pub use trios_agent_memory_sr_mem_00::*;