INTEGRITY Документация

Сеансы

Session API предоставляет агентам постоянное хранилище переписки с сообщениями, организованными в древовидную структуру (по аналогии с Pi ↗), контекстные блоки, компактизацию, полнотекстовый поиск и инструменты, управляемые ИИ. По умолчанию используется Durable Object SQLite. Для приложений, которым нужен общий доступ к базе данных, аналитика или запросы между несколькими Durable Object, доступно также внешнее хранилище Postgres.

быстрый старт

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 = await this.session.getHistory();
		const system = await this.session.freezeSystemPrompt();
		const tools = await this.session.tools();
		// Pass history, system prompt, and tools to your LLM
	}
}
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 = await this.session.getHistory();
		const system = await this.session.freezeSystemPrompt();
		const tools = await this.session.tools();
		// Pass history, system prompt, and tools to your LLM
	}
}

Создание сеанса

Используйте Session.create(agent) с цепочечным билдером. Провайдеры контекста без явного provider опции автоматически подключаются к SQLite.

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

Прямой конструктор

Для полного контроля над провайдерами:

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." } },
	],
});
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." } },
	],
});

Методы Builder

Все методы builder возвращают this для цепочки вызовов. Порядок не важен: провайдеры разрешаются лениво при первом использовании.

Метод Описание
Session.create(agent) Статическая фабрика. agent это любой объект с sql метод с tagged template (ваш Agent или Durable Object).
.forSession(sessionId) Присвойте этой сессии пространство имён по ID. Требуется для изоляции нескольких сессий, если не используется SessionManager.
.withContext(label, options?) Добавьте блок контекста. Обратитесь к Блоки контекста.
.withCachedPrompt(provider?) Включите сохранение системного промпта. Промпт фиксируется при первом использовании и переживает гибернацию и вытеснение.
.onCompaction(fn) Зарегистрируйте функцию компактизации. См. Компактизация.
.compactAfter(tokenThreshold, options?) Автоматическая компактизация, когда оценочное количество токенов превышает порог. Требует .onCompaction(). Передайте { tokenCounter } чтобы управлять тем, как измеряется порог.
.onCompactionError(handler) Обрабатывайте ошибки автоматической компактизации. Сбои обработчика подавляются, поэтому запись сообщений остаётся некритичной.

Сообщения

Сообщения используют SessionMessage тип: минимальная форма с id, role, parts, и необязательный createdAt. AI SDK UIMessage структурно совместим и может быть передан напрямую. Сессия хранит сообщения в древовидной структуре через parent_id, что позволяет ветвить разговоры.

// 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)
await session.updateMessage(message);

// Delete specific messages
await session.deleteMessages(["msg-1", "msg-2"]);

// Clear all messages and skill state
await session.clearMessages();
// 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)
await session.updateMessage(message);

// Delete specific messages
await session.deleteMessages(["msg-1", "msg-2"]);

// Clear all messages and skill state
await session.clearMessages();

Чтение истории

// Linear history from root to the latest leaf
const messages = await session.getHistory();

// History to a specific leaf (for branching)
const branch = await session.getHistory(leafId);

// Get a single message
const msg = await session.getMessage("msg-1");

// Get the newest message
const latest = await session.getLatestLeaf();

// Count messages in path
const count = await session.getPathLength();
// Linear history from root to the latest leaf
const messages = await session.getHistory();

// History to a specific leaf (for branching)
const branch = await session.getHistory(leafId);

// Get a single message
const msg = await session.getMessage("msg-1");

// Get the newest message
const latest = await session.getLatestLeaf();

// Count messages in path
const count = await session.getPathLength();

Ветвление

Сообщения образуют дерево. Когда вы appendMessage с parentId у которого уже есть дочерние элементы, вы создаёте ветку. Используйте getBranches() чтобы получить все дочерние сообщения, ответвляющиеся от заданной точки:

// Get all child messages that branch from messageId
const branches = await session.getBranches(messageId);
// Get all child messages that branch from messageId
const branches = await session.getBranches(messageId);

Это лежит в основе таких функций, как повторная генерация ответа: передайте ID сообщения пользователя, чтобы получить и исходный, и повторно сгенерированный ответ. getHistory(leafId) проходит по выбранному пути.

