Skip to content

Integrate Agental Memory and GraphRAG (Local Dojo)#751

Merged
iberi22 merged 3 commits intomainfrom
feat/agental-memory-graphrag-9813784176388851409
Apr 3, 2026
Merged

Integrate Agental Memory and GraphRAG (Local Dojo)#751
iberi22 merged 3 commits intomainfrom
feat/agental-memory-graphrag-9813784176388851409

Conversation

@iberi22
Copy link
Copy Markdown
Owner

@iberi22 iberi22 commented Mar 31, 2026

This PR implements the Agental Memory and GraphRAG integration.

Key changes:

  1. Graph Storage: SledMemoryAdapter now supports directed relationships via a dedicated Sled tree.
  2. Knowledge Graph Construction: The Metabolism process now digests interactions into a structured graph where summaries (Layer 1) are linked to their source interactions (Layer 0 facts) using "summarizes" relationships.
  3. Hierarchical Graph Retrieval: HiRag has been upgraded to perform hierarchical traversal. It identifies relevant high-level summaries and follows graph edges to extract specific evidence from Layer 0, providing richer and more grounded context to the LLM.
  4. Agentic Memory Training (Dojo): The DojoService is now connected to the real memory store. Instead of static mocks, it generates validation tasks from the Knowledge Graph, asking users to verify the accuracy of summaries against their linked facts.
  5. Backend Integration: Wired the new capabilities into the Tauri desktop application, ensuring proper initialization order and exposing async Dojo task generation.

This evolution moves the Synapse Protocol from a simple vector-based RAG to a sophisticated, relationship-aware Agental Memory system.

Fixes #747


PR created automatically by Jules for task 9813784176388851409 started by @iberi22

Summary by CodeRabbit

  • New Features

    • Task generation now queries long-term memory for summaries and evidence, producing more context-aware tasks.
    • Ingestion creates a single summary plus individual fact entries linked as evidence for better traceability.
    • Retrieval now follows summary→evidence links to surface supporting context.
  • Bug Fixes / Improvements

    • Relationship persistence added so evidence links are stored and retrievable.
    • Minor reliability and logging tweaks.

- Implement relationship storage and retrieval in `SledMemoryAdapter`.
- Update `Metabolism` to create a hierarchical Knowledge Graph (Layer 1 summaries linking to Layer 0 facts).
- Enhance `HiRag` hierarchical search with graph traversal to retrieve underlying evidence facts.
- Transform `DojoService` into a real memory training tool using GraphRAG data.
- Integrate updated services into the Tauri backend.
- Maintain `MemoryPort` trait compatibility across SurrealDB and LanceDB adapters.
- Fix compilation issue in `ImmunoAdapter`.
- Update unit tests for `Metabolism` and `HiRag` to reflect new graph-based logic.

Co-authored-by: iberi22 <10615454+iberi22@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5174efa7-347d-4a3a-8774-9309f8eacdf1

📥 Commits

Reviewing files that changed from the base of the PR and between 2c6bd93 and 07b764f.

📒 Files selected for processing (1)
  • scripts/guardian-core.ps1

📝 Walkthrough

Walkthrough

Deferred DojoService initialization to after tokenomics/metabolism setup, added MemoryPort usage across memory/adapters, converted Dojo task generation to async and memory-driven, changed metabolism to emit Summary (layer 1) + Fact (layer 0) nodes linked by "summarizes", and added GraphRAG traversal in HiRAG to fetch evidence via relationships.

Changes

