# How Does Cascade Retrieval Work in jcode's Memory Graph: BFS Traversal and Score Decay Explained

> Uncover jcode's cascade retrieval: learn how BFS traversal and score decay in its memory graph find related items with this expert explanation.

- Repository: [Jeremy Huang/jcode](https://github.com/1jehuang/jcode)
- Tags: deep-dive
- Published: 2026-04-30

---

**jcode's cascade retrieval uses a Breadth-First Search (BFS) algorithm that expands seed memories across the MemoryGraph, applying depth-based score decay to rank related items.**

jcode is an open-source memory management system that stores information in a graph structure. Understanding how cascade retrieval works in jcode's memory graph is essential for developers building applications that need to surface semantically related content beyond simple vector similarity.

## The MemoryGraph Architecture

jcode organizes data in a **MemoryGraph** where each node represents a memory entry. These entries connect through typed edges, creating a traversable network of relationships.

### Nodes, Tags, and Edges

Each memory in the graph can link to:

- **Tags** (prefixed with `tag:`)
- **Other memories** via edges like `RelatesTo` and `Contradicts`

This structure enables multi-hop reasoning. A query about one memory can surface indirectly connected memories through shared tags or explicit relationships.

## The cascade_retrieve Algorithm

The core implementation resides in `MemoryGraph::cascade_retrieve` within [`src/memory_graph.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory_graph.rs). The method signature accepts seed IDs, seed scores, maximum depth, and result limits, then executes a five-phase BFS expansion.

### Step 1: Initialization with Seed Memories

The algorithm begins by enqueueing seed memories obtained from an initial embedding search. These seeds represent the highest vector similarity matches to the original query.

```rust
for (id, score) in seed_ids.iter().zip(seed_scores.iter()) {
    if self.memories.contains_key(id) {
        queue.push_back((id.clone(), *score, 0));
        results.insert(id.clone(), *score);
    }
}

```

This initialization populates both the BFS queue and a results HashMap, establishing depth-zero scores for direct matches (lines 560‑565).

### Step 2: Breadth-First Search Loop

The main processing loop continues while the queue contains entries. Each iteration dequeues a node, marks it as visited, and checks whether the current depth exceeds the user-specified `max_depth` parameter. If the depth limit is reached, expansion stops for that branch, ensuring computational bounds (lines 568‑576).

### Step 3: Edge Traversal and Score Decay

For each outgoing edge from the current node, the algorithm calculates a propagated score using two factors:

1. **Edge weight**: Retrieved via `edge.kind.traversal_weight()`
2. **Depth decay**: Applied as `0.7_f32.powi(depth + 1)`

The decay factor ensures that indirectly connected memories receive exponentially lower scores than direct matches.

Edge handling differs by target type:

- **Tag targets** (`target.starts_with("tag:")`): Every memory containing that tag becomes a neighbor
- **Memory targets**: The specific memory node becomes the neighbor

This distinction allows cascade retrieval to leverage both explicit links and categorical relationships (lines 579‑608).

### Step 4: Score Updates and Queue Management

When a neighbor receives a computed `new_score`, the algorithm compares it against any existing score for that node. If the new score is higher, the neighbor is pushed onto the queue with an incremented depth value for further expansion. This ensures that only optimal paths propagate through the graph and prevents infinite loops from cycles (lines 618‑626).

### Step 5: Top-K Result Truncation

After BFS completion, the algorithm filters the results map using `top_k_scored` to return only the highest-scoring memories up to the `max_results` limit. This truncation occurs at lines 616‑618 in the source.

## High-Level APIs for Cascade Retrieval

The `Memory` struct in [`src/memory.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory.rs) provides ergonomic wrappers around the raw graph traversal.

### Retrieving Related Memories with get_related

The `Memory::get_related` method (lines 1775‑1889) wraps `cascade_retrieve` to return fully instantiated `MemoryEntry` objects rather than raw IDs. This handles deserialization and context loading automatically.

### Combining Vector Search with find_similar_with_cascade

For end-to-end semantic search, `Memory::find_similar_with_cascade` (lines 1809‑1847) first executes a standard embedding similarity search, then feeds those results into the cascade algorithm across both project-specific and global memory graphs.

This hybrid approach captures both vector similarity and structural relationships in the final results.

## Practical Implementation Examples

### Basic Related Memory Retrieval

```rust
use jcode::memory::Memory;

let mem = Memory::new()?;
let related = mem.get_related("abcd1234", 2)?; // depth = 2
println!("Related memories: {:?}", related);

```

*`get_related` internally invokes `cascade_retrieve` according to the source implementation in [`src/memory.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory.rs).*

### Direct Cascade Retrieval on MemoryGraph

```rust
let mut graph = MemoryGraph::new();
let seed_id = graph.add_memory(make_test_memory("Seed"));
let results = graph.cascade_retrieve(
    &[seed_id.clone()],            // seed ids
    &[1.0],                        // seed scores
    2,                             // max_depth
    10,                            // max_results
);
println!("Cascade results: {:?}", results);

```

*This demonstrates the raw BFS API available in [`src/memory_graph.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory_graph.rs).*

### End-to-End Semantic Search with Cascade

```rust
let mem = Memory::new()?;
let similar = mem.find_similar_with_cascade(
    "rust async",      // query text
    0.5,               // similarity threshold
    5,                 // limit
)?;
println!("Similar with cascade: {:?}", similar);

```

*The method first runs a vector search, then expands via cascade according to the implementation in [`src/memory.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory.rs).*

## Summary

- **jcode's cascade retrieval** expands seed memories using BFS traversal through the MemoryGraph, starting from `MemoryGraph::cascade_retrieve` in [`src/memory_graph.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory_graph.rs).
- **Score decay** applies a factor of `0.7_f32.powi(depth+1)` to prioritize direct matches over distant connections.
- **Dual path handling** treats tag nodes and memory nodes differently, expanding tags to all associated memories while following memory edges to specific targets.
- **High-level APIs** in [`src/memory.rs`](https://github.com/1jehuang/jcode/blob/main/src/memory.rs) including `get_related` and `find_similar_with_cascade` provide convenient access with automatic result hydration.
- **Configurable limits** via `max_depth` and `max_results` parameters control computational cost and result set size.

## Frequently Asked Questions

### What is the default decay factor in jcode's cascade retrieval?

jcode uses a fixed decay factor of **0.7** applied as `0.7_f32.powi(depth + 1)`. This means each hop away from the original seed reduces the contribution score by 30%, ensuring that directly connected memories maintain higher relevance than those reached through multiple relationship hops.

### How does cascade retrieval handle tag nodes differently from memory nodes?

When the BFS encounters a tag node (identified by the `tag:` prefix), it expands to **all memories** containing that tag rather than treating the tag as a single endpoint. In contrast, memory nodes expand only to their specific target memory. This design leverages categorical associations to surface broader context while maintaining precision for explicit links.

### What is the maximum depth parameter used for in cascade_retrieve?

The `max_depth` parameter in `MemoryGraph::cascade_retrieve` defines how many relationship hops the algorithm will traverse from the initial seeds. A depth of 1 includes only direct neighbors, while higher values enable multi-hop discovery. The default implementations typically use depths of 2 or 3 to balance recall with computational overhead.

### How does cascade retrieval differ from pure embedding search?

Pure embedding search relies solely on vector similarity between query and content embeddings. **Cascade retrieval** augments this by walking the graph structure, incorporating relationship types, tag co-occurrence, and edge weights. This hybrid approach surfaces memories that may not be vector-similar to the query but are structurally relevant through shared tags or explicit links.