Skip to content

Commit 97c5f1a

Browse files
senamakelmedullabot
andcommitted
feat: add Supermemory Mem0 and Cognee adapters
Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent 82c2a21 commit 97c5f1a

20 files changed

Lines changed: 2349 additions & 33 deletions

Cargo.lock

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
2-
members = [".", "api", "core", "adapters/tinycortex"]
3-
default-members = [".", "api", "core", "adapters/tinycortex"]
2+
members = [".", "api", "core", "adapters/tinycortex", "adapters/remote"]
3+
default-members = [".", "api", "core", "adapters/tinycortex", "adapters/remote"]
44
# `vendor/` holds engine submodules (tinycortex, tinybus, tinyagents), each of
55
# which is its own workspace with its own lockfile. Same exclusion
66
# `vendor/tinycortex` uses for its own nested vendor directory.

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ src/
2222
└── mandatory/ the three mandatory capability families, composed once
2323
over the `Memory` storage trait
2424
adapters/
25-
└── tinycortex/ the TinyCortex engine seen through the contract
25+
├── tinycortex/ the TinyCortex engine seen through the contract
26+
└── remote/ native HTTP dialects for Supermemory, Mem0, and Cognee
2627
vendor/
2728
├── tinycortex/ the engine, pinned as a submodule
2829
└── tinybus/ pinned TinyBus submodule
@@ -69,6 +70,25 @@ that skips enforcement is the entire reason the policy layer exists.
6970
widen `capabilities()` in lockstep with the accessors.
7071
4. Reserve the driver id: `DriverRegistry::builtin().with_reserved("my-engine", DriverClass::Embedded)`.
7172

73+
## Remote engines
74+
75+
The `tinymemory-remote` crate supports the self-hosted native APIs of
76+
Supermemory, Mem0, and Cognee. Each adapter stores TinyMemory's key, category,
77+
session, and provenance in backend metadata (or a Cognee raw-data envelope), so
78+
exact CRUD and portability survive the seam while recall remains engine-native.
79+
80+
```rust
81+
use tinymemory_remote::{SupermemoryMemory, supermemory_provider};
82+
83+
let memory = SupermemoryMemory::new("http://localhost:6767", Some("sm_..."))?;
84+
let provider = supermemory_provider(memory);
85+
# Ok::<_, anyhow::Error>(provider)
86+
```
87+
88+
All three advertise the mandatory Core, Recall, and Portability families. The
89+
live Docker harness and conformance command are documented in
90+
[`integration/remote-engines/`](integration/remote-engines/README.md).
91+
7292
## Development
7393