Cohort / File(s) Summary
Cognition init & Dojo
apps/desktop/src-tauri/src/lib.rs, crates/synapse-cognition/src/dojo_service.rs
Deferred DojoService construction to post-tokenomics init when state.tokenomics exists; DojoService now takes a MemoryPort and generate_task is async (.await).
Memory trait & adapters
crates/synapse-core/src/ports/memory_port.rs, crates/synapse-infra/src/adapters/sled_memory_adapter.rs, crates/synapse-infra/src/adapters/lancedb_adapter.rs, crates/synapse-infra/src/adapters/surrealdb_adapter.rs
Added get_related_ids(&self, from_id) to MemoryPort; Sled adapter persists relationships and implements scanning/parse logic; LanceDB and SurrealDB adapters provide stub implementations.
Metabolism (digest) changes
crates/synapse-core/src/logic/metabolism.rs
Digest now creates one Summary node at layer 1 and individual Fact nodes at layer 0 for each interaction, persists embeddings per fact, and links summary→fact via add_relationship("summarizes"); tests updated accordingly.
HiRAG graph traversal
crates/synapse-core/src/logic/hirag.rs
Start search from layer 1, perform GraphRAG traversal using get_related_ids and append "Evidence: ..." from fact nodes; fallback to layer 0 search if no high-layer results; adjusted loop/control flow.
Infrastructure & misc
crates/synapse-infra/src/security/immune_adapter.rs, scripts/guardian-core.ps1
Replaced io::copy with buffered read loop for exe hashing; minor PowerShell interpolation change.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Dojo as DojoService
    participant Memory
    participant HiRAG
    participant Metabolism as Metabolism

    Client->>Dojo: generate_task().await
    Dojo->>Memory: get_by_layer(1).await
    Memory-->>Dojo: [summary_nodes]
    Dojo->>Dojo: select summary
    Dojo->>Memory: get_related_ids(summary_id).await
    Memory-->>Dojo: [(relation, fact_id), ...]
    alt Evidence found
        Dojo->>Memory: get fact content for fact_id(s).await
        Memory-->>Dojo: fact contents
        Dojo-->>Client: DojoTask(with summary + evidence)
    else No evidence
        Dojo-->>Client: DojoTask(from fallback scenarios)
    end
    Note right of Metabolism: Parallel digestion
    Metabolism->>Memory: create Summary (layer 1)
    Metabolism->>Memory: create Fact nodes (layer 0)
    Metabolism->>Memory: add_relationship(summary->"summarizes"->fact)
    Memory-->>Metabolism: persisted
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🧠 Layers hum, summaries rise,
Facts linked under watchful eyes,
GraphRAG threads the evidence seam,
Dojo wakes and crafts a dream. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: integrating Agental Memory and GraphRAG for local Dojo functionality, matching the substantial refactoring across memory, retrieval, and task generation systems.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding objectives from issue #747: graph storage via SledMemoryAdapter relationships, hierarchical retrieval via HiRag GraphRAG integration, and Dojo connection to memory for task generation.
Out of Scope Changes check ✅ Passed All changes align with the stated scope. The immune_adapter hash buffering and guardian-core.ps1 syntax fix are minor supporting changes; the desktop/tauri initialization order adjustments properly integrate the new components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agental-memory-graphrag-9813784176388851409
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/agental-memory-graphrag-9813784176388851409

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a GraphRAG-style memory architecture by introducing hierarchical relationships between Layer 1 summary nodes and Layer 0 facts. Key changes include updating the HiRag retrieval logic to traverse these links, refactoring Metabolism to generate and store these relationships, and enhancing DojoService to utilize real memory data for task generation. Feedback focuses on optimizing performance by parallelizing sequential asynchronous operations in HiRag and Metabolism, improving error handling for embeddings, and ensuring consistent logging by returning correct interaction counts. Additionally, the reviewer suggested hardening the SledMemoryAdapter key format against delimiter collisions and addressing minor idiomatic and cleanup issues in the security adapter.