Полнотекстовый поиск по истории разговора с помощью SQLite FTS5:

const results = await session.search("deployment Friday", { limit: 10 });
// Returns: Array<{ id, role, content, createdAt? }>
const results = await session.search("deployment Friday", { limit: 10 });
// Returns: Array<{ id, role, content, createdAt? }>

Сессии на основе SQLite используют FTS5 со стеммингом Porter и юникод-токенизацией. Сессии на основе Postgres используют полнотекстовый индекс Postgres, предоставляемый провайдером. search() выбрасывает исключение, если провайдер сессий не поддерживает search.

Блоки контекста

Блоки контекста представляют собой постоянные секции в формате ключ-значение, встраиваемые в системный промпт. У каждого блока есть метка, необязательный описание, а также провайдер который определяет его поведение.

Типы провайдеров

Существует четыре типа провайдеров, которые определяются через duck-typing:

Провайдер Интерфейс Поведение ИИ-инструмент
ContextProvider get() Блок только для чтения в системном промпте ,
WritableContextProvider get() + set() Доступно для записи через AI set_context
SkillProvider get() + load() + set?() Документы с ключами, доступные по запросу. get() возвращает список метаданных; load(key) загружает полное содержимое. load_context, unload_context, set_context
SearchProvider get() + search() + set?() Записи с возможностью полнотекстового поиска. get() возвращает сводку; search(query) использует FTS5. search_context, set_context

Встроенные провайдеры

AgentContextProvider : доступный для записи контекст на базе SQLite. Используется по умолчанию, если builder применяется без явно указанного provider.

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

new AgentContextProvider(this, "memory");
import { AgentContextProvider } from "agents/experimental/memory/session";

new AgentContextProvider(this, "memory");

R2SkillProvider : Бакет Cloudflare R2 для загрузки документов по требованию. Skills перечислены в системном промпте как метаданные; модель загружает полное содержимое по требованию через load_context.

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

Session.create(this).withContext("skills", {
	provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" }),
});
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.

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

Session.create(this).withContext("knowledge", {
	description: "Searchable knowledge base",
	provider: new AgentSearchProvider(this),
});
import { AgentSearchProvider } from "agents/experimental/memory/session";

Session.create(this).withContext("knowledge", {
	description: "Searchable knowledge base",
	provider: new AgentSearchProvider(this),
});

Добавление и удаление контекста во время выполнения

Блоки можно динамически добавлять и удалять после инициализации:

// 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();
// 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();

Чтение и запись контекста

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

Системный промпт

Системный промпт собирается из всех блоков контекста с заголовками и метаданными:

══════════════════════════════════════════════
SOUL (Identity) [readonly]
══════════════════════════════════════════════
You are a helpful assistant.

══════════════════════════════════════════════
MEMORY (Learned facts) [45% — 495/1100 tokens]
══════════════════════════════════════════════
User likes coffee.
User prefers dark roast.
// 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();
// 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();

Замороженный промпт сохраняется при гибернации и вытеснении Durable Object, когда withCachedPrompt() включён.

ИИ-инструменты

Session автоматически генерирует инструменты на основе типов провайдеров ваших контекстных блоков. Передавайте их в LLM вместе с собственными инструментами.

const tools = await session.tools();
const allTools = { ...tools, ...myTools };
const tools = await session.tools();
const allTools = { ...tools, ...myTools };

set_context

Создаётся, когда существует хотя бы один доступный для записи блок. Выполняет запись в обычные блоки, блоки skill (с ключом) или блоки search (с ключом). Обеспечивает соблюдение maxTokens лимиты.

load_context

Создаётся, когда существует хотя бы один блок skill. Загружает полное содержимое по ключу из SkillProvider.

unload_context

Создаётся вместе с load_context. Освобождает место в контексте, выгружая ранее загруженный навык. Навык остаётся доступным для повторной загрузки.

search_context

Создаётся, когда существует хотя бы один блок search. Полнотекстовый поиск в контекстном блоке с возможностью поиска. Возвращает 10 лучших результатов по рейтингу FTS5.