7494
```bash

adapters/remote/Cargo.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[package]
2+
name = "tinymemory-remote"
3+
publish = false
4+
version = "0.1.0"
5+
edition = "2021"
6+
rust-version = "1.85"
7+
license = "MIT"
8+
description = "HTTP adapters for self-hosted Supermemory, Mem0, and Cognee"
9+
repository = "https://github.com/tinyhumansai/tinymemory"
10+
11+
[dependencies]
12+
# The engine-neutral contract and mandatory-family composition.
13+
tinymemory = { path = "../.." }
14+
tinymemory-api = { path = "../../api" }
15+
# Memory is an object-safe async trait and each native HTTP dialect is async.
16+
async-trait = "0.1"
17+
# The storage trait deliberately uses opaque backend errors.
18+
anyhow = "1"
19+
# Native self-hosted APIs are HTTP/JSON; multipart is required by Cognee.
20+
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "rustls-tls"] }
21+
# Remote records are translated through a private, lossless envelope.
22+
serde = { version = "1", features = ["derive"] }
23+
serde_json = "1"
24+
# Supermemory custom ids are bounded, so namespace/key identities use SHA-256.
25+
sha2 = "0.10"
26+
27+
[dev-dependencies]
28+
# Adapter tests run lightweight native-API doubles over a real TCP transport.
29+
axum = { version = "0.8", features = ["multipart"] }
30+
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] }
31+
32+
[lints.rust]
33+
unsafe_code = "forbid"
34+
missing_docs = "warn"
35+
unreachable_pub = "warn"
36+
37+
[lints.clippy]
38+
all = { level = "warn", priority = -1 }
39+
unwrap_used = "warn"
40+
expect_used = "warn"
41+
panic = "warn"
42+
missing_errors_doc = "warn"
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! Live mandatory-family smoke test for a self-hosted remote engine.
2+
3+
use std::sync::Arc;
4+
use std::time::{SystemTime, UNIX_EPOCH};
5+
6+
use tinymemory_api::provider::MemoryProvider;
7+
use tinymemory_api::recall::OwnedRecallOpts;
8+
use tinymemory_api::types::{MemoryCategory, MemoryTaint};
9+
use tinymemory_remote::{
10+
cognee_provider, mem0_provider, supermemory_provider, CogneeMemory, Mem0Memory,
11+
SupermemoryMemory,
12+
};
13+
14+
fn usage() -> anyhow::Error {
15+
anyhow::anyhow!("usage: conformance <supermemory|mem0|cognee> <endpoint> [credential]")
16+
}
17+
18+
#[tokio::main]
19+
async fn main() -> anyhow::Result<()> {
20+
let mut args = std::env::args().skip(1);
21+
let engine = args.next().ok_or_else(usage)?;
22+
let endpoint = args.next().ok_or_else(usage)?;
23+
let credential = args.next();
24+
let provider: Arc<dyn MemoryProvider> = match engine.as_str() {
25+
"supermemory" => Arc::new(supermemory_provider(SupermemoryMemory::new(
26+
&endpoint,
27+
credential.as_deref(),
28+
)?)),
29+
"mem0" => Arc::new(mem0_provider(Mem0Memory::new(
30+
&endpoint,
31+
credential.as_deref(),
32+
)?)),
33+
"cognee" => Arc::new(cognee_provider(CogneeMemory::new(
34+
&endpoint,
35+
credential.as_deref(),
36+
)?)),
37+
_ => return Err(usage()),
38+
};
39+
40+
tinymemory_api::provider::audit_provider(provider.as_ref())?;
41+
let health = provider.health().await;
42+
anyhow::ensure!(health.is_usable(), "driver health is {health:?}");
43+
44+
let suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
45+
let namespace = format!("tinymemory-conformance-{suffix}");
46+
let key = "native-round-trip";
47+
let content = format!("TinyMemory native adapter conformance marker {suffix}");
48+
49+
provider
50+
.store(
51+
&namespace,
52+
key,
53+
&content,
54+
MemoryCategory::Core,
55+
Some("live-conformance"),
56+
MemoryTaint::ExternalSync,
57+
)
58+
.await?;
59+
let stored = provider
60+
.get(&namespace, key)
61+
.await?
62+
.ok_or_else(|| anyhow::anyhow!("stored record was not readable"))?;
63+
anyhow::ensure!(stored.content == content, "stored content changed");
64+
anyhow::ensure!(
65+
stored.taint == MemoryTaint::ExternalSync,
66+
"stored taint changed"
67+
);
68+
69+
let hits = provider
70+
.recall(
71+
"conformance marker",
72+
10,
73+
&OwnedRecallOpts {
74+
namespace: Some(namespace.clone()),
75+
..OwnedRecallOpts::default()
76+
},
77+
None,
78+
)
79+
.await?;
80+
anyhow::ensure!(!hits.is_empty(), "native recall returned no record");
81+
82+
let mut cursor = None;
83+
let mut exported_keys = Vec::new();
84+
loop {
85+
let page = provider.export_page(cursor.as_deref(), 100).await?;
86+
exported_keys.extend(page.records.iter().filter_map(|record| {
87+
record
88+
.payload
89+
.get("key")
90+
.and_then(serde_json::Value::as_str)
91+
.map(str::to_owned)
92+
}));
93+
let Some(next) = page.next_cursor else {
94+
break;
95+
};
96+
cursor = Some(next);
97+
}
98+
anyhow::ensure!(
99+
exported_keys.iter().any(|exported| exported == key),
100+
"portability export omitted the record; exported keys: {exported_keys:?}"
101+
);
102+
anyhow::ensure!(
103+
provider.forget(&namespace, key).await?,
104+
"forget missed record"
105+
);
106+
107+
println!(
108+
"{}: Core, Recall, and Portability passed",
109+
provider.driver_id()
110+
);
111+
Ok(())
112+
}

0 commit comments

Comments
 (0)