LaserData Cloud
Laser SDK

Memory

Remember what matters.

Remember, recall, improve, and forget on an auditable log. Add a vector backend or reranker when recall should search by meaning.

Built for

Assistants that remember users, agents that learn

How it works

laser.memory(namespace) creates durable agent memory. Every remember, improve, and forget operation writes a log message under a conversation id. Any process with a connection can recall that memory. Its history remains versioned and auditable.

  • Default recall ranks by recency. It reads the managed key-value read view - the source of truth, not a search index.
  • .folded() reads the memory topic in process instead, so recall works against standalone VSR-enabled Apache Iggy with no managed capability. This is what the quick example below uses. All three SDKs carry it.
  • Semantic recall needs a ranking backend. Rust uses memory_with(namespace, MemoryBackend::Vector).embedder(..). Python uses Memory.vector(embedder). TypeScript uses MemoryHandle.vector(embedder). Rust and TypeScript can add a reranker. Plain log memory treats .semantic(..) as recent recall.

.limit(n) caps how many hits come back. The token-budget framing in the ELI5 refers to trimming a recall result set to fit a downstream LLM call's budget, the same concern Context applies to conversation assembly.

Recall is four strategies, not one. Each sets the ranking, the sugar call carries the query text:

  • .recent() - the most recent items, no query text needed.
  • .semantic(text) - ranked by embedding similarity. Honored by an embedding backend, the plain log backend falls back to most recent.
  • .keyword(text) - exact-term matching, for identifiers and names an embedding blurs.
  • .hybrid(text) - fuses the semantic and keyword signals by reciprocal rank. Each fused item keeps its per-signal attribution in signals.

A memory has a kind. remember(..) defaults to Fact. Other kinds are Message, Summary, Entity, Feedback, and Procedure. They distinguish events, distilled content, graph nodes, ranking signals, and reusable workflows. Set the kind with .kind(..).

Dedup is content-addressed. .dedup() on a remember derives the id from the owner, kind, and body instead of minting a random one, so remembering the same fact twice stores it once. This is the same content-addressing the graph uses for node ids.

Named facts skip recall entirely: set_named(key, body), fetch_named(key), update_named(key, patch), and forget_named(key) treat memory as a keyed store for the facts you address directly instead of searching for.

From recall to prompt. Rust's memory.context(conversation, token_budget) recalls relevant items and renders one prompt-ready block. It estimates tokens at roughly 4 bytes each to avoid a tokenizer dependency. When the budget is full, it drops the remaining items and appends an omission marker.

Consolidation limits scope growth. consolidate(scope, max_items) keeps the newest items, prunes the rest, and returns a report. DefaultConsolidator can summarize pruned items into a Summary. prune_summarized() removes items covered by that summary.

Quick example

const memory = laser.memory("customer:42")
const conversation = ConversationId.new()

const id = await memory
  .remember(utf8("Prefers aisle seats, travels monthly"))
  .conversation(conversation)
  .send()

const hits = await memory
  .recall()
  .conversation(conversation)
  .recent()
  .limit(5)
  .folded()
  .fetch()
for (const hit of hits) {
  console.log(decodeUtf8(hit.payload))
}

await memory.improve({ conversation }, { target: id, weight: 1 })
await memory.forget({ conversation }, id)
let memory = laser.memory("customer:42");
let scope = MemoryScope::builder().conversation(conversation).build();

let fact = memory
    .remember("Prefers aisle seats, travels monthly".as_bytes())
    .scope(conversation)
    .send()
    .await?;

let hits = memory
    .recall(conversation)
    .recent()
    .limit(5)
    .folded()
    .fetch()
    .await?;

for hit in &hits {
    println!("{}", String::from_utf8_lossy(&hit.payload));
}

memory.improve(&scope, Feedback::new(fact, 1.0)).await?;
memory.forget(&scope, fact).await?;
memory = laser.memory("customer:42")

fact_id = await memory.remember(
    "Prefers aisle seats, travels monthly",
    conversation=conversation,
)

hits = await memory.recall(
    limit=5,
    conversation=conversation,
    strategy="recent",
    folded=True,
)
print([hit.text for hit in hits])

await memory.improve(fact_id, 1.0, conversation=conversation)
await memory.forget(fact_id, conversation=conversation)

Full runnable example: Rust · Python · TypeScript

The full durable-memory-plus-knowledge-graph deep dive: memory

Key operations

VerbWhat it does
laser.memory(namespace)Scope memory to a namespace, log-backed and versioned
remember(payload)Store a fact under a conversation scope
.kind(k) / .dedup() / .agent(id)Memory kind, content-addressed dedup, per-agent scoping on a remember
recall(..) + .recent() / .semantic(t) / .keyword(t) / .hybrid(t)The four recall strategies
.folded()Read the memory topic in process, no managed capability needed
.embedder(..) / .reranker(..)Attach an embedding function or a reranking pass
limit(n)Cap how many hits come back
improve(id, weight, ..)Adjust a remembered item's standing with feedback
forget(id, ..)Remove a remembered item
set_named(key, body) / fetch_named(key)Keyed facts you address directly instead of searching for
context(conversation, budget)Recall and render one prompt-ready block (Rust)
consolidate(scope, max_items)Prune a scope with the default keep-newest policy

Client differences: All three SDKs support the four verbs, recency-based managed reads, folded reads, and four recall strategies.

TypeScript includes kind, dedup, hybrid, and consolidate in camelCase.

Python uses Memory.vector(embedder) for similarity ranking. It supports named facts through set, fetch, update, and remove. It does not support kind= or dedup on remember. It also lacks the combined context and consolidate calls.

Running it

Memory records use the agent audit topic. Run bootstrap(partitions) once per stream before the first write. The example uses .folded(), so it runs on every VSR target. Default recall reads the managed KV view from laser-plane.

On this page