Доступно на SessionManager только. Выполняет поиск по всем сессиям.

Компактизация

Компактизация резюмирует более старые сообщения, чтобы уместить диалог в лимит токенов. Исходные сообщения сохраняются в SQLite: сводка представляет собой неразрушающий слой, который накладывается поверх данных при чтении.

Настройка

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

Как работает компактизация

  1. Защита начала : первые N сообщений никогда не сжимаются (по умолчанию 3)
  2. Защита конца : проходит назад от конца, накапливая токены до заданного бюджета (по умолчанию 20 тысяч токенов)
  3. Выравнивание границ : сдвигает границы, чтобы не разбивать пары вызов и результат инструмента
  4. Суммаризация середины : отправляет среднюю часть в LLM в структурированном формате (тема, ключевые пункты, текущее состояние, открытые пункты)
  5. Наложение хранилища : сохранено в assistant_compactions таблица с ключом по fromMessageId и toMessageId
  6. Итеративный : при последующих компактизациях существующая сводка передаётся LLM для обновления, а не замены

Когда getHistory() вызывается, наложения сжатия применяются прозрачно: сжатый диапазон заменяется синтетическим сообщением-сводкой.

Ручная компактизация

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

Автокомпактизация

Когда .compactAfter(threshold) задан, appendMessage() проверяет оценочное количество токенов после каждой записи. Если оно превышает порог, compact() вызывается автоматически. Сбой автоматического сжатия не критичен: сообщение уже сохранено.

По умолчанию оценка включает сохранённые части сообщений и замороженный системный промпт, управляемый Session, поэтому блоки контекста и кэшированные промпты, управляемые Session учитываются в пороге. Сюда не входят специфичные для фреймворка дополнения промпта или сериализация схемы инструмента, которые происходят вне Session.

Подсчёт токенов включает два решения:

Обычно достаточно настроить один счётчик. .compactAfter() счётчик также передаётся в createCompactFunction: обход границ (через CompactContext) если явно не задан createCompactFunction({ tokenCounter }) задан, поэтому один и тот же счётчик определяет и «нужно ли компактизировать?», и «что компактизировать?».

Используйте собственный счётчик, если у вас есть данные об использовании от модели или собственный токенизатор:

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

SessionManager

SessionManager это реестр для нескольких именованных сессий в пределах одного Durable Object. Он предоставляет управление жизненным циклом, удобные методы и поиск между сессиями.

Создание 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");

Настройки блоков контекста, кеширования промптов и компактизации распространяются на все сессии, созданные через менеджер. Ключи провайдера автоматически получают отдельное пространство имён по идентификатору сессии.

Методы Builder

Метод Описание
SessionManager.create(agent) Статическая фабрика.
.withContext(label, options?) Добавьте шаблон блока контекста для всех сессий.
.withCachedPrompt(provider?) Включите сохранение промпта для всех сессий.
.onCompaction(fn) Зарегистрируйте функцию компактизации для всех сессий.
.compactAfter(tokenThreshold, options?) Порог автокомпактизации для всех сессий. Поддерживает те же tokenCounter опцию как Session.
.onCompactionError(handler) Обрабатывайте ошибки автоматической компактизации для управляемых сессий.
.withSearchableHistory(label) Добавьте блок истории с поиском между сессиями. Модель может искать прошлые разговоры из любой сессии.

Жизненный цикл сессии

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

Доступ к сессиям

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

Вспомогательные методы для сообщений

Эти методы делегируют вызовы базовой Session и обновляют у сессии updated_at временная метка:

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

Разветвление

Разветвление сессии на конкретном сообщении: копирует историю до этой точки в новую сессию:

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

Вспомогательные функции компактизации

// 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() создаёт новую сессию со сводным сообщением вместо наложения на месте. Исходная сессия помечается end_reason: "compaction".

Отслеживание использования

await manager.addUsage(sessionId, inputTokens, outputTokens, cost);
await manager.addUsage(sessionId, inputTokens, outputTokens, cost);
// 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();

Пользовательские провайдеры

Реализуйте любой из четырёх интерфейсов провайдера, чтобы подключить собственное хранилище:

