Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sessions

Session API 为 agent 提供持久化的会话存储,带有树状结构消息(灵感来自 Pi ↗)、上下文块、压缩、全文搜索以及 AI 可控工具。它完全运行在 Durable Object SQLite 上 — 无需外部数据库。

实验性

Session API 位于 agents/experimental/memory/session 下。API 接口稳定,但在毕业到主包之前可能会演变。

快速开始

JavaScript


import { Agent } from "agents";

import { Session } from "agents/experimental/memory/session";


class MyAgent extends Agent {

  session = Session.create(this)

    .withContext("soul", {

      provider: { get: async () => "You are a helpful assistant." },

    })

    .withContext("memory", {

      description: "Learned facts about the user",

      maxTokens: 1100,

    })

    .withCachedPrompt();


  async onMessage(message) {

    await this.session.appendMessage(message);

    const history = this.session.getHistory();

    const system = await this.session.freezeSystemPrompt();

    const tools = await this.session.tools();

    // Pass history, system prompt, and tools to your LLM

  }

}


Explain Code

TypeScript


import { Agent } from "agents";

import { Session } from "agents/experimental/memory/session";


class MyAgent extends Agent {

  session = Session.create(this)

    .withContext("soul", {

      provider: { get: async () => "You are a helpful assistant." },

    })

    .withContext("memory", {

      description: "Learned facts about the user",

      maxTokens: 1100,

    })

    .withCachedPrompt();


  async onMessage(message: unknown) {

    await this.session.appendMessage(message);

    const history = this.session.getHistory();

    const system = await this.session.freezeSystemPrompt();

    const tools = await this.session.tools();

    // Pass history, system prompt, and tools to your LLM

  }

}


Explain Code

创建会话

Builder API(推荐)

使用带有可链式 builder 的 Session.create(agent)。没有显式 provider 选项的上下文 provider 会自动连接到 SQLite。

JavaScript


const session = Session.create(this)

  .withContext("soul", { provider: { get: async () => "You are helpful." } })

  .withContext("memory", { description: "Learned facts", maxTokens: 1100 })

  .withCachedPrompt()

  .onCompaction(myCompactFn)

  .compactAfter(100_000);


TypeScript


const session = Session.create(this)

  .withContext("soul", { provider: { get: async () => "You are helpful." } })

  .withContext("memory", { description: "Learned facts", maxTokens: 1100 })

  .withCachedPrompt()

  .onCompaction(myCompactFn)

  .compactAfter(100_000);


直接构造

为了完全控制 provider:

JavaScript


import {

  Session,

  AgentSessionProvider,

  AgentContextProvider,

} from "agents/experimental/memory/session";


const session = new Session(new AgentSessionProvider(this), {

  context: [

    {

      label: "memory",

      description: "Notes",

      maxTokens: 500,

      provider: new AgentContextProvider(this, "memory"),

    },

    { label: "soul", provider: { get: async () => "You are helpful." } },

  ],

});


Explain Code

TypeScript


import {

  Session,

  AgentSessionProvider,

  AgentContextProvider,

} from "agents/experimental/memory/session";


const session = new Session(new AgentSessionProvider(this), {

  context: [

    {

      label: "memory",

      description: "Notes",

      maxTokens: 500,

      provider: new AgentContextProvider(this, "memory"),

    },

    { label: "soul", provider: { get: async () => "You are helpful." } },

  ],

});


Explain Code

Builder 方法

所有 builder 方法都返回 this 以支持链式调用。顺序无关紧要 — provider 在首次使用时才会被解析。

方法描述
Session.create(agent)静态工厂。agent 是任何带有 sql 标记模板方法的对象(你的 Agent 或 Durable Object)。
.forSession(sessionId)通过 ID 命名此会话。当不使用 SessionManager 时,多会话隔离需要此项。
.withContext(label, options?)添加上下文块。请参阅 Context blocks
.withCachedPrompt(provider?)启用系统提示持久化。提示在首次使用时被冻结,并在休眠和驱逐后保留。
.onCompaction(fn)注册压缩函数。请参阅 Compaction
.compactAfter(tokenThreshold)当估计的 token 数超过阈值时自动压缩。需要 .onCompaction()。

消息

消息使用 SessionMessage 类型 — 一个最小的形状,带有 idroleparts 和可选的 createdAt。Vercel AI SDK 的 UIMessage 在结构上兼容,可以直接传递。会话通过 parent_id 在树状结构中存储消息,启用分支会话。

JavaScript


