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 lastnmessages 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. Unscopedlaser.memory(..)reads across conversations.scope.graph(name)returns the shared graph. Addconversation(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:
| Kind | Topic |
|---|---|
instruction | Commands |
response | Responses |
model.response | LlmIo |
tool.call | ToolCalls |
tool.result | ToolResults |
human.input | HumanInput |
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
| Verb | What 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.