@@ -207,10 +207,10 @@ async def _search_stepped(
207207 if not seed_facts :
208208 logger .debug ("search: no facts for user=%s, falling back to sentence search" , user_id )
209209 if depth == "l0" :
210- return {"facts" : [], "episodes" : [], "memory_found" : False }
210+ return {"facts" : [], "episodes" : [], "syntheses" : [], " memory_found" : False }
211211 return {
212212 "facts" : [],
213- "episodes" : [],
213+ "episodes" : [], "syntheses" : [],
214214 "sentences" : direct_sentences ,
215215 "memory_found" : False ,
216216 }
@@ -239,16 +239,19 @@ async def _search_stepped(
239239 if depth == "l0" :
240240 # L0: graph traversal only — keep it light
241241 episodes = await self .db .get_episodes_for_facts (seed_fact_ids )
242+ syntheses = await self .db .get_syntheses_for_facts (seed_fact_ids )
242243 top = _clean (top_k_facts )
243- return {"facts" : top , "episodes" : episodes , "memory_found" : len (top ) > 0 }
244+ return {"facts" : top , "episodes" : episodes , "syntheses" : syntheses , " memory_found" : len (top ) > 0 }
244245
245246 # ── Step 2: Episodes (L1/L2) — graph traversal + vector search ────────
246247 # Run both concurrently: graph edges from matched facts, and direct
247248 # cosine search over episode embeddings. Merge by ID so the same
248249 # episode doesn't appear twice.
249- graph_episodes , vec_episodes = await asyncio .gather (
250+ graph_episodes , vec_episodes , graph_syntheses , vec_syntheses = await asyncio .gather (
250251 self .db .get_episodes_for_facts (seed_fact_ids ),
251252 self .db .search_episodes (query_embedding , user_id , agent_id ),
253+ self .db .get_syntheses_for_facts (seed_fact_ids ),
254+ self .db .search_syntheses (query_embedding , user_id , agent_id ),
252255 )
253256 seen_episode_ids : set [str ] = set ()
254257 episodes : list [dict [str , Any ]] = []
@@ -258,6 +261,14 @@ async def _search_stepped(
258261 seen_episode_ids .add (iid )
259262 episodes .append (ins )
260263
264+ seen_synthesis_ids : set [str ] = set ()
265+ syntheses : list [dict [str , Any ]] = []
266+ for ins in (* graph_syntheses , * vec_syntheses ):
267+ iid = ins .get ("id" )
268+ if iid and iid not in seen_synthesis_ids :
269+ seen_synthesis_ids .add (iid )
270+ syntheses .append (ins )
271+
261272 # ── Step 3: Trace facts → source sentences ────────────────────────────
262273 source_sentence_ids = await self .db .get_source_sentences (seed_fact_ids )
263274
@@ -289,7 +300,7 @@ async def _search_stepped(
289300 if not all_candidate_ids :
290301 return {
291302 "facts" : top_facts ,
292- "episodes" : episodes ,
303+ "episodes" : episodes , "syntheses" : syntheses ,
293304 "sentences" : [],
294305 "memory_found" : memory_found ,
295306 }
@@ -342,7 +353,7 @@ async def _search_stepped(
342353
343354 return {
344355 "facts" : top_facts ,
345- "episodes" : episodes ,
356+ "episodes" : episodes , "syntheses" : syntheses ,
346357 "sentences" : _dedup (result_sentences ),
347358 "memory_found" : memory_found ,
348359 }
@@ -394,9 +405,11 @@ async def _search_l2_fast(
394405 top_facts = _clean (_diverse_top_k (scored_facts , top_k ))
395406 top_fact_ids = [f ["id" ] for f in top_facts ]
396407
397- graph_episodes , vec_episodes = await asyncio .gather (
408+ graph_episodes , vec_episodes , graph_syntheses , vec_syntheses = await asyncio .gather (
398409 self .db .get_episodes_for_facts (top_fact_ids ),
399410 self .db .search_episodes (query_embedding , user_id , agent_id ),
411+ self .db .get_syntheses_for_facts (top_fact_ids ),
412+ self .db .search_syntheses (query_embedding , user_id , agent_id ),
400413 )
401414 seen_episode_ids : set [str ] = set ()
402415 episodes : list [dict [str , Any ]] = []
@@ -406,9 +419,17 @@ async def _search_l2_fast(
406419 seen_episode_ids .add (iid )
407420 episodes .append (ins )
408421
422+ seen_synthesis_ids : set [str ] = set ()
423+ syntheses : list [dict [str , Any ]] = []
424+ for ins in (* graph_syntheses , * vec_syntheses ):
425+ iid = ins .get ("id" )
426+ if iid and iid not in seen_synthesis_ids :
427+ seen_synthesis_ids .add (iid )
428+ syntheses .append (ins )
429+
409430 return {
410431 "facts" : top_facts ,
411- "episodes" : episodes ,
432+ "episodes" : episodes , "syntheses" : syntheses ,
412433 "sentences" : _dedup (raw .get ("sentences" , [])),
413434 "memory_found" : len (top_facts ) > 0 ,
414435 }
@@ -482,7 +503,7 @@ async def _search_one(embedding: list[float]) -> list[dict[str, Any]]:
482503 logger .debug ("search_expanded: no facts found, falling back to sentence search" )
483504 return {
484505 "facts" : [],
485- "episodes" : [],
506+ "episodes" : [], "syntheses" : [],
486507 "sentences" : direct_sentences ,
487508 "memory_found" : False ,
488509 }
@@ -508,9 +529,11 @@ async def _search_one(embedding: list[float]) -> list[dict[str, Any]]:
508529 top_facts = _diverse_top_k (scored_facts , top_k )
509530 fact_ids = [f ["id" ] for f in top_facts ]
510531
511- graph_episodes , vec_episodes , source_sentence_ids = await asyncio .gather (
532+ graph_episodes , vec_episodes , graph_syntheses , vec_syntheses , source_sentence_ids = await asyncio .gather (
512533 self .db .get_episodes_for_facts (fact_ids ),
513534 self .db .search_episodes (embeddings [0 ], user_id , agent_id ),
535+ self .db .get_syntheses_for_facts (fact_ids ),
536+ self .db .search_syntheses (embeddings [0 ], user_id , agent_id ),
514537 self .db .get_source_sentences (fact_ids ),
515538 )
516539 seen_episode_ids : set [str ] = set ()
@@ -521,6 +544,14 @@ async def _search_one(embedding: list[float]) -> list[dict[str, Any]]:
521544 seen_episode_ids .add (iid )
522545 episodes .append (ins )
523546
547+ seen_synthesis_ids : set [str ] = set ()
548+ syntheses : list [dict [str , Any ]] = []
549+ for ins in (* graph_syntheses , * vec_syntheses ):
550+ iid = ins .get ("id" )
551+ if iid and iid not in seen_synthesis_ids :
552+ seen_synthesis_ids .add (iid )
553+ syntheses .append (ins )
554+
524555 # Session scoring: merge direct search + fact-linked sentences
525556 direct_sent_by_id : dict [str , dict [str , Any ]] = {s ["id" ]: s for s in direct_sentences }
526557 missing_ids = [sid for sid in source_sentence_ids if sid not in direct_sent_by_id ]
@@ -574,7 +605,7 @@ async def _search_one(embedding: list[float]) -> list[dict[str, Any]]:
574605
575606 return {
576607 "facts" : _clean (top_facts ),
577- "episodes" : episodes ,
608+ "episodes" : episodes , "syntheses" : syntheses ,
578609 "sentences" : _dedup (result_sentences ),
579610 "memory_found" : len (top_facts ) > 0 ,
580611 }
@@ -596,23 +627,23 @@ async def _sentence_fallback(
596627 since they're stored synchronously in add().
597628 """
598629 if depth == "l0" :
599- return {"facts" : [], "episodes" : [], "memory_found" : False }
630+ return {"facts" : [], "episodes" : [], "syntheses" : [], " memory_found" : False }
600631
601632 sentences = await self .db .search_sentences (
602633 embedding = query_embedding ,
603634 user_id = user_id ,
604635 agent_id = agent_id ,
605636 limit = top_k ,
606637 )
607- return {"facts" : [], "episodes" : [], "sentences" : sentences , "memory_found" : False }
638+ return {"facts" : [], "episodes" : [], "syntheses" : [], " sentences" : sentences , "memory_found" : False }
608639
609640
610641# ── Helpers ───────────────────────────────────────────────────────────────────
611642
612643
613644def _empty (depth : str ) -> dict [str , Any ]:
614645 """Return the correct empty structure for a given depth."""
615- result : dict [str , Any ] = {"facts" : [], "episodes" : []}
646+ result : dict [str , Any ] = {"facts" : [], "episodes" : [], "syntheses" : [] }
616647 if depth in ("l1" , "l2" ):
617648 result ["sentences" ] = []
618649 return result
0 commit comments