Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions adapters/remote/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ anyhow = "1"
# cap rather than buffered whole, because the endpoint is operator-supplied
# and a broken or hostile one must not be able to OOM the host.
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "rustls-tls", "stream"] }
# Only the timer, for the read-retry backoff (issue #18 §U5) — reqwest already
# requires a tokio runtime, so this adds no new runtime assumption.
tokio = { version = "1", default-features = false, features = ["time"] }
# Remote records are translated through a private, lossless envelope.
serde = { version = "1", features = ["derive"] }
# Streaming a capped body needs a Stream combinator.
Expand Down
53 changes: 48 additions & 5 deletions adapters/remote/src/cognee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tinymemory_api::recall::RecallOpts;
use tinymemory_api::traits::Memory;
use tinymemory_api::types::MemoryTaint;

use crate::common::{stable_id, Dialect, HttpClient, RemoteMemory, StoredEntry};
use crate::common::{stable_id, Attempts, Dialect, HttpClient, RemoteMemory, StoredEntry};

/// Stable driver id used by configuration and status output.
pub use tinymemory_api::drivers::COGNEE_DRIVER_ID;
Expand All @@ -20,6 +20,26 @@ pub struct CogneeMemory {
}

impl CogneeMemory {
/// Rebuilds the HTTP transport with a different per-request deadline
/// (issue #18 follow-up U5). The default is 60s with a 10s connect
/// deadline — right for interactive calls; a bulk migration or a tight
/// liveness probe may want its own budget.
///
/// # Errors
///
/// Fails only if the underlying HTTP client cannot be rebuilt — a
/// configuration-time failure, before any request is made.
pub fn with_request_timeout(mut self, timeout: std::time::Duration) -> anyhow::Result<Self> {
let client = self
.inner
.dialect_mut()
.client
.clone()
.with_timeout(timeout)?;
self.inner.dialect_mut().client = client;
Ok(self)
}

/// Connect to a self-hosted Cognee server.
///
/// `access_token` is sent as a bearer token. Local deployments with
Expand Down Expand Up @@ -152,6 +172,13 @@ impl Memory for CogneeMemory {
async fn health_check(&self) -> bool {
self.inner.health_check().await
}
/// Forwarded explicitly: this wrapper delegates method-by-method, so the
/// defaulted `None` would otherwise shadow `RemoteMemory`'s typed probe —
/// which is exactly what the first cut shipped, making §U4's deep health
/// unreachable through every public type (the #68 review's Major 1).
async fn health_probe(&self) -> Option<tinymemory_api::health::MemoryHealth> {
self.inner.health_probe().await
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -181,7 +208,12 @@ impl CogneeDialect {
async fn datasets(&self) -> anyhow::Result<Vec<Dataset>> {
let response: Value = self
.client
.json(Method::GET, "api/v1/datasets/", None)
.json(
Method::GET,
"api/v1/datasets/",
None,
Attempts::RetryTransient,
)
.await?;
Ok(response
.as_array()
Expand All @@ -205,6 +237,7 @@ impl CogneeDialect {
Method::GET,
&format!("api/v1/datasets/{}/data", dataset.id),
None,
Attempts::RetryTransient,
)
.await?;
let mut entries = Vec::new();
Expand All @@ -225,6 +258,7 @@ impl CogneeDialect {
.text(
Method::GET,
&format!("api/v1/datasets/{}/data/{id}/raw", dataset.id),
Attempts::RetryTransient,
)
.await?;
let mut entry: StoredEntry =
Expand Down Expand Up @@ -356,6 +390,7 @@ impl Dialect for CogneeDialect {
"only_context": true,
"session_id": opts.session_id
})),
Attempts::RetryTransient,
)
.await?;
let mut entries = Vec::new();
Expand Down Expand Up @@ -402,9 +437,17 @@ impl Dialect for CogneeDialect {
Ok(true)
}

/// Checks Cognee's aggregate health endpoint.
async fn health(&self) -> bool {
self.client.healthy("health").await
/// Probes Cognee's aggregate health endpoint, typed.
async fn health(&self) -> anyhow::Result<()> {
self.client.probe("health").await
}

/// Context-only recall carries no score field — see the trait doc for
/// what this means for `min_score` (documented-inert, not everything-
/// dropping; the first cut's over-fetch pulled 3x the data and discarded
/// all of it).
fn scores_recall(&self) -> bool {
false
}
}

Expand Down
21 changes: 21 additions & 0 deletions adapters/remote/src/cognee_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,27 @@ async fn native_cognee_round_trips_the_tinymemory_contract() {
.len(),
1
);
// #68 review Major 2: Cognee's context-only recall is scoreless, so the
// strict filter would have dropped 100% of every thresholded result.
// The dialect declares scores_recall() = false and min_score is
// documented-inert: the hit survives.
assert_eq!(
driver
.recall(
"graph",
3,
&OwnedRecallOpts {
namespace: Some("project".into()),
min_score: Some(0.5),
..OwnedRecallOpts::default()
},
None
)
.await
.expect("recall with a threshold the backend cannot score")
.len(),
1
);
assert!(driver.forget("project", "key").await.expect("forget"));
assert!(!driver.forget("project", "key").await.expect("forget again"));
assert!(driver.health().await.is_usable());
Expand Down
Loading
Loading