// Append — auto-parents to the latest leaf unless parentId is specified

await session.appendMessage(message);

await session.appendMessage(message, parentId);


// Update an existing message (matched by message.id)

session.updateMessage(message);


// Delete specific messages

session.deleteMessages(["msg-1", "msg-2"]);


// Clear all messages and skill state

session.clearMessages();


Explain Code

TypeScript


// Append — auto-parents to the latest leaf unless parentId is specified

await session.appendMessage(message);

await session.appendMessage(message, parentId);


// Update an existing message (matched by message.id)

session.updateMessage(message);


// Delete specific messages

session.deleteMessages(["msg-1", "msg-2"]);


// Clear all messages and skill state

session.clearMessages();


Explain Code

注意

appendMessage()async 的,因为它可能触发自动压缩。底层的 SQLite 写入是同步的。所有其他写入方法(updateMessagedeleteMessagesclearMessages)是同步的。

读取历史

JavaScript


// Linear history from root to the latest leaf

const messages = session.getHistory();


// History to a specific leaf (for branching)

const branch = session.getHistory(leafId);


// Get a single message

const msg = session.getMessage("msg-1");


// Get the newest message

const latest = session.getLatestLeaf();


// Count messages in path

const count = session.getPathLength();


Explain Code

TypeScript


// Linear history from root to the latest leaf

const messages = session.getHistory();


// History to a specific leaf (for branching)

const branch = session.getHistory(leafId);


// Get a single message

const msg = session.getMessage("msg-1");


// Get the newest message

const latest = session.getLatestLeaf();


// Count messages in path

const count = session.getPathLength();


Explain Code

分支

消息形成一棵树。当你使用已有子项的 parentId 调用 appendMessage 时,你创建了一个分支。使用 getBranches() 获取从给定点分支的所有子消息:

JavaScript


// Get all child messages that branch from messageId

const branches = session.getBranches(messageId);


TypeScript


// Get all child messages that branch from messageId

const branches = session.getBranches(messageId);


这驱动了如响应重新生成等功能 — 传入用户消息 ID,即可同时获取原始响应和重新生成的响应。getHistory(leafId) 走选择的路径。

搜索

使用 SQLite FTS5 在会话历史上进行全文搜索:

JavaScript


const results = session.search("deployment Friday", { limit: 10 });

// Returns: Array<{ id, role, content, createdAt? }>


TypeScript


const results = session.search("deployment Friday", { limit: 10 });

// Returns: Array<{ id, role, content, createdAt? }>


使用 porter stemming 和 unicode 分词。搜索覆盖会话中的所有消息。

上下文块

上下文块是注入到系统提示中的持久化键值段。每个块都有一个 label、可选的 description 和决定其行为的 provider

Provider 类型

通过 duck-typing 检测出四种 provider 类型:

Provider接口行为AI 工具
ContextProviderget()系统提示中的只读块
WritableContextProviderget() + set()通过 AI 可写set_context
SkillProviderget() + load() + set?()按需键控文档。get() 返回元数据列表;load(key) 获取完整内容。load_context, unload_context, set_context
SearchProviderget() + search() + set?()全文可搜索条目。get() 返回摘要;search(query) 运行 FTS5。search_context, set_context

内置 provider

AgentContextProvider — SQLite 支持的可写上下文。这是在不使用显式 provider 时使用 builder 的默认值。

JavaScript


import { AgentContextProvider } from "agents/experimental/memory/session";


new AgentContextProvider(this, "memory");


TypeScript


import { AgentContextProvider } from "agents/experimental/memory/session";


new AgentContextProvider(this, "memory");


R2SkillProvider — 用于按需文档加载的 Cloudflare R2 bucket。Skill 在系统提示中作为元数据列出;模型通过 load_context 按需加载完整内容。

JavaScript


import { R2SkillProvider } from "agents/experimental/memory/session";


Session.create(this).withContext("skills", {

  provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" }),

});


TypeScript


import { R2SkillProvider } from "agents/experimental/memory/session";


Session.create(this).withContext("skills", {

  provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" }),

});


AgentSearchProvider — SQLite FTS5 可搜索上下文。条目被索引并可由模型通过 search_context 搜索。

JavaScript


import { AgentSearchProvider } from "agents/experimental/memory/session";


Session.create(this).withContext("knowledge", {

  description: "Searchable knowledge base",

  provider: new AgentSearchProvider(this),

});


TypeScript


import { AgentSearchProvider } from "agents/experimental/memory/session";


