Sessions·续1
AI tools
Session automatically generates tools based on the provider types of your context blocks. Pass these to your LLM alongside your own tools.
const tools = await session.tools();
const allTools = { ...tools, ...myTools };
const tools = await session.tools();
const allTools = { ...tools, ...myTools };
set_context
Generated when any writable block exists. Writes to regular blocks, skill blocks (keyed), or search blocks (keyed). Enforces maxTokens limits.
load_context
Generated when any skill block exists. Loads full content by key from a SkillProvider.
unload_context
Generated alongside load_context. Frees context space by unloading a previously loaded skill. The skill remains available for re-loading.
search_context
Generated when any search block exists. Full-text search within a searchable context block. Returns top 10 results by FTS5 rank.
session_search
Available on SessionManager only. Searches across all sessions.
Compaction
Compaction summarizes older messages to keep conversations within token limits. Original messages are preserved in SQLite — the summary is a non-destructive overlay applied at read time.
Setup
import { createCompactFunction } from "agents/experimental/memory/utils/compaction-helpers";
const session = Session.create(this)
.withContext("memory", { maxTokens: 1100 })
.onCompaction(
createCompactFunction({
summarize: (prompt) =>
generateText({ model: myModel, prompt }).then((r) => r.text),
protectHead: 3,
tailTokenBudget: 20000,
minTailMessages: 2,
tokenCounter: async (messages) => estimateWithYourTokenizer({ messages }),
}),
)
.compactAfter(100_000);
import { createCompactFunction } from "agents/experimental/memory/utils/compaction-helpers";
const session = Session.create(this)
.withContext("memory", { maxTokens: 1100 })
.onCompaction(
createCompactFunction({
summarize: (prompt) =>
generateText({ model: myModel, prompt }).then((r) => r.text),
protectHead: 3,
tailTokenBudget: 20000,
minTailMessages: 2,
tokenCounter: async (messages) => estimateWithYourTokenizer({ messages }),
}),
)
.compactAfter(100_000);
How compaction works
- Protect head — first N messages are never compacted (default 3)
- Protect tail — walk backward from the end, accumulating tokens up to a budget (default 20K tokens)
- Align boundaries — shift boundaries to avoid splitting tool call/result pairs
- Summarize middle — send the middle section to an LLM with a structured format (Topic, Key Points, Current State, Open Items)
- Store overlay — saved in the
assistant_compactionstable, keyed byfromMessageIdandtoMessageId - Iterative — on subsequent compactions, the existing summary is passed to the LLM to update rather than replace
When getHistory() is called, compaction overlays are applied transparently — the compacted range is replaced by a synthetic summary message.
Manual compaction
const result = await session.compact();
// Or manage overlays directly
await session.addCompaction("Summary of messages 1-50", "msg-1", "msg-50");
const overlays = await session.getCompactions();
const result = await session.compact();
// Or manage overlays directly
await session.addCompaction("Summary of messages 1-50", "msg-1", "msg-50");
const overlays = await session.getCompactions();
Auto-compaction
When .compactAfter(threshold) is set, appendMessage() checks the estimated token count after each write. If it exceeds the threshold, compact() is called automatically. Auto-compaction failure is non-fatal — the message is already saved.
Note
Auto-compaction is checked between turns (on each appendMessage()), not within a turn. A single long, tool-heavy turn can grow past the model context window mid-flight, before the next check. @cloudflare/think adds opt-in mid-turn recovery on top of this — refer to Context-window overflow recovery.
By default, the estimate includes stored message parts plus the Session-managed frozen system prompt, so context blocks and cached prompts managed by Session contribute to the threshold. It does not include framework-specific prompt additions or tool schema serialization that happen outside Session.
There are two token-counting decisions:
.compactAfter(threshold, { tokenCounter })controls when automatic compaction is triggered after writes.createCompactFunction({ tokenCounter })controls which tail messages are protected from summarization. Use this when tool-heavy histories are much larger than the Workers-safe heuristic can estimate.
You usually only need to configure one counter. The .compactAfter() counter also flows into createCompactFunction's boundary walk (via CompactContext) when no explicit createCompactFunction({ tokenCounter }) is given, so a single counter drives both "should we compact?" and "what should we compact?".
Caution
The flowed counter is invoked per message during the boundary walk. A tokenizer-style counter budgets accurately; a usage-only counter that returns a fixed whole-prompt total (for example usage.inputTokens regardless of which messages are passed) degrades the tail budget to minTailMessages — compaction still runs and context stays bounded, but the byte budget is effectively ignored. Pass an explicit per-message createCompactFunction({ tokenCounter }) for precise tail budgeting.
Use a custom counter when you have model-reported usage or your own tokenizer:
const session = Session.create(this)
.onCompaction(myCompactFn)
.compactAfter(100_000, {
tokenCounter: async ({ messages, systemPrompt, contextBlocks }) => {
return estimateWithYourTokenizer({
messages,
systemPrompt,
contextBlocks,
});
},
})
.onCompactionError((err) => {
console.warn("Auto-compaction failed", err);
});
const session = Session.create(this)
.onCompaction(myCompactFn)
.compactAfter(100_000, {
tokenCounter: async ({ messages, systemPrompt, contextBlocks }) => {
return estimateWithYourTokenizer({
messages,
systemPrompt,
contextBlocks,
});
},
})
.onCompactionError((err) => {
console.warn("Auto-compaction failed", err);
});
Note
The default token estimation is heuristic (not tiktoken). It uses max(chars/4, words*1.3) with 4 tokens per-message overhead, and also applies the string heuristic to the Session-managed system prompt. Tiktoken would add 80–120 MB heap overhead, which exceeds Cloudflare Workers' 128 MB limit.
SessionManager
SessionManager is a registry for multiple named sessions within a single Durable Object. It provides lifecycle management, convenience methods, and cross-session search.
Creating a SessionManager
import { SessionManager } from "agents/experimental/memory/session";
const manager = SessionManager.create(this)
.withContext("soul", { provider: { get: async () => "You are helpful." } })
.withContext("memory", { description: "Learned facts", maxTokens: 1100 })
.withCachedPrompt()
.onCompaction(myCompactFn)
.compactAfter(100_000)
.withSearchableHistory("history");
import { SessionManager } from "agents/experimental/memory/session";
const manager = SessionManager.create(this)
.withContext("soul", { provider: { get: async () => "You are helpful." } })
.withContext("memory", { description: "Learned facts", maxTokens: 1100 })
.withCachedPrompt()
.onCompaction(myCompactFn)
.compactAfter(100_000)
.withSearchableHistory("history");
Context blocks, prompt caching, and compaction settings are propagated to all sessions created through the manager. Provider keys are automatically namespaced by session ID.
Builder methods
| Method | Description |
|---|---|
SessionManager.create(agent) |
Static factory. |
.withContext(label, options?) |
Add context block template for all sessions. |
.withCachedPrompt(provider?) |
Enable prompt persistence for all sessions. |
.onCompaction(fn) |
Register compaction function for all sessions. |
.compactAfter(tokenThreshold, options?) |
Auto-compact threshold for all sessions. Supports the same tokenCounter option as Session. |
.onCompactionError(handler) |
Handle automatic compaction errors for managed sessions. |
.withSearchableHistory(label) |
Add a cross-session searchable history block. The model can search past conversations from any session. |
Session lifecycle
// Create a new session
const info = await manager.create("My Chat");
// Create with metadata
const info2 = await manager.create("My Chat", {
parentSessionId: "parent-id",
model: "claude-sonnet-4-20250514",
source: "web",
});
// Get session metadata (null if not found)
const session = await manager.get(sessionId);
// List all sessions (ordered by updated_at DESC)
const sessions = await manager.list();
// Rename
await manager.rename(sessionId, "New Name");
// Delete (clears messages too)
await manager.delete(sessionId);
// Create a new session
const info = await manager.create("My Chat");
// Create with metadata
const info2 = await manager.create("My Chat", {
parentSessionId: "parent-id",
model: "claude-sonnet-4-20250514",
source: "web",
});
// Get session metadata (null if not found)
const session = await manager.get(sessionId);
// List all sessions (ordered by updated_at DESC)
const sessions = await manager.list();
// Rename
await manager.rename(sessionId, "New Name");
// Delete (clears messages too)
await manager.delete(sessionId);
Accessing sessions
// Get or create the Session instance for an ID
// Lazy — creates on first access, caches for subsequent calls
const session = manager.getSession(sessionId);
// Get or create the Session instance for an ID
// Lazy — creates on first access, caches for subsequent calls
const session = manager.getSession(sessionId);
Message convenience methods
These delegate to the underlying Session and update the session's updated_at timestamp:
// Append a single message
await manager.append(sessionId, message, parentId);
// Add or update (upsert)
await manager.upsert(sessionId, message, parentId);
// Batch append (auto-chains parent IDs)
await manager.appendAll(sessionId, messages, parentId);
// Read history
const history = await manager.getHistory(sessionId, leafId);
// Message count
const count = await manager.getMessageCount(sessionId);
// Clear messages
await manager.clearMessages(sessionId);
// Delete specific messages
await manager.deleteMessages(sessionId, ["msg-1"]);
// Append a single message
await manager.append(sessionId, message, parentId);
// Add or update (upsert)
await manager.upsert(sessionId, message, parentId);
// Batch append (auto-chains parent IDs)
await manager.appendAll(sessionId, messages, parentId);
// Read history
const history = await manager.getHistory(sessionId, leafId);
// Message count
const count = await manager.getMessageCount(sessionId);
// Clear messages
await manager.clearMessages(sessionId);
// Delete specific messages
await manager.deleteMessages(sessionId, ["msg-1"]);
Forking
Fork a session at a specific message — copies history up to that point into a new session:
const forked = await manager.fork(sessionId, atMessageId, "Forked Chat");
// forked.parent_session_id === sessionId
const forked = await manager.fork(sessionId, atMessageId, "Forked Chat");
// forked.parent_session_id === sessionId
Compaction helpers
// Add a compaction overlay
await manager.addCompaction(sessionId, summary, fromId, toId);
// Get overlays
const compactions = await manager.getCompactions(sessionId);
// Compact and split — marks old session as ended, creates a continuation
const continuation = await manager.compactAndSplit(
sessionId,
summary,
"Continued Chat",
);
// Add a compaction overlay
await manager.addCompaction(sessionId, summary, fromId, toId);
// Get overlays
const compactions = await manager.getCompactions(sessionId);
// Compact and split — marks old session as ended, creates a continuation
const continuation = await manager.compactAndSplit(
sessionId,
summary,
"Continued Chat",
);
compactAndSplit() creates a new session with a summary message instead of an in-place overlay. The original session is marked with end_reason: "compaction".
Usage tracking
await manager.addUsage(sessionId, inputTokens, outputTokens, cost);
await manager.addUsage(sessionId, inputTokens, outputTokens, cost);
Cross-session search
// Search across all sessions (FTS5)
const results = await manager.search("deployment Friday", { limit: 20 });
// Get tools for the model (includes session_search)
const tools = await manager.tools();
// Search across all sessions (FTS5)
const results = await manager.search("deployment Friday", { limit: 20 });
// Get tools for the model (includes session_search)
const tools = await manager.tools();
Custom providers
Implement any of the four provider interfaces to plug in your own storage:
