Pagination
Cursor-based enumeration on the wire client — the flatten vs *_frames pattern, and how to page memory_list and graph_fetch.
Pagination lives on BrainClient. BrainHttpClient does not expose
memory_list, graph_fetch, or the typed-graph list_* verbs; its recall
takes a max_results cap and returns a single RecallResult. See
Wire client. All request types live under
brain_db_sdk::wire::types.
Two forms per enumeration
Every streamed verb exposes both a flattening method and a *_frames method:
| Flatten | Raw frames |
|---|---|
memory_list | memory_list_frames |
graph_fetch | graph_fetch_frames |
list_entities | list_entities_frames |
list_statements | list_statements_frames |
list_relations_from | list_relations_from_frames |
list_relations_to | list_relations_to_frames |
list_schemas | list_schemas_frames |
statement_history | statement_history_frames |
traverse_relations | traverse_relations_frames |
The flatten form is the common case: it drains every streamed frame and
concatenates the items into a single Vec for you.
MemoryListRequest has no Default — spell out every field. v1 supports
sort: Created (Asc/Desc); other sorts, the text filter, and the occurred
axis are rejected with InvalidRequest.
use brain_db_sdk::wire::types::{
MemoryListRequest, MemoryListSortWire, MemoryListDirWire, MemoryListTimeAxisWire,
};
let memories = client
.memory_list(&MemoryListRequest {
sort: MemoryListSortWire::Created,
dir: MemoryListDirWire::Desc,
limit: 100, // server validates to 1..=100
cursor: Vec::new(), // empty = first page
kinds: Vec::new(), // empty = all kinds
include_tombstoned: false,
time_axis: MemoryListTimeAxisWire::Created,
from_unix_nanos: 0, // 0 = no lower bound
to_unix_nanos: 0, // 0 = no upper bound
salience_min: 0.0,
salience_max: 1.0,
text_contains: String::new(),
act_as: None,
})
.await?;Driving the cursor yourself
Use a *_frames method when you want to page incrementally — render a page,
then fetch the next. Each frame carries next_cursor, cumulative_count, and
is_final; a non-empty next_cursor means more pages remain.
let mut cursor: Vec<u8> = Vec::new(); // empty on the first page
loop {
let frames = client
.memory_list_frames(&MemoryListRequest {
sort: MemoryListSortWire::Created,
dir: MemoryListDirWire::Desc,
limit: 50,
cursor: cursor.clone(),
kinds: Vec::new(),
include_tombstoned: false,
time_axis: MemoryListTimeAxisWire::Created,
from_unix_nanos: 0,
to_unix_nanos: 0,
salience_min: 0.0,
salience_max: 1.0,
text_contains: String::new(),
act_as: None,
})
.await?;
for frame in &frames {
for item in &frame.items {
render(item);
}
}
let last = frames.last().expect("at least one frame");
if last.is_final && last.next_cursor.is_empty() {
break;
}
cursor = last.next_cursor.clone(); // opaque continuation token
}The cursor is an opaque byte string — pass it back verbatim, don't parse it. An
empty cursor requests the first page; a non-empty next_cursor from a frame
requests the following page.
graph_fetch
graph_fetch exports the caller's whole typed graph the same way, but pages
{ nodes, edges }. Its limit validates server-side to 1..=500. Nodes and
edges may repeat across pages (completeness, not disjointness) — dedup by id if
you need a unique set.
GraphFetchRequest has no Default either. include_memory_edges requires
include_memories — setting it alone is rejected, since the edges would have no
rendered endpoints.
use std::collections::HashMap;
use brain_db_sdk::wire::types::GraphFetchRequest;
let mut cursor: Vec<u8> = Vec::new();
let mut nodes = HashMap::new();
let mut edges = HashMap::new();
loop {
let frames = client
.graph_fetch_frames(&GraphFetchRequest {
limit: 500,
cursor: cursor.clone(),
include_statements: true,
include_memories: false,
include_memory_edges: false,
include_tombstoned: false,
act_as: None,
})
.await?;
for frame in &frames {
for n in &frame.nodes {
nodes.insert(n.id, n.clone()); // dedup by node id
}
for e in &frame.edges {
edges.insert((e.from_id, e.to_id, e.kind), e.clone()); // dedup by endpoints + kind
}
}
let last = frames.last().expect("at least one frame");
if last.is_final && last.next_cursor.is_empty() {
break;
}
cursor = last.next_cursor.clone();
}When the whole result fits in memory, the flattening graph_fetch is simpler —
it drains every frame and returns the combined { nodes, edges } for you.
Streamed recall
recall streams too, but its frames carry no cursor — they terminate at EOS.
The flattening recall concatenates them into one RecallAnswer; use
recall_frames to see each frame's cumulative counts and estimated_remaining
(and the per-stage trace when you asked for it). See
recall.
Was this page helpful?
Retries
Retry policies for both clients — the HTTP client's built-in HttpRetryPolicy for idempotent verbs, the wire client's with_retry combinator over RetryPolicy, and how deadlines cancel a call.
Recipes
Copy-runnable Brain recipes — under a screen of code each, problem-focused, using the real encode / recall / forget verbs.