From 4845ac139ef643e6e159bbdff6fceb8bff39613f Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 7 Oct 2026 02:43:06 +0530 Subject: [PATCH] Keep the recall future Send: the keep predicate must be Sync #206 (19602185) passes `keep: &dyn Fn(&Hit) -> bool` through recall's gathering and holds it across an .await, so a holistic_recall future is not Send. Hosts run recall on multi-threaded runtimes (a spawned pre-turn, an async_trait method), and openhuman no longer compiles against v1.23.3 (E0277, "cannot be shared between threads safely"). The three private signatures take `&(dyn Fn(&Hit) -> bool + Sync)`, and a test asserts that a holistic_recall future is Send, so this fails to compile in this crate rather than in a host. --- crates/tinymemory-tools/src/recall/gather.rs | 6 +++--- crates/tinymemory-tools/src/recall/mod_tests.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/tinymemory-tools/src/recall/gather.rs b/crates/tinymemory-tools/src/recall/gather.rs index 6883bf6e..c8bc9cd9 100644 --- a/crates/tinymemory-tools/src/recall/gather.rs +++ b/crates/tinymemory-tools/src/recall/gather.rs @@ -236,7 +236,7 @@ async fn with_listed_beliefs( engine: &dyn MemoryEngine, section: &ScopeSection, want: usize, - keep: &dyn Fn(&Hit) -> bool, + keep: &(dyn Fn(&Hit) -> bool + Sync), ) -> tinymemory_api::Result<(Vec, Vec)> { if !reads_learnings(section) { return Ok(( @@ -324,7 +324,7 @@ async fn fetch( query: &str, limit: usize, beliefs: usize, - keep: &dyn Fn(&Hit) -> bool, + keep: &(dyn Fn(&Hit) -> bool + Sync), ) -> tinymemory_api::Result<(Vec, Vec)> { let Some(mode) = preferred_mode(engine) else { return Ok((latest(engine, filter, limit, keep).await?, Vec::new())); @@ -347,7 +347,7 @@ async fn latest( engine: &dyn MemoryEngine, filter: &MetaFilter, limit: usize, - keep: &dyn Fn(&Hit) -> bool, + keep: &(dyn Fn(&Hit) -> bool + Sync), ) -> tinymemory_api::Result> { let mut all: Vec = Vec::new(); let mut cursor: Option = None; diff --git a/crates/tinymemory-tools/src/recall/mod_tests.rs b/crates/tinymemory-tools/src/recall/mod_tests.rs index 4da9295f..2ffbfd02 100644 --- a/crates/tinymemory-tools/src/recall/mod_tests.rs +++ b/crates/tinymemory-tools/src/recall/mod_tests.rs @@ -615,3 +615,19 @@ async fn a_belief_the_filter_rules_out_is_left_out() { .unwrap(); assert!(!pack.markdown.contains("pnpm"), "{}", pack.markdown); } + +/// Hosts run recall on multi-threaded runtimes (a spawned pre-turn, an +/// `async_trait` method), which need its future to be `Send`. Every +/// reference it holds across an `.await` must therefore be `Sync`, +/// including the `keep` predicate the gathering passes down. +#[test] +fn a_recall_future_can_cross_threads() { + fn send(_: &T) {} + let engine = ReferenceEngine::new(); + let request = HolisticRecall::new( + Some("refunds".into()), + vec![ScopeSection::fetch("Docs", docs(), 5)], + ); + let future = holistic_recall(&engine, &request); + send(&future); +}