// 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),
};
// 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),
};

Вы также можете реализовать SessionProvider чтобы полностью заменить хранилище SQLite:

const myStorage = {
	async getMessage(id) {
		/* ... */
	},
	async getHistory(leafId) {
		/* ... */
	},
	async getLatestLeaf() {
		/* ... */
	},
	async getBranches(messageId) {
		/* ... */
	},
	async getPathLength(leafId) {
		/* ... */
	},
	async appendMessage(message, parentId) {
		/* ... */
	},
	async updateMessage(message) {
		/* ... */
	},
	async deleteMessages(messageIds) {
		/* ... */
	},
	async clearMessages() {
		/* ... */
	},
	async addCompaction(summary, fromId, toId) {
		/* ... */
	},
	async getCompactions() {
		/* ... */
	},
	async searchMessages(query, limit) {
		/* ... */
	},
};
const myStorage: SessionProvider = {
	async getMessage(id) {
		/* ... */
	},
	async getHistory(leafId?) {
		/* ... */
	},
	async getLatestLeaf() {
		/* ... */
	},
	async getBranches(messageId) {
		/* ... */
	},
	async getPathLength(leafId?) {
		/* ... */
	},
	async appendMessage(message, parentId?) {
		/* ... */
	},
	async updateMessage(message) {
		/* ... */
	},
	async deleteMessages(messageIds) {
		/* ... */
	},
	async clearMessages() {
		/* ... */
	},
	async addCompaction(summary, fromId, toId) {
		/* ... */
	},
	async getCompactions() {
		/* ... */
	},
	async searchMessages(query, limit) {
		/* ... */
	},
};

Провайдеры Postgres

По умолчанию хранилище Session использует Durable Object SQLite и создаёт таблицы лениво. Если вам нужны данные сессии во внешней базе данных Postgres для межагентных запросов, аналитики или общего хранилища, используйте PostgresSessionProvider, PostgresContextProvider, а также PostgresSearchProvider.

Эти провайдеры работают с Postgres-совместимыми базами данных через Hyperdrive для пула соединений.

1. Создайте конфигурацию Hyperdrive

Создайте конфигурацию Hyperdrive для своей базы данных Postgres:

npx wrangler hyperdrive create my-session-db \
	--connection-string="postgresql://user:password@host:port/dbname"

Затем добавьте биндинг Hyperdrive в wrangler.jsonc:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "<your-hyperdrive-id>"
    }
  ],
  "placement": {
    "mode": "smart"
  }
}
compatibility_flags = ["nodejs_compat"]

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<your-hyperdrive-id>"

[placement]
mode = "smart"

Если вам известен регион вашей базы данных, настройте размещение ближе к ней, чтобы снизить задержку запросов.

2. Создайте таблицы

У пользователя Postgres может не быть прав на создание таблиц во время выполнения. Выполните схему один раз в консоли своей базы данных:

CREATE TABLE IF NOT EXISTS assistant_messages (
	id TEXT NOT NULL,
	session_id TEXT NOT NULL DEFAULT '',
	parent_id TEXT,
	role TEXT NOT NULL,
	content TEXT NOT NULL,
	text_content TEXT NOT NULL DEFAULT '',
	created_at TIMESTAMPTZ DEFAULT NOW(),
	content_tsv TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', text_content)) STORED,
	PRIMARY KEY (session_id, id)
);

CREATE INDEX IF NOT EXISTS idx_assistant_msg_parent
	ON assistant_messages (parent_id);
CREATE INDEX IF NOT EXISTS idx_assistant_msg_session
	ON assistant_messages (session_id);
CREATE INDEX IF NOT EXISTS idx_assistant_msg_fts
	ON assistant_messages USING GIN (content_tsv);