Session.create(this).withContext("knowledge", {

  description: "Searchable knowledge base",

  provider: new AgentSearchProvider(this),

});


在运行时添加和删除上下文

块可以在初始化后动态添加和删除:

JavaScript


// Add a new block (auto-wires to SQLite if no provider given)

await session.addContext("extension-notes", {

  description: "From extension X",

  maxTokens: 500,

});


// Remove it

session.removeContext("extension-notes");


// Rebuild the system prompt to reflect changes

await session.refreshSystemPrompt();


Explain Code

TypeScript


// Add a new block (auto-wires to SQLite if no provider given)

await session.addContext("extension-notes", {

  description: "From extension X",

  maxTokens: 500,

});


// Remove it

session.removeContext("extension-notes");


// Rebuild the system prompt to reflect changes

await session.refreshSystemPrompt();


Explain Code

注意

addContextremoveContext 不会自动更新冻结的系统提示。之后调用 refreshSystemPrompt()

读取和写入上下文

JavaScript


// Read a single block

const block = session.getContextBlock("memory");

// { label, description?, content, tokens, maxTokens?, writable, isSkill, isSearchable }


// Read all blocks

const blocks = session.getContextBlocks();


// Replace content entirely

await session.replaceContextBlock("memory", "User likes coffee.");


// Append content

await session.appendContextBlock("memory", "\nUser prefers dark roast.");


Explain Code

TypeScript


// Read a single block

const block = session.getContextBlock("memory");

// { label, description?, content, tokens, maxTokens?, writable, isSkill, isSearchable }


// Read all blocks

const blocks = session.getContextBlocks();


// Replace content entirely

await session.replaceContextBlock("memory", "User likes coffee.");


// Append content

await session.appendContextBlock("memory", "\nUser prefers dark roast.");


Explain Code

系统提示

系统提示由所有上下文块构建,带有标题和元数据:


══════════════════════════════════════════════

SOUL (Identity) [readonly]

══════════════════════════════════════════════

You are a helpful assistant.


══════════════════════════════════════════════

MEMORY (Learned facts) [45% — 495/1100 tokens]

══════════════════════════════════════════════

User likes coffee.

User prefers dark roast.


Explain Code

JavaScript


// Freeze — first call renders and persists; subsequent calls return cached value

const prompt = await session.freezeSystemPrompt();


// Refresh — re-render from current block state and persist

const updated = await session.refreshSystemPrompt();


TypeScript


// Freeze — first call renders and persists; subsequent calls return cached value

const prompt = await session.freezeSystemPrompt();


// Refresh — re-render from current block state and persist

const updated = await session.refreshSystemPrompt();


启用 withCachedPrompt() 时,冻结的提示在 Durable Object 休眠和驱逐后仍然保留。

AI 工具

Session 自动基于上下文块的 provider 类型生成工具。将这些工具与你自己的工具一起传递给你的 LLM。

JavaScript


const tools = await session.tools();

const allTools = { ...tools, ...myTools };


TypeScript


const tools = await session.tools();

const allTools = { ...tools, ...myTools };


set_context

当存在任何可写块时生成。写入到普通块、skill 块(键控)或搜索块(键控)。强制执行 maxTokens 限制。

load_context

当存在任何 skill 块时生成。从 SkillProvider 按 key 加载完整内容。

unload_context

load_context 一起生成。通过卸载先前加载的 skill 来释放上下文空间。skill 仍然可用于重新加载。

search_context

当存在任何搜索块时生成。在可搜索的上下文块内进行全文搜索。返回按 FTS5 排名的前 10 个结果。

仅在 SessionManager 上可用。在所有会话中搜索。

压缩

压缩通过总结较旧的消息以将会话保持在 token 限制内。原始消息保留在 SQLite 中 — 摘要是在读取时应用的非破坏性叠加。

设置

JavaScript


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,

    }),

  )

  .compactAfter(100_000);


Explain Code

TypeScript


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,

    }),

  )

  .compactAfter(100_000);


Explain Code

压缩工作原理

  1. 保护头部 — 前 N 条消息从不被压缩(默认 3)
  2. 保护尾部 — 从末尾向后走,累积 token 直到预算(默认 20K token)
  3. 对齐边界 — 移动边界以避免拆分工具调用/结果对
  4. 总结中间 — 使用结构化格式(Topic, Key Points, Current State, Open Items)将中间部分发送给 LLM
  5. 存储叠加 — 保存在 assistant_compactions 表中,以 fromMessageIdtoMessageId 为键
  6. 迭代 — 在后续压缩中,现有摘要被传递给 LLM 进行更新而不是替换