Comment on lines +124 to +132
for (rel, target_id) in relations {
if rel == "summarizes" {
if let Ok(Some(fact_node)) = self.memory.get_by_id(&target_id).await {
context.push_str(" - Evidence: ");
context.push_str(&fact_node.content);
context.push('\n');
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This loop introduces an N+1 query pattern where self.memory.get_by_id is called sequentially for every relationship found. This can significantly increase latency during retrieval, especially if a summary node has many associated facts. Consider parallelizing these requests using futures::future::join_all to improve performance.

Comment on lines +140 to +165
for interaction in interactions {
let fact_content = format!("User: {}\nAI: {}", interaction.user_input, interaction.ai_response);

// Generate embedding for the fact
let fact_embedding = self.embedder.embed(&fact_content).await.unwrap_or_default();

let fact_node = MemoryNode {
id: Uuid::new_v4().to_string(),
content: fact_content,
layer: 0,
node_type: NodeType::Fact,
created_at: interaction.timestamp,
updated_at: Utc::now().timestamp(),
embedding: fact_embedding,
metadata: std::collections::HashMap::new(),
namespace: "default".to_string(),
source: "user".to_string(),
holographic_data: None,
loop_level: 1,
};

let fact_id = self.memory.store(fact_node).await?;

// Create GraphRAG relationship: Summary --summarizes--> Fact
self.memory.add_relationship(&summary_id, "summarizes", &fact_id).await?;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The processing of interactions is sequential. Since each iteration involves multiple asynchronous calls (embed, store, and add_relationship), this will become a performance bottleneck as the buffer threshold grows. Parallelizing the interaction processing (e.g., using join_all on a collection of tasks) would be more efficient.

let fact_content = format!("User: {}\nAI: {}", interaction.user_input, interaction.ai_response);

// Generate embedding for the fact
let fact_embedding = self.embedder.embed(&fact_content).await.unwrap_or_default();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using unwrap_or_default() here silently ignores embedding failures for individual facts. This results in fact nodes being stored without embeddings, making them invisible to vector-based retrieval. Consider logging an error or propagating the failure, similar to the summary embedding logic at line 102.

self.memory.add_relationship(&summary_id, "summarizes", &fact_id).await?;
}

Ok(1) // Number of summaries created
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function now returns Ok(1) (the number of summaries) instead of the number of interactions digested. This causes the log message in lib.rs (line 337) to report '1 items' regardless of how many interactions were actually processed. Consider returning the count of interactions to maintain accurate logging and consistency with the previous implementation.

// MVP: Relationships not yet implemented in Sled adapter
// We could store them in a separate tree/prefix if needed.
async fn add_relationship(&self, from_id: &str, relation: &str, to_id: &str) -> Result<()> {
let key = format!("{}:{}:{}", from_id, relation, to_id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The relationship key format {from_id}:{relation}:{to_id} is vulnerable to parsing errors if any component contains a colon. While UUIDs are safe, the relation string is not guaranteed to be. Using a null byte (\0) or a length-prefixed encoding would be more robust for a database key.

use synapse_core::error::{Result, Error};
use tracing::info;
use sha2::{Sha256, Digest};
use std::io::Write;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The std::io::Write import appears to be unused in this file and can be removed.

.map_err(|e| Error::System(format!("Failed to read executable file for hashing: {}", e)))?;
let mut buffer = [0u8; 8192];
loop {
let n = io::Read::read(&mut file, &mut buffer)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling io::Read::read explicitly is non-idiomatic in Rust. It is preferred to use the method directly on the object (e.g., file.read(&mut buffer)), provided the Read trait is in scope.

Suggested change
let n = io::Read::read(&mut file, &mut buffer)
let n = file.read(&mut buffer)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/synapse-core/src/logic/metabolism.rs (1)

119-167: ⚠️ Potential issue | 🔴 Critical

This digest write sequence is not atomic after the buffer pop.

The batch has already been removed from the buffer before this block runs. Any failure in store() or add_relationship() after Line 137 leaves partial long-term state behind while the original interactions are gone, which can orphan summaries and permanently drop facts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-core/src/logic/metabolism.rs` around lines 119 - 167, The code
currently pops the buffer before writing long-term memory, so failures in
self.memory.store() or self.memory.add_relationship() can leave partial state;
change to make the digest write atomic by either performing the stores inside a
single transaction (if self.memory exposes a transaction API) or by
deferring/removing the buffer-pop until after all writes succeed; alternatively
implement a rollback that re-inserts the original interactions into the buffer
on any error. Locate the summary creation (summary_node / summary_id), the loop
that calls self.embedder.embed(), self.memory.store(), and
self.memory.add_relationship(), and update the flow so
facts+summary+relationships are committed atomically or the buffer is restored
on failure.
🧹 Nitpick comments (1)
crates/synapse-infra/src/security/immune_adapter.rs (1)

19-25: Buffered read loop implementation is correct and secure.

The manual read loop properly:

  • Uses a reasonable 8KB buffer size
  • Handles EOF correctly (n == 0)
  • Updates the hasher incrementally with only bytes read (&buffer[..n])

Minor style improvement: sha2 = "0.11" resolves to 0.11.0 (the latest stable version, released 2026-03-25), so the version is valid. For idiomatic Rust, consider importing Read directly rather than using the qualified path:

use std::io::Read;
// ...
let n = file.read(&mut buffer)
    .map_err(|e| Error::System(format!("Failed to read executable file for hashing: {}", e)))?;

This improves readability while maintaining the same functionality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-infra/src/security/immune_adapter.rs` around lines 19 - 25,
Import std::io::Read and use the method-call form on the file to improve
readability: replace the qualified call io::Read::read(&mut file, &mut buffer)
with file.read(&mut buffer) while keeping the existing buffer,
hasher.update(&buffer[..n]) and the Error::System(...) mapping intact so
behavior doesn't change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/synapse-cognition/src/dojo_service.rs`:
- Around line 73-105: generate_task currently picks one random summary from
memory.get_by_layer(1) and only uses it if it has evidence, which causes
fallback even when other summaries do have evidence; update generate_task so it
filters the summaries returned by memory.get_by_layer(1) to only those that have
at least one "summarizes" relation (use
memory.get_related_ids(&summary.id).await and confirm any related id resolves
via memory.get_by_id) and then pick a random summary from that filtered list
before building the DojoTask; reference the generate_task function,
memory.get_by_layer(1), memory.get_related_ids, memory.get_by_id, and the
DojoTask construction to find and change the logic.

In `@crates/synapse-core/src/logic/hirag.rs`:
- Around line 101-105: The current logic skips a direct layer-0 search when
relation lookup returns no facts by checking context.is_empty(); instead, change
to an evidence-aware fallback: inspect whether context already contains
grounding evidence (e.g., engram hits, summary text, or non-empty results from
memory.search_layer) rather than using context.is_empty(), and if no actual
grounding evidence is present then perform memory.search_layer(&embedding, 0, N)
(the direct layer-0 search) to retrieve base facts. Update the branch that calls
relation lookup and the subsequent conditional that decides whether to run
search_layer for layer 0 (refer to variables/functions: context,
relation_lookup, summary, memory.search_layer, and the HiRAG search loop) so the
fallback runs only when no engram hits or summary grounding exist.
- Around line 317-319: get_related_ids in struct implementing the trait in
hirag.rs currently calls unimplemented!(), which panics when
hierarchical_search() invokes it during tests; replace the panic by implementing
a minimal working return (e.g., return Ok(Vec::new()) or the appropriate
computed Vec<(String,String)>) in the async fn get_related_ids(&self, _from_id:
&str) so tests no longer panic—locate the get_related_ids function in hirag.rs
and change unimplemented!() to return an Ok value (or the correct related-id
computation) matching the signature.

In `@crates/synapse-core/src/logic/metabolism.rs`:
- Around line 139-158: The loop that builds Layer-0 MemoryNode entries (inside
the for interaction in interactions block) constructs fact_content from raw
interaction.user_input and interaction.ai_response and stores/embeds unsanitized
PII; instead, run both interaction.user_input and interaction.ai_response
through the existing sanitizer (the same sanitization used for combined_text)
before formatting fact_content, then use that sanitized string for
embedder.embed and for MemoryNode.content (the MemoryNode creation and
fact_embedding steps). Ensure you call the sanitizer consistently prior to
embedding and storing so MemoryNode, fact_embedding, and any downstream
holographic_data/reference use the sanitized values.

In `@crates/synapse-core/src/ports/memory_port.rs`:
- Around line 80-81: Add the new async trait method async fn
get_related_ids(&self, from_id: &str) -> Result<Vec<(String, String)>> to every
impl MemoryPort in the codebase—specifically the impl blocks located in the
consolidation, metabolism and recall_service modules—implementing it to read the
same in-memory structures those modules already use for node/edge lookups,
return a Vec of (related_id, relation_type) following existing error handling
and Result conventions, and ensure any necessary imports (Result, String) and
lifetimes match the other MemoryPort methods so the trait implementation
compiles.

In `@crates/synapse-infra/src/adapters/surrealdb_adapter.rs`:
- Around line 622-625: get_related_ids currently returns an empty Vec which
causes the adapter to never read stored graph edges; implement a SurrealDB query
inside get_related_ids to fetch relationships written by add_relationship and
return them as Vec<(String, String)>. Specifically, locate the add_relationship
logic to find the table/record shape (e.g., fields like from_id, to_id,
relation/type) and perform an async SurrealDB query (using the existing
SurrealDB client in this module) filtering by from_id == _from_id, map each
result into (relation, to_id) or the appropriate (String, String) tuple shape,
convert DB errors into the function's Result error type, and return Ok(results).
Ensure the function uses the same client instance and error handling conventions
as other methods in this file (async/await and the module's Result type).

In `@crates/synapse-infra/src/security/immune_adapter.rs`:
- Line 6: Remove the now-unused import std::io::Write from the top of
immune_adapter.rs; the manual read loop that calls hasher.update() replaced the
previous io::copy usage so the Write trait is no longer required—delete the use
std::io::Write; line to eliminate the clippy warning and keep the file imports
minimal.

---

Outside diff comments:
In `@crates/synapse-core/src/logic/metabolism.rs`:
- Around line 119-167: The code currently pops the buffer before writing
long-term memory, so failures in self.memory.store() or
self.memory.add_relationship() can leave partial state; change to make the
digest write atomic by either performing the stores inside a single transaction
(if self.memory exposes a transaction API) or by deferring/removing the
buffer-pop until after all writes succeed; alternatively implement a rollback
that re-inserts the original interactions into the buffer on any error. Locate
the summary creation (summary_node / summary_id), the loop that calls
self.embedder.embed(), self.memory.store(), and self.memory.add_relationship(),
and update the flow so facts+summary+relationships are committed atomically or
the buffer is restored on failure.

---

Nitpick comments:
In `@crates/synapse-infra/src/security/immune_adapter.rs`:
- Around line 19-25: Import std::io::Read and use the method-call form on the
file to improve readability: replace the qualified call io::Read::read(&mut
file, &mut buffer) with file.read(&mut buffer) while keeping the existing
buffer, hasher.update(&buffer[..n]) and the Error::System(...) mapping intact so
behavior doesn't change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 99816757-b521-4631-a479-b96c1842ea27

📥 Commits

Reviewing files that changed from the base of the PR and between 2957012 and 2c6bd93.

📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/lib.rs
  • crates/synapse-cognition/src/dojo_service.rs
  • crates/synapse-core/src/logic/hirag.rs
  • crates/synapse-core/src/logic/metabolism.rs
  • crates/synapse-core/src/ports/memory_port.rs
  • crates/synapse-infra/src/adapters/lancedb_adapter.rs
  • crates/synapse-infra/src/adapters/sled_memory_adapter.rs
  • crates/synapse-infra/src/adapters/surrealdb_adapter.rs
  • crates/synapse-infra/src/security/immune_adapter.rs

Comment on lines +73 to +105
pub async fn generate_task(&self) -> DojoTask {
// Try to generate a task from real summary nodes
if let Ok(summaries) = self.memory.get_by_layer(1).await {
if !summaries.is_empty() {
// Pick a random summary
let idx = (rand::random::<u32>() as usize) % summaries.len();
let summary = &summaries[idx];

// Fetch evidence facts for this summary
let mut evidence = Vec::new();
if let Ok(relations) = self.memory.get_related_ids(&summary.id).await {
for (rel, target_id) in relations {
if rel == "summarizes" {
if let Ok(Some(fact)) = self.memory.get_by_id(&target_id).await {
evidence.push(fact.content);
}
}
}
}

if !evidence.is_empty() {
return DojoTask {
id: summary.id.clone(),
question: format!("Does this summary accurately represent the underlying facts?\n\nSummary: {}\n\nFacts:\n- {}",
summary.content,
evidence.join("\n- ")
),
option_a: "Yes, it is accurate.".to_string(),
option_b: "No, it misses key details or is biased.".to_string(),
reward: 50,
};
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Select a summary only after confirming it has evidence.

generate_task() samples one random layer-1 node first. If that single summary has no summarizes edges, the method falls back to the generic scenarios even when other summaries in the same store do have linked facts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-cognition/src/dojo_service.rs` around lines 73 - 105,
generate_task currently picks one random summary from memory.get_by_layer(1) and
only uses it if it has evidence, which causes fallback even when other summaries
do have evidence; update generate_task so it filters the summaries returned by
memory.get_by_layer(1) to only those that have at least one "summarizes"
relation (use memory.get_related_ids(&summary.id).await and confirm any related
id resolves via memory.get_by_id) and then pick a random summary from that
filtered list before building the DojoTask; reference the generate_task
function, memory.get_by_layer(1), memory.get_related_ids, memory.get_by_id, and
the DojoTask construction to find and change the logic.

Comment on lines +101 to +105
// Start from high layers and go down
for layer in (1..=5).rev() {
let results = self
.memory
.search_layer(&embedding, layer, if layer == 0 { 5 } else { 1 })
.search_layer(&embedding, layer, 1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use an evidence-aware fallback instead of context.is_empty().

By the time Line 141 runs, context may already contain engram hits or the summary text itself. If relation lookup returns no facts, the direct layer-0 search is skipped and HiRAG returns ungrounded context only.

Possible fix
-        let mut context = String::new();
+        let mut context = String::new();
+        let mut added_fact_evidence = false;
@@
                             if rel == "summarizes" {
                                 if let Ok(Some(fact_node)) = self.memory.get_by_id(&target_id).await {
+                                    added_fact_evidence = true;
                                     context.push_str("   - Evidence: ");
                                     context.push_str(&fact_node.content);
                                     context.push('\n');
                                 }
                             }
@@
-        if context.is_empty() {
+        if !added_fact_evidence {
             let results = self.memory.search_layer(&embedding, 0, 5).await?;
             for result in results {
                 context.push_str(&result.node.content);
                 context.push('\n');
             }

Also applies to: 122-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-core/src/logic/hirag.rs` around lines 101 - 105, The current
logic skips a direct layer-0 search when relation lookup returns no facts by
checking context.is_empty(); instead, change to an evidence-aware fallback:
inspect whether context already contains grounding evidence (e.g., engram hits,
summary text, or non-empty results from memory.search_layer) rather than using
context.is_empty(), and if no actual grounding evidence is present then perform
memory.search_layer(&embedding, 0, N) (the direct layer-0 search) to retrieve
base facts. Update the branch that calls relation lookup and the subsequent
conditional that decides whether to run search_layer for layer 0 (refer to
variables/functions: context, relation_lookup, summary, memory.search_layer, and
the HiRAG search loop) so the fallback runs only when no engram hits or summary
grounding exist.

Comment on lines +317 to +319
async fn get_related_ids(&self, _from_id: &str) -> Result<Vec<(String, String)>> {
unimplemented!()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

unimplemented!() here now panics in the happy-path test.

hierarchical_search() now calls get_related_ids() for any matching layer-1 summary, so test_execute_query_end_to_end will panic as soon as it finds summary_node.

Minimal fix
         async fn get_related_ids(&self, _from_id: &str) -> Result<Vec<(String, String)>> {
-            unimplemented!()
+            Ok(vec![])
         }

As per coding guidelines, ensure tests pass before submitting PR.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn get_related_ids(&self, _from_id: &str) -> Result<Vec<(String, String)>> {
unimplemented!()
}
async fn get_related_ids(&self, _from_id: &str) -> Result<Vec<(String, String)>> {
Ok(vec![])
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-core/src/logic/hirag.rs` around lines 317 - 319,
get_related_ids in struct implementing the trait in hirag.rs currently calls
unimplemented!(), which panics when hierarchical_search() invokes it during
tests; replace the panic by implementing a minimal working return (e.g., return
Ok(Vec::new()) or the appropriate computed Vec<(String,String)>) in the async fn
get_related_ids(&self, _from_id: &str) so tests no longer panic—locate the
get_related_ids function in hirag.rs and change unimplemented!() to return an Ok
value (or the correct related-id computation) matching the signature.

Comment on lines +139 to +158
// 10. Store individual interactions as Layer 0 nodes and link to summary
for interaction in interactions {
let fact_content = format!("User: {}\nAI: {}", interaction.user_input, interaction.ai_response);

// Generate embedding for the fact
let fact_embedding = self.embedder.embed(&fact_content).await.unwrap_or_default();

let fact_node = MemoryNode {
id: Uuid::new_v4().to_string(),
content: fact_content,
layer: 0,
node_type: NodeType::Fact,
created_at: interaction.timestamp,
updated_at: Utc::now().timestamp(),
embedding: fact_embedding,
metadata: std::collections::HashMap::new(),
namespace: "default".to_string(),
source: "user".to_string(),
holographic_data: None,
loop_level: 1,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Layer-0 facts bypass the sanitizer.

combined_text is sanitized before summarization, but this block rebuilds each fact from raw interaction.user_input / interaction.ai_response. That writes unsanitized PII into long-term memory and then surfaces it again via HiRAG/Dojo evidence.

Minimal fix
         for interaction in interactions {
-            let fact_content = format!("User: {}\nAI: {}", interaction.user_input, interaction.ai_response);
+            let sanitized_user = self.sanitizer.sanitize(&interaction.user_input)?;
+            let sanitized_ai = self.sanitizer.sanitize(&interaction.ai_response)?;
+            let fact_content = format!("User: {}\nAI: {}", sanitized_user, sanitized_ai);
 
             // Generate embedding for the fact
             let fact_embedding = self.embedder.embed(&fact_content).await.unwrap_or_default();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 10. Store individual interactions as Layer 0 nodes and link to summary
for interaction in interactions {
let fact_content = format!("User: {}\nAI: {}", interaction.user_input, interaction.ai_response);
// Generate embedding for the fact
let fact_embedding = self.embedder.embed(&fact_content).await.unwrap_or_default();
let fact_node = MemoryNode {
id: Uuid::new_v4().to_string(),
content: fact_content,
layer: 0,
node_type: NodeType::Fact,
created_at: interaction.timestamp,
updated_at: Utc::now().timestamp(),
embedding: fact_embedding,
metadata: std::collections::HashMap::new(),
namespace: "default".to_string(),
source: "user".to_string(),
holographic_data: None,
loop_level: 1,
// 10. Store individual interactions as Layer 0 nodes and link to summary
for interaction in interactions {
let sanitized_user = self.sanitizer.sanitize(&interaction.user_input)?;
let sanitized_ai = self.sanitizer.sanitize(&interaction.ai_response)?;
let fact_content = format!("User: {}\nAI: {}", sanitized_user, sanitized_ai);
// Generate embedding for the fact
let fact_embedding = self.embedder.embed(&fact_content).await.unwrap_or_default();
let fact_node = MemoryNode {
id: Uuid::new_v4().to_string(),
content: fact_content,
layer: 0,
node_type: NodeType::Fact,
created_at: interaction.timestamp,
updated_at: Utc::now().timestamp(),
embedding: fact_embedding,
metadata: std::collections::HashMap::new(),
namespace: "default".to_string(),
source: "user".to_string(),
holographic_data: None,
loop_level: 1,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-core/src/logic/metabolism.rs` around lines 139 - 158, The loop
that builds Layer-0 MemoryNode entries (inside the for interaction in
interactions block) constructs fact_content from raw interaction.user_input and
interaction.ai_response and stores/embeds unsanitized PII; instead, run both
interaction.user_input and interaction.ai_response through the existing
sanitizer (the same sanitization used for combined_text) before formatting
fact_content, then use that sanitized string for embedder.embed and for
MemoryNode.content (the MemoryNode creation and fact_embedding steps). Ensure
you call the sanitizer consistently prior to embedding and storing so
MemoryNode, fact_embedding, and any downstream holographic_data/reference use
the sanitized values.

Comment on lines +80 to +81
/// Get related node IDs from a source node ID.
async fn get_related_ids(&self, from_id: &str) -> Result<Vec<(String, String)>>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY

Repository: iberi22/synapse-protocol

Length of output: 292


Update all three MemoryPort implementations before landing this trait change.

Adding the new get_related_ids method to the MemoryPort trait requires updates to every implementation. Currently missing in:

  • crates/synapse-core/src/logic/consolidation.rs:167
  • crates/synapse-core/src/logic/metabolism.rs:280
  • crates/synapse-core/src/logic/recall_service.rs:60

This will cause a compile failure until all implementations are updated. After adding the method to each impl, verify there are no remaining gaps:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

for path in Path('.').rglob('*.rs'):
    text = path.read_text()
    for match in re.finditer(r'impl\s+MemoryPort\s+for\s+[^{]+\{', text):
        start = match.end()
        depth = 1
        i = start
        while i < len(text) and depth:
            if text[i] == '{':
                depth += 1
            elif text[i] == '}':
                depth -= 1
            i += 1
        block = text[start:i-1]
        if 'async fn get_related_ids' not in block:
            line = text.count('\n', 0, match.start()) + 1
            print(f"{path}:{line}: missing get_related_ids")
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-core/src/ports/memory_port.rs` around lines 80 - 81, Add the
new async trait method async fn get_related_ids(&self, from_id: &str) ->
Result<Vec<(String, String)>> to every impl MemoryPort in the
codebase—specifically the impl blocks located in the consolidation, metabolism
and recall_service modules—implementing it to read the same in-memory structures
those modules already use for node/edge lookups, return a Vec of (related_id,
relation_type) following existing error handling and Result conventions, and
ensure any necessary imports (Result, String) and lifetimes match the other
MemoryPort methods so the trait implementation compiles.

Comment on lines +622 to +625
async fn get_related_ids(&self, _from_id: &str) -> Result<Vec<(String, String)>> {
// TODO: Implement SurrealDB Graph traversal
Ok(vec![])
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This adapter can write graph edges but never read them back.

add_relationship() is implemented in this file, but get_related_ids() always returns an empty list. Any HiRAG or Dojo flow running on SurrealDbAdapter will silently lose fact evidence and degrade to summary-only/static behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/synapse-infra/src/adapters/surrealdb_adapter.rs` around lines 622 -
625, get_related_ids currently returns an empty Vec which causes the adapter to
never read stored graph edges; implement a SurrealDB query inside
get_related_ids to fetch relationships written by add_relationship and return
them as Vec<(String, String)>. Specifically, locate the add_relationship logic
to find the table/record shape (e.g., fields like from_id, to_id, relation/type)
and perform an async SurrealDB query (using the existing SurrealDB client in
this module) filtering by from_id == _from_id, map each result into (relation,
to_id) or the appropriate (String, String) tuple shape, convert DB errors into
the function's Result error type, and return Ok(results). Ensure the function
uses the same client instance and error handling conventions as other methods in
this file (async/await and the module's Result type).

Comment thread crates/synapse-infra/src/security/immune_adapter.rs Outdated
@iberi22
Copy link
Copy Markdown
Owner Author

iberi22 commented Apr 2, 2026

⚠️ MERGE BLOCKED: This PR has merge conflicts with current main (commit e51447d after LFS migration). Please rebase onto latest main and resolve conflicts. The guardian CI check is failing. CodeRabbit and Socket security are passing.

@iberi22 iberi22 merged commit 551462d into main Apr 3, 2026
3 of 4 checks passed
@iberi22 iberi22 deleted the feat/agental-memory-graphrag-9813784176388851409 branch April 3, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EPIC] Integrar Memoria Agéntica y GraphRAG (Dojo Local)

1 participant