LaserData Cloud
Laser SDK

Memory

Remember facts for an agent or a user, recall them from any agent, and trace each one back to the log

Memory stores facts your agents learn, such as a user's preferences or the fix that closed an incident. Any agent that opens the same namespace can recall them. Every change is a record on the log, so you can see who wrote a fact, when, and from which message.

Quick example

This program remembers a fact about a user, recalls it, and forgets it. It reads the memory topic in process with folded, so it runs against plain Apache Iggy.

import { ConversationId, Laser, TopicRetention, memoryItemText } from "@laserdata/laser-sdk"

await using laser = await Laser.connectWithStream("iggy:[email protected]:8090", "support")
await laser.bootstrap(1, TopicRetention.expireAfter(86_400_000))

const memory = laser.memory("support")
const conversation = ConversationId.new()

const fact = await memory
  .remember(new TextEncoder().encode("Prefers email, works in UTC+2"))
  .scope(conversation)
  .user("user-42")
  .send()

const hits = await memory.recall(conversation).user("user-42").recent().limit(5).folded().fetch()
for (const hit of hits) console.log(memoryItemText(hit))

await memory.forget({ conversation }, fact)
use laser_sdk::prelude::full::*;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), LaserError> {
    let laser = Laser::connect_with_stream("iggy:[email protected]:8090", "support").await?;
    laser
        .bootstrap(1, TopicRetention::expire_after(Duration::from_secs(86_400)))
        .await?;

    let memory = laser.memory("support");
    let conversation = ConversationId::new();

    let fact = memory
        .remember("Prefers email, works in UTC+2".as_bytes())
        .scope(conversation)
        .user("user-42")
        .send()
        .await?;

    let hits = memory
        .recall(conversation)
        .user("user-42")
        .recent()
        .limit(5)
        .folded()
        .fetch()
        .await?;
    for hit in &hits {
        println!("{}", hit.text());
    }

    let scope = MemoryScope::builder().conversation(conversation).build();
    memory.forget(&scope, fact).await?;
    Ok(())
}
import asyncio

import laser_sdk as ls


async def main():
    async with await ls.Laser.connect("iggy:[email protected]:8090", stream="support") as laser:
        await laser.bootstrap(1, retention=ls.TopicRetention.expire_after(86_400_000))

        memory = laser.memory("support")
        conversation = ls.new_conversation_id()

        fact = await memory.remember(
            "Prefers email, works in UTC+2",
            conversation=conversation,
            user="user-42",
        )

        hits = await memory.recall(
            limit=5,
            conversation=conversation,
            user="user-42",
            strategy="recent",
            folded=True,
        )
        for hit in hits:
            print(hit.text())

        await memory.forget(fact, conversation=conversation)

asyncio.run(main())

bootstrap creates the agent.memory topic once per stream. Drop folded to read the managed view on Laser Stack or LaserData Cloud, which scales to large histories.

Share facts across agents

Agents share memory by opening the same namespace on the same stream, in any of the three languages. Each item records the conversation, agent, user, and application it belongs to. Set them when you remember and narrow by them when you recall. A field you leave out widens the read, so a recall without a user returns every user's items. Agents shows MemoryHandler, which remembers every message an agent handles.

import { AgentId } from "@laserdata/laser-sdk"

const triage = AgentId.new("triage")
await memory.remember(new TextEncoder().encode("Disk alerts on node-7 are noise")).scope(conversation).agent(triage).send()

const notes = await memory.recall(conversation).agent(triage).fetch()
let triage: AgentId = "triage".parse()?;
memory
    .remember("Disk alerts on node-7 are noise".as_bytes())
    .scope(conversation)
    .agent(triage.clone())
    .send()
    .await?;

let notes = memory.recall(conversation).agent(triage).fetch().await?;
await memory.remember(
    "Disk alerts on node-7 are noise",
    conversation=conversation,
    agent="triage",
)

notes = await memory.recall(conversation=conversation, agent="triage")

Recall by meaning

The vector backend ranks items by how close they are to a question. You supply the embedding function, usually a call to an embedding model. Vector items live in process memory, so use this for working memory inside one agent.

import { MemoryBackend } from "@laserdata/laser-sdk"

const runbooks = laser.memoryWith("runbooks", MemoryBackend.Vector, embedder)
await runbooks.remember(new TextEncoder().encode("Gateway latency spikes mean the db pool is exhausted")).scope(conversation).send()

const hits = await runbooks.recall(conversation).semantic("gateway is slow").limit(3).fetch()
let runbooks = laser.memory_with("runbooks", MemoryBackend::Vector).embedder(embedder);
runbooks
    .remember("Gateway latency spikes mean the db pool is exhausted".as_bytes())
    .scope(conversation)
    .send()
    .await?;

let hits = runbooks.recall(conversation).semantic("gateway is slow").limit(3).fetch().await?;
runbooks = laser.memory_with("runbooks", "vector", embedder=embed)
await runbooks.remember(
    "Gateway latency spikes mean the db pool is exhausted",
    conversation=conversation,
)

hits = await runbooks.recall(conversation=conversation, semantic="gateway is slow", limit=3)

Trace a fact back to the log

Each item recalled from log memory carries source, the stream, topic, partition, and offset of the record it came from. Inside a session, linked_memory() (TypeScript: linkedMemory) also stamps each new item with the record the session was handling and the agent that wrote it.

Reweight or forget

improve adds a feedback weight that moves an item up or down in later recalls. forget writes a tombstone, so the item stops appearing while the topic keeps the history. For a value you look up by name, such as the current plan, use set(key, body) and fetch(key) instead of a recall.

Good to know

  • Default recall and fetch read the managed view, so they need Laser Stack or LaserData Cloud. folded recall and fetch_folded work with Iggy alone. Keep one handle for folded reads, because it reads only new records on each call.
  • The managed view catches up in the background, so a fresh write can take a moment to show in default recall.
  • Only the vector backend ranks by meaning. Log memory uses query text as a filter and returns the newest matches.
  • Namespaces are scoped to the default stream, so laser.memory("support") addresses stream:<stream>/support.
  • In Rust, memory needs the agent feature, and default recall also needs kv, which managed includes.

On this page