调用 getHistory() 时,压缩叠加会被透明地应用 — 被压缩的范围被替换为合成的摘要消息。

手动压缩

JavaScript


const result = await session.compact();


// Or manage overlays directly

session.addCompaction("Summary of messages 1-50", "msg-1", "msg-50");

const overlays = session.getCompactions();


TypeScript


const result = await session.compact();


// Or manage overlays directly

session.addCompaction("Summary of messages 1-50", "msg-1", "msg-50");

const overlays = session.getCompactions();


自动压缩

设置了 .compactAfter(threshold) 时,appendMessage() 会在每次写入后检查估计的 token 数。如果超过阈值,compact() 会被自动调用。自动压缩失败是非致命的 — 消息已经被保存。

注意

Token 估算是启发式的(不是 tiktoken)。它使用 max(chars/4, words*1.3),每条消息有 4 token 的开销。Tiktoken 会增加 80–120 MB 的堆开销,这超过了 Cloudflare Workers 128 MB 的限制。

SessionManager

SessionManager 是单个 Durable Object 中多个命名会话的注册表。它提供生命周期管理、便捷方法和跨会话搜索。

创建 SessionManager

JavaScript


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");


TypeScript


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");


上下文块、提示缓存和压缩设置会传播到通过 manager 创建的所有会话。Provider 键自动按会话 ID 命名空间化。

Builder 方法

方法描述
SessionManager.create(agent)静态工厂。
.withContext(label, options?)为所有会话添加上下文块模板。
.withCachedPrompt(provider?)为所有会话启用提示持久化。
.onCompaction(fn)为所有会话注册压缩函数。
.compactAfter(tokenThreshold)所有会话的自动压缩阈值。
.withSearchableHistory(label)添加跨会话可搜索的历史块。模型可以从任何会话搜索过去的会话。

会话生命周期

JavaScript


// Create a new session

const info = manager.create("My Chat");


// Create with metadata

const info2 = manager.create("My Chat", {

  parentSessionId: "parent-id",

  model: "claude-sonnet-4-20250514",

  source: "web",

});


// Get session metadata (null if not found)

const session = manager.get(sessionId);


// List all sessions (ordered by updated_at DESC)

const sessions = manager.list();


// Rename

manager.rename(sessionId, "New Name");


// Delete (clears messages too)

manager.delete(sessionId);


Explain Code

TypeScript


// Create a new session

const info = manager.create("My Chat");


// Create with metadata

const info2 = manager.create("My Chat", {

  parentSessionId: "parent-id",

  model: "claude-sonnet-4-20250514",

  source: "web",

});


// Get session metadata (null if not found)

const session = manager.get(sessionId);


// List all sessions (ordered by updated_at DESC)

const sessions = manager.list();


// Rename

manager.rename(sessionId, "New Name");


// Delete (clears messages too)

manager.delete(sessionId);


Explain Code

访问会话

JavaScript


// Get or create the Session instance for an ID

// Lazy — creates on first access, caches for subsequent calls

const session = manager.getSession(sessionId);


TypeScript


// Get or create the Session instance for an ID

// Lazy — creates on first access, caches for subsequent calls

const session = manager.getSession(sessionId);


消息便捷方法

这些方法委托给底层 Session,并更新会话的 updated_at 时间戳:

JavaScript


// 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 = manager.getHistory(sessionId, leafId);


// Message count

const count = manager.getMessageCount(sessionId);


// Clear messages

manager.clearMessages(sessionId);


// Delete specific messages

manager.deleteMessages(sessionId, ["msg-1"]);


Explain Code

TypeScript


// 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 = manager.getHistory(sessionId, leafId);


// Message count

const count = manager.getMessageCount(sessionId);


// Clear messages

manager.clearMessages(sessionId);


// Delete specific messages

manager.deleteMessages(sessionId, ["msg-1"]);


Explain Code

Forking

在特定消息处 fork 会话 — 将历史复制到该点的新会话:

JavaScript


const forked = await manager.fork(sessionId, atMessageId, "Forked Chat");

// forked.parent_session_id === sessionId


TypeScript


const forked = await manager.fork(sessionId, atMessageId, "Forked Chat");

// forked.parent_session_id === sessionId


用量跟踪

JavaScript


manager.addUsage(sessionId, inputTokens, outputTokens, cost);


TypeScript


