Memory¶
Conversation history that survives multiple agent(task) calls. Per-agent
by default, shareable across agents, automatically bounded by a
sliding-window or LLM-summary compression strategy.
Signature¶
from lazybridge import Memory
Memory(
*,
strategy="auto", # "auto" | "sliding" | "summary" | "none"
max_tokens=4000, # token budget that triggers compression
max_turns=1000, # hard backstop on retained turns
store=None, # Store — persist across process restarts
key=None, # required with store= — stable identity (e.g. agent name)
session_key="default", # secondary identity — distinguishes conversations under the same key
summarizer=None, # Agent or callable used by strategy="summary"
summarizer_timeout=30.0, # deadline for async summarisers (None = unbounded)
)
# Methods
mem.add(user, assistant, *, tokens=0) # append a turn
mem.messages() # list[Message] for the LLM
mem.text() # current view as a plain string (live)
mem.clear() # wipe everything in process
Pass to an Agent via memory=mem (private to that agent) or
sources=[mem] (live read-only view shared across agents).
Strategies¶
strategy= |
What it does | When |
|---|---|---|
"auto" |
Sliding window + summary of older turns once max_tokens is exceeded |
General chat, the safe default |
"sliding" |
Drops oldest turns whenever > 10 are retained; works without max_tokens |
Cheap, lossy, no LLM cost |
"summary" |
Compresses whenever > 10 turns are retained, using summarizer= (or a keyword-extraction fallback) |
Higher fidelity at the cost of summariser tokens |
"none" |
Never compress; only max_turns bounds the buffer |
You want full history and have explicit control over size |
Synopsis¶
Memory is "what the model should see in the next prompt". It carries
conversation continuity across calls to the same agent (or across
several agents that share the instance). The default "auto" strategy
keeps the memory bounded without any tuning — sliding window first,
LLM summary of the older turns once the token budget is exceeded.
By default Memory is not durable — the whole buffer lives in the
agent's process and disappears when the process exits. Pass
store= (a Store) and key= (a stable identifier, typically
the owning agent's name) to persist the compressed turns/summary across
restarts: Memory reloads them in __init__ and writes the full current
state back after every add(), amend_last(), and clear(). Under the
hood this lands in a dedicated agent_memory table on the Store, keyed by
(key, session_key) — not the generic store.write(key, value) blackboard
— so persisted conversations have real identity instead of a
string-key naming convention. session_key (default "default") lets
one key (e.g. one agent) hold several independent conversations — one
per user, one per thread — without colliding.
When to use it¶
- Multi-turn conversations with a single agent. Without
Memory, everyagent(task)call starts fresh; with it, the model sees the recent history. - Cross-agent shared context when you want a judge or monitor
agent to read the live conversation without writing to it. Pass the
same
Memoryto the chat agent'smemory=and the judge agent'ssources=[mem]. - Bounded buffers in long-running interactive sessions — the
default
"auto"strategy keeps token usage from growing without bound, and you don't have to do the trimming yourself.
When NOT to use it¶
- Durable cross-run state without
store=. PlainMemory()doesn't survive process exit. Passstore=+key=(see Example 4) when the agent needs to resume where it left off after a restart. - Pipeline data passing. A
Planstep's output flows to the next step via the envelope and sentinels (from_prev,from_step("…")), not via memory. Memory is for conversational context, not workflow state. - Structured-output retry loops. When LazyBridge re-prompts the agent to fix an invalid structured-output payload, those correction turns are not added to memory — and neither should you add them manually.
Example¶
from lazybridge import Agent, LLMEngine, Memory
# 1) Default "auto" strategy — sliding window + summary fallback.
chat_memory = Memory(
strategy="auto",
max_tokens=3000,
)
chat = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
memory=chat_memory,
name="chat",
)
chat("hi, I'm Marco")
result = chat("what's my name?")
print(result.text()) # "Marco"
print(chat_memory.text()) # current compressed view
# 2) "summary" strategy with a cheap summariser.
summariser = Agent(
engine=LLMEngine(
"claude-haiku-4-5-20251001",
system="Summarize conversations concisely.",
),
)
high_fidelity_memory = Memory(
strategy="summary",
summarizer=summariser,
summarizer_timeout=15.0,
)
# 3) Sharing live conversation across agents — chat writes, judge reads.
chat = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
memory=chat_memory,
name="chat",
)
judge = Agent(
engine=LLMEngine(
"claude-opus-4-7",
system="Grade the assistant's last reply on helpfulness 1-5.",
),
sources=[chat_memory], # read-only live view
name="judge",
)
chat("explain LazyBridge in one sentence")
print(judge("grade the last turn").text())
# 4) Persistent memory — survives an agent restart.
from lazybridge import Store
store = Store(db="agents.sqlite")
support_memory = Memory(
strategy="auto",
max_tokens=3000,
store=store,
key="support-agent", # stable identity — pass the agent's own name
)
support = Agent(
engine=LLMEngine("claude-opus-4-7"),
memory=support_memory,
name="support-agent",
)
support("my order #4821 hasn't arrived")
# ...process restarts...
support_memory_2 = Memory(strategy="auto", max_tokens=3000, store=store, key="support-agent")
support_2 = Agent(engine=LLMEngine("claude-opus-4-7"), memory=support_memory_2, name="support-agent")
support_2("any update?") # still has order #4821 in context
Pitfalls¶
strategy="summary"without asummarizer=falls back to keyword extraction — bounded, but lossy. Pass a cheap agent for production-quality summaries.memory.clear()wipes everything including the in-process summary. Withstore=set, the wipe is persisted too — a restart right afterclear()comes back empty, not with the pre-clear state.store=withoutkey=raisesValueErrorat construction — identity must be explicit; there's no auto-generated fallback that would silently make twoMemoryinstances collide or fail to find each other.- Every
add()writes the full state to the Store, not a delta. Fine at single-user / low-throughput scale; if a single agent callsadd()at high frequency, that's a SQLite upsert on every turn — profile before assuming it's free. max_turnsis a hard backstop, not the primary compression knob. When it fires you get a one-shot warning — that's the signal to switch fromstrategy="none"to"auto".summarizer_timeout=Nonerestores the legacy unbounded behaviour. Use it only if you trust your summariser to be fast and reliable; otherwise a stuck summariser will block everyadd(...)call indefinitely.memory.text()is a live read — every call re-materialises the current view. Don't snapshot and cache it; if you need a stable reference for diagnostics, copy the string.- Sync summarisers can't be cancelled mid-call. Only async
summarisers (
async defor returning a coroutine) honoursummarizer_timeout. On timeout the keyword fallback runs. - Compression happens outside the internal lock, so concurrent
add()calls keep progressing while a slow summariser is in flight. This means a memory snapshot taken during compression may reflect the pre-compression view; that's intentional, but worth knowing if you're debugging.