CREATE TABLE IF NOT EXISTS assistant_compactions (
	id TEXT PRIMARY KEY,
	session_id TEXT NOT NULL DEFAULT '',
	summary TEXT NOT NULL,
	from_message_id TEXT NOT NULL,
	to_message_id TEXT NOT NULL,
	created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS cf_agents_context_blocks (
	label TEXT PRIMARY KEY,
	content TEXT NOT NULL,
	updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS cf_agents_search_entries (
	label TEXT NOT NULL,
	key TEXT NOT NULL,
	content TEXT NOT NULL,
	content_tsv TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
	created_at TIMESTAMPTZ DEFAULT NOW(),
	updated_at TIMESTAMPTZ DEFAULT NOW(),
	PRIMARY KEY (label, key)
);

CREATE INDEX IF NOT EXISTS idx_search_entries_fts
	ON cf_agents_search_entries USING GIN (content_tsv);

3. Настройте подключение

Установка pg, затем создайте клиент на основе строки подключения Hyperdrive и передайте его провайдерам Postgres:

npm i pg
import { Agent } from "agents";
import {
	PostgresContextProvider,
	PostgresSearchProvider,
	PostgresSessionProvider,
	Session,
} from "agents/experimental/memory/session";
import { Client } from "pg";

export class MyAgent extends Agent {
	session;
	pgClient;

	async onStart() {
		const client = new Client({
			connectionString: this.env.HYPERDRIVE.connectionString,
		});
		await client.connect();
		this.pgClient = client;

		const sessionId = this.ctx.id.toString();
		this.session = Session.create(
			new PostgresSessionProvider(client, sessionId),
		)
			.withContext("soul", {
				provider: {
					get: async () => "You are a helpful assistant.",
				},
			})
			.withContext("memory", {
				description: "Short facts",
				maxTokens: 1100,
				provider: new PostgresContextProvider(client, `memory_${sessionId}`),
			})
			.withContext("knowledge", {
				description: "Searchable knowledge base",
				provider: new PostgresSearchProvider(client),
			})
			.withCachedPrompt(
				new PostgresContextProvider(client, `_prompt_${sessionId}`),
			);
	}
}
import { Agent } from "agents";
import {
	PostgresContextProvider,
	PostgresSearchProvider,
	PostgresSessionProvider,
	Session,
} from "agents/experimental/memory/session";
import { Client } from "pg";

export class MyAgent extends Agent<Env> {
	private session?: Session;
	private pgClient?: Client;

	async onStart(): Promise<void> {
		const client = new Client({
			connectionString: this.env.HYPERDRIVE.connectionString,
		});
		await client.connect();
		this.pgClient = client;

		const sessionId = this.ctx.id.toString();
		this.session = Session.create(
			new PostgresSessionProvider(client, sessionId),
		)
			.withContext("soul", {
				provider: {
					get: async () => "You are a helpful assistant.",
				},
			})
			.withContext("memory", {
				description: "Short facts",
				maxTokens: 1100,
				provider: new PostgresContextProvider(client, `memory_${sessionId}`),
			})
			.withContext("knowledge", {
				description: "Searchable knowledge base",
				provider: new PostgresSearchProvider(client),
			})
			.withCachedPrompt(
				new PostgresContextProvider(client, `_prompt_${sessionId}`),
			);
	}
}

Различия в поведении

Когда Session.create() получает SessionProvider вместо провайдера на основе SQLite, автоматическое подключение SQLite пропускается:

Жизненный цикл системного промпта

freezeSystemPrompt() возвращает закешированный prompt из хранилища. При первом вызове он загружает context blocks от providers, формирует prompt и сохраняет его. Последующие вызовы возвращают сохранённое значение без повторного формирования.

Используйте refreshSystemPrompt() чтобы принудительно перезагрузить блоки контекста, повторно отрендерить промпт и обновить сохранённое значение.

Таблицы хранилища

По умолчанию хранилище использует Durable Object SQLite, а таблицы создаются лениво при первом использовании. Сессии на основе Postgres используют внешние таблицы, описанные в разделе о провайдерах Postgres.

Таблица Назначение
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, юникод-токенизация)
assistant_sessions Реестр Session (только для SessionManager) с name, parent_session_id, model, source, счётчики токенов и стоимости
cf_agents_context_blocks Постоянное хранилище блоков контекста (AgentContextProvider)
cf_agents_search_entries / cf_agents_search_fts Записи контекста с возможностью поиска и индекс FTS5 (AgentSearchProvider)

Благодарности