LaserData Cloud
Laser SDK

Context

Assemble conversation messages, sessions, and memory within explicit size limits

Context collects messages and working memory for one conversation. It assembles a bounded history for a prompt. Sessions add typed turns and checkpointed replay on top of it. Knowledge graphs remain shared across conversations.

Built for

Use Context for multi-turn agents, support assistants, and session replay.

How it works

laser.context(conversation_id) creates a conversation scope. Message and memory operations inherit that ID. append(topic, payload) writes a turn to a topic. fetch(topics, n) reads the last n messages across selected topics.

For other limits, choose an assembly policy:

  • LastN(n) limits the message count.
  • TokenBudget(n) limits the estimated token count.
  • Chain([...]) applies policies in sequence, such as message count followed by token budget.

fetch requires a bound. Use fetch_with for composed policies or full replay.

Python's fetch, block, and assemble_context accept last_n and token_budget. They apply LastN before TokenBudget. Use assemble_context(roles=[...]) to filter roles.

The scope reaches every primitive

context(conversation) also applies to these operations:

  • block(topics, n) returns the last n messages as one newline-joined prompt string. All three languages support it.
  • scope.memory(namespace) applies the conversation ID to recall and writes. It also supports bounded blocks and consolidation. Unscoped laser.memory(..) reads across conversations.
  • scope.graph(name) returns the shared graph. Add conversation(id) to restrict a graph query to one conversation.

Rust's scope.state(topics, bound, init, fold) rebuilds state under a ReplayBound. Bounds include offsets, last N, and full replay. state_with(store, ..) starts from the latest snapshot and applies later records.

Context uses ordinary Iggy topics. Run bootstrap() once to create the agent topics.

Quick example

const ctx = laser.context(conversation)
await ctx.append(AgentTopic.Commands, utf8("book me an aisle seat"))
await ctx.append(AgentTopic.Responses, utf8("booked, aisle 12"))

const turns = await ctx.fetchWith(
  [AgentTopic.Commands, AgentTopic.Responses],
  new ContextChain([new LastN(20), new TokenBudget(4_000)])
)
for (const turn of turns) {
  console.log(decodeUtf8(turn.payload))
}
let scope = laser.context(conversation);
scope
    .append(
        AgentTopic::Commands,
        "book me an aisle seat".as_bytes(),
    )
    .await?;
scope
    .append(
        AgentTopic::Responses,
        "booked, aisle 12".as_bytes(),
    )
    .await?;

let turns = scope
    .fetch_with(
        vec![AgentTopic::Commands, AgentTopic::Responses],
        Box::new(Chain(vec![
            Box::new(LastN(20)),
            Box::new(TokenBudget::new(4_000)),
        ])),
    )
    .await?;

for turn in &turns {
    println!("{}", String::from_utf8_lossy(&turn.payload));
}
ctx = laser.context(conversation)
await ctx.append(ls.Topics.COMMANDS, b"book me an aisle seat")
await ctx.append(ls.Topics.RESPONSES, b"booked, aisle 12")

turns = await ctx.fetch(
    topics=[ls.Topics.COMMANDS, ls.Topics.RESPONSES],
    last_n=20,
    token_budget=4_000,
)
for turn in turns:
    print(bytes(turn.payload).decode())

Complete examples: Rust, Python, and TypeScript.

Sessions

A session is one agent's conversation as typed turns. laser.sessions().create(id) derives the conversation from id, so the same id always reaches the same history. start() mints a fresh conversation. open(conversation) attaches to an existing one, such as the conversation carried by an inbound message.

append(kind, data) writes one turn. Each kind rides one conversation-level agent topic:

KindTopic
instructionCommands
responseResponses
model.responseLlmIo
tool.callToolCalls
tool.resultToolResults
human.inputHumanInput

A turn is an ordinary agent message. Any reader of those topics sees it, and any agent message on them reads back as a turn. Sessions build on context(..) and add no second store or wire operation.

context() returns the last 50 turns across the session's topics, trimmed to about 4000 estimated tokens. memory() is the conversation's scoped memory in the agent.session namespace. checkpoint() records the next offset per topic partition. It serializes, so you can persist it anywhere. turns_at(checkpoint) and state_at(..) read up to that point. turns_since(checkpoint) and replay(..) read after it.

Change the layout when one fleet must not share topics with another. Rust uses sessions_with(SessionConfig). TypeScript passes options to sessions({ .. }). Python passes keyword arguments to sessions(..). You can select the stream, one topic per kind, the memory namespace, and the context bounds. Two kinds cannot share a topic.

const session = laser.sessions().create("agent-42")
await session.append("instruction", utf8("summarize the ticket"))
await session.append("model.response", utf8("it is a login bug"))

const turns = await session.context()
const hits = await session.memory().search("login bug")
const checkpoint = await session.checkpoint()
const later = await session.turnsSince(checkpoint)
let session = laser.sessions().create("agent-42");
session
    .append(SessionTurnKind::Instruction, b"summarize the ticket")
    .await?;
session
    .append(SessionTurnKind::ModelResponse, b"it is a login bug")
    .await?;

let turns = session.context().await?;
let facts = session.memory().search("login bug").await?;
let checkpoint = session.checkpoint().await?;
let later = session.turns_since(checkpoint).await?;
session = laser.sessions().create("agent-42")
await session.append("instruction", b"summarize the ticket")
await session.append("model.response", b"it is a login bug")

turns = await session.context()
facts = await session.memory().search("login bug")
checkpoint = await session.checkpoint()
later = await session.turns_since(checkpoint)

Key operations

VerbWhat it does
context(conversation_id)Scope everything below to one conversation
append(topic, payload)Write one turn under this conversation
fetch(topics, n)Read the last n messages, default policy
fetchWith(topics, policy)Read using a composed policy (Python: fetch(last_n=, token_budget=))
block(topics, n)The last n messages as one prompt-ready string
LastN(n)Policy: cap by message count (Python: last_n=)
TokenBudget(n)Policy: cap by estimated token count (Python: token_budget=)
Chain([...])Compose policies in sequence (Python: passing both keywords)
scope.memory(ns) / scope.graph(name)This conversation's memory, and the shared graph, from one scope
state(topics, bound, init, fold)Fold the conversation's log into in-memory state (Rust)
state_with(store, ..)The same fold seeded from a snapshot, replaying only the tail (Rust)
sessions().create(id) / start() / open(conversation)One agent's conversation as typed turns
session.append(kind, data)Write one typed turn on its agent topic
session.context()The last turns, bounded by count and estimated tokens
session.memory()This conversation's scoped memory in the session namespace
session.checkpoint()Save the next offset per topic partition
turns_at(checkpoint) / turns_since(checkpoint)Turns before or after a checkpoint
state_at(checkpoint, init, fold) / replay(checkpoint, init, fold)Fold turns before or after a checkpoint into state

Running it

Context and sessions work with Iggy and do not require laser-plane.

On this page