manager.addUsage(sessionId, inputTokens, outputTokens, cost);


跨会话搜索

JavaScript


// Search across all sessions (FTS5)

const results = manager.search("deployment Friday", { limit: 20 });


// Get tools for the model (includes session_search)

const tools = manager.tools();


TypeScript


// Search across all sessions (FTS5)

const results = manager.search("deployment Friday", { limit: 20 });


// Get tools for the model (includes session_search)

const tools = manager.tools();


自定义 provider

实现四个 provider 接口中的任一个,即可插入你自己的存储:

JavaScript


// Read-only context

const myProvider = {

  get: async () => "Static content here",

};


// Writable context (enables set_context tool)

const myWritable = {

  get: async () => fetchFromMyDB(),

  set: async (content) => saveToMyDB(content),

};


// Skill provider (enables load_context tool)

const mySkills = {

  get: async () => "- api-ref: API Reference\n- guide: User Guide",

  load: async (key) => fetchDocument(key),

  set: async (key, content, description) =>

    saveDocument(key, content, description),

};


// Search provider (enables search_context tool)

const mySearch = {

  get: async () => "42 entries indexed",

  search: async (query) => searchMyIndex(query),

  set: async (key, content) => indexContent(key, content),

};


Explain Code

TypeScript


// Read-only context

const myProvider: ContextProvider = {

  get: async () => "Static content here",

};


// Writable context (enables set_context tool)

const myWritable: WritableContextProvider = {

  get: async () => fetchFromMyDB(),

  set: async (content) => saveToMyDB(content),

};


// Skill provider (enables load_context tool)

const mySkills: SkillProvider = {

  get: async () => "- api-ref: API Reference\n- guide: User Guide",

  load: async (key) => fetchDocument(key),

  set: async (key, content, description) => saveDocument(key, content, description),

};


// Search provider (enables search_context tool)

const mySearch: SearchProvider = {

  get: async () => "42 entries indexed",

  search: async (query) => searchMyIndex(query),

  set: async (key, content) => indexContent(key, content),

};


Explain Code

你也可以实现 SessionProvider 来完全替换 SQLite 存储:

JavaScript


const myStorage = {

  getMessage(id) {

    /* ... */

  },

  getHistory(leafId) {

    /* ... */

  },

  getLatestLeaf() {

    /* ... */

  },

  getBranches(messageId) {

    /* ... */

  },

  getPathLength(leafId) {

    /* ... */

  },

  appendMessage(message, parentId) {

    /* ... */

  },

  updateMessage(message) {

    /* ... */

  },

  deleteMessages(messageIds) {

    /* ... */

  },

  clearMessages() {

    /* ... */

  },

  addCompaction(summary, fromId, toId) {

    /* ... */

  },

  getCompactions() {

    /* ... */

  },

  searchMessages(query, limit) {

    /* ... */

  },

};


Explain Code

TypeScript


const myStorage: SessionProvider = {

  getMessage(id) { /* ... */ },

  getHistory(leafId?) { /* ... */ },

  getLatestLeaf() { /* ... */ },

  getBranches(messageId) { /* ... */ },

  getPathLength(leafId?) { /* ... */ },

  appendMessage(message, parentId?) { /* ... */ },

  updateMessage(message) { /* ... */ },

  deleteMessages(messageIds) { /* ... */ },

  clearMessages() { /* ... */ },

  addCompaction(summary, fromId, toId) { /* ... */ },

  getCompactions() { /* ... */ },

  searchMessages(query, limit) { /* ... */ },

};


Explain Code

存储表

所有存储都在 Durable Object SQLite 中。表在首次使用时延迟创建。

用途
assistant_messages树状结构消息,带有 id、session_id、parent_id、role、content (JSON)、created_at
assistant_compactions压缩叠加,带有 summary、from_message_id、to_message_id
assistant_fts用于消息搜索的 FTS5 虚表(porter stemming, unicode 分词)
assistant_sessions会话注册表(仅 SessionManager),带有 name、parent_session_id、model、source、token/cost 计数器
cf_agents_context_blocks持久化的上下文块存储(AgentContextProvider)
cf_agents_search_entries / cf_agents_search_fts可搜索的上下文条目和 FTS5 索引(AgentSearchProvider)

鸣谢

相关

  • Think — 通过 configureSession() 使用 Session 进行对话存储的有主张的 chat agent
  • Chat agentsAIChatAgent 带有自己的消息持久化层
  • Store and sync statesetState() 用于更简单的键值持久化