OpenPay (1 crate, 3751 LOC, 83 tests)
op-crypto-extra Extended crypto rails. Stablecoin catalog (USDC, USDT,
PYUSD, DAI, RLUSD, EURC, FDUSD — 26 deployments). L2
catalog (12 chains incl. Optimism, Arbitrum, Base,
zkSync Era, Polygon zkEVM, Linea, Scroll, Starknet).
CCTP v2 BurnMessage encode/decode + domain ids +
attestation. ERC-4337 UserOperation v0.7 +
PackedUserOperation. EIP-7702 set-EOA-code with RLP
signing preimage. Uniswap Permit2 single + batch
transfer EIP-712 hashes. EIP-2612 ERC-20 permit.
BOLT-11 bech32 invoices + LNURL decode. Bitcoin
PSBT v2 (BIP-370). Atomic-swap HTLC. Solana SPL /
Aleo / Aztec confidential-transfer sketches.
Smart Byte (1 crate, 2304 LOC, 30 tests)
smart-byte-acdc Authentic Chained Data Containers (KERI ecosystem).
Acdc + AcdcBuilder (targeted + untargeted) with SAID
derivation. JSON Schema 2020-12 validator with
oneOf partial-disclosure. TEL (RIP/VRT/ISS/REV)
with hash-chained verification. IPEX (apply/offer/
agree/grant/admit/spurn) chained by SAID. Edge
records (I2I/NI2I/DI2I) + EdgeGraph BFS traversal
with cycle detection. Selective disclosure with
full/none/subset plans + compact packing.
CredentialRegistry trait + InMemoryRegistry over
TEL. Composes with smart-byte-core + smart-byte-keri-witness.
EOC (1 crate, 1764 LOC, 16 tests)
eoc-memory Long-term memory primitives. Memory trait + MemoryKind +
EpisodeId. Episodic event log with TimeIndex range
queries. RDF-style Triple store with S/P/O indices.
Bounded attention-budget Scratchpad with
salience-weighted eviction. Rolling SummaryBuffer +
FIFO SlidingWindow. Dream-cycle consolidate()
(episodic → semantic). Ebbinghaus forgetting curve
with reinforcement. Retriever blending cosine
similarity + recency + saturating frequency.
MemGpt three-tier shape (Main/Recall/Archival).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
183 lines
5.4 KiB
Rust
183 lines
5.4 KiB
Rust
//! Semantic memory: an entity-relation triple store.
|
|
//!
|
|
//! Triples follow the classical RDF shape `(subject, predicate,
|
|
//! object)` (Berners-Lee 2001) and are stored in three flat indices
|
|
//! so lookups by S, P, or O are linear in the number of matches.
|
|
//! No external graph dependency is pulled in — the goal is
|
|
//! deterministic, embeddable, WASM-clean.
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::error::MemoryResult;
|
|
use crate::memory::{Memory, MemoryItem, MemoryKind, MemoryRef};
|
|
|
|
/// An entity-relation-entity assertion.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct Triple {
|
|
/// Subject identifier.
|
|
pub subject: String,
|
|
/// Predicate / relation name.
|
|
pub predicate: String,
|
|
/// Object identifier or literal.
|
|
pub object: String,
|
|
/// Source-episode id, optional, for provenance back to the
|
|
/// episodic log.
|
|
pub source_episode: Option<String>,
|
|
/// Insertion timestamp in ms (for `Memory::recent`).
|
|
pub timestamp_ms: u64,
|
|
}
|
|
|
|
impl Triple {
|
|
/// Construct a triple at a given timestamp.
|
|
pub fn new(
|
|
subject: impl Into<String>,
|
|
predicate: impl Into<String>,
|
|
object: impl Into<String>,
|
|
timestamp_ms: u64,
|
|
) -> Self {
|
|
Self {
|
|
subject: subject.into(),
|
|
predicate: predicate.into(),
|
|
object: object.into(),
|
|
source_episode: None,
|
|
timestamp_ms,
|
|
}
|
|
}
|
|
|
|
/// Attach a source-episode hex id (builder).
|
|
#[must_use]
|
|
pub fn with_source(mut self, source_episode: impl Into<String>) -> Self {
|
|
self.source_episode = Some(source_episode.into());
|
|
self
|
|
}
|
|
|
|
/// Stable BLAKE3-16 hex id of this triple (deterministic).
|
|
#[must_use]
|
|
pub fn id(&self) -> String {
|
|
let mut hasher = blake3::Hasher::new();
|
|
hasher.update(self.subject.as_bytes());
|
|
hasher.update(b"\x1f");
|
|
hasher.update(self.predicate.as_bytes());
|
|
hasher.update(b"\x1f");
|
|
hasher.update(self.object.as_bytes());
|
|
let h = hasher.finalize();
|
|
let mut s = String::with_capacity(32);
|
|
for b in &h.as_bytes()[..16] {
|
|
s.push_str(&format!("{b:02x}"));
|
|
}
|
|
s
|
|
}
|
|
|
|
/// Render as `MemoryItem` for injection.
|
|
#[must_use]
|
|
pub fn as_item(&self) -> MemoryItem {
|
|
MemoryItem::new(
|
|
MemoryRef {
|
|
kind: MemoryKind::Semantic,
|
|
id: self.id(),
|
|
},
|
|
format!("({} {} {})", self.subject, self.predicate, self.object),
|
|
self.timestamp_ms,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// In-memory knowledge graph: a set of [`Triple`] plus S/P/O indices.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct SemanticGraph {
|
|
triples: Vec<Triple>,
|
|
by_subject: HashMap<String, Vec<usize>>,
|
|
by_predicate: HashMap<String, Vec<usize>>,
|
|
by_object: HashMap<String, Vec<usize>>,
|
|
}
|
|
|
|
impl SemanticGraph {
|
|
/// Fresh empty graph.
|
|
#[must_use]
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Assert a triple into the graph. Duplicates (same S/P/O) are
|
|
/// idempotent — the new timestamp wins, but no new row is added.
|
|
pub fn assert(&mut self, triple: Triple) -> String {
|
|
let id = triple.id();
|
|
if let Some(existing) = self.triples.iter_mut().find(|t| t.id() == id) {
|
|
existing.timestamp_ms = triple.timestamp_ms;
|
|
return id;
|
|
}
|
|
let idx = self.triples.len();
|
|
self.by_subject
|
|
.entry(triple.subject.clone())
|
|
.or_default()
|
|
.push(idx);
|
|
self.by_predicate
|
|
.entry(triple.predicate.clone())
|
|
.or_default()
|
|
.push(idx);
|
|
self.by_object
|
|
.entry(triple.object.clone())
|
|
.or_default()
|
|
.push(idx);
|
|
self.triples.push(triple);
|
|
id
|
|
}
|
|
|
|
/// All triples with subject `s`.
|
|
#[must_use]
|
|
pub fn subject(&self, s: &str) -> Vec<&Triple> {
|
|
self.by_subject
|
|
.get(s)
|
|
.map(|v| v.iter().filter_map(|&i| self.triples.get(i)).collect())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// All triples with predicate `p`.
|
|
#[must_use]
|
|
pub fn predicate(&self, p: &str) -> Vec<&Triple> {
|
|
self.by_predicate
|
|
.get(p)
|
|
.map(|v| v.iter().filter_map(|&i| self.triples.get(i)).collect())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// All triples with object `o`.
|
|
#[must_use]
|
|
pub fn object(&self, o: &str) -> Vec<&Triple> {
|
|
self.by_object
|
|
.get(o)
|
|
.map(|v| v.iter().filter_map(|&i| self.triples.get(i)).collect())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// All triples.
|
|
#[must_use]
|
|
pub fn all(&self) -> &[Triple] {
|
|
&self.triples
|
|
}
|
|
|
|
/// True iff a triple with the same S/P/O already exists.
|
|
#[must_use]
|
|
pub fn contains(&self, triple: &Triple) -> bool {
|
|
let id = triple.id();
|
|
self.triples.iter().any(|t| t.id() == id)
|
|
}
|
|
}
|
|
|
|
impl Memory for SemanticGraph {
|
|
fn kind(&self) -> MemoryKind {
|
|
MemoryKind::Semantic
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.triples.len()
|
|
}
|
|
|
|
fn recent(&self, n: usize) -> MemoryResult<Vec<MemoryItem>> {
|
|
let mut sorted: Vec<&Triple> = self.triples.iter().collect();
|
|
sorted.sort_by(|a, b| b.timestamp_ms.cmp(&a.timestamp_ms));
|
|
Ok(sorted.into_iter().take(n).map(Triple::as_item).collect())
|
|
}
|
|
}
|