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

Начало работы

Добавьте Agent Memory к агенту, чтобы он мог находить долговременный контекст в разных разговорах.

В этом руководстве используется Agents SDK и его Session API чтобы предоставить вызов памяти в виде инструмента, доступного модели. Тот же подход применим, если вы используете другой фреймворк для агентов: сохраняйте записи памяти с помощью ingest() или remember(), предоставьте recall() через один из инструментов вашего агента и используйте системный промпт, чтобы указать модели, когда искать в памяти.

Предварительные требования

  1. Зарегистрируйтесь для получения Аккаунт Cloudflare ↗.
  2. Установка Node.js ↗.

менеджер версий Node.js

Используйте менеджер версий Node, например Volta ↗ или nvm ↗ чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.

Вам также потребуется доступ к Agent Memory.

Как работает agent memory

Используйте recall() когда модели нужна релевантная запись памяти для ответа или действия. Используйте ingest() когда у вас есть сообщения разговора и вы хотите, чтобы Agent Memory автоматически извлекал устойчивые записи памяти. Используйте remember() когда ваш агент уже точно знает, какую запись памяти нужно сохранить.

Не вызывайте ingest() после каждого хода модели. Вместо этого выполняйте пакетную загрузку после того, как пользователь бездействует, при сжатии разговора или в другой естественной контрольной точке.

1. Создайте проект

Создайте проект Worker:

npm create cloudflare@latest -- memory-agent

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

Перейдите в каталог проекта:

cd memory-agent

Установите зависимости, используемые в этом руководстве:

npm i agents ai workers-ai-provider

2. Создайте пространство имён

A пространство имён определяет область профилей памяти для вашего приложения. Создайте его с помощью Wrangler:

npx wrangler agent-memory namespace create my-agent

Вы будете использовать имя пространства имён, my-agent, в привязке вашего Worker.

3. Настройте привязки

Добавьте agent_memory привязку в конфигурацию Wrangler. Если вы используете Agents SDK, также зарегистрируйте Durable Object вашего агента.

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "memory-agent",
  "main": "src/server.ts",
  // Set this to today's date
  "compatibility_date": "2026-08-28",
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "ai": {
    "binding": "AI"
  },
  "agent_memory": [
    {
      "binding": "MEMORY",
      "namespace": "my-agent"
    }
  ],
  "durable_objects": {
    "bindings": [
      {
        "name": "ChatAgent",
        "class_name": "ChatAgent"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "ChatAgent"
      ]
    }
  ]
}
name = "memory-agent"
main = "src/server.ts"
# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = ["nodejs_compat"]

[ai]
binding = "AI"

[[agent_memory]]
binding = "MEMORY"
namespace = "my-agent"

[[durable_objects.bindings]]
name = "ChatAgent"
class_name = "ChatAgent"

[[migrations]]
tag = "v1"
new_sqlite_classes = ["ChatAgent"]

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

npx wrangler types

4. Добавьте вызов памяти в виде инструмента

Модель не может использовать память только потому, что в вашем приложении есть привязка памяти. Нужно предоставить доступ к извлечению через инструмент и указать модели, когда его вызывать.

С Agents SDK Session API, добавьте провайдер контекста с возможностью поиска. Session берёт search() метод провайдера и превращает его в search_context инструмент для модели.

Создайте src/server.ts и добавьте настройку вызова памяти:

src/server.js
import { Agent, routeAgentRequest } from "agents";
import { Session } from "agents/experimental/memory/session";

const INSTRUCTIONS = "You are a helpful assistant.";

const MEMORY_CONTEXT = `Long-term memory is available through the search_context tool.

MEMORY POLICY
- Search memory with search_context when the user asks what you know or remember about them.
- Search memory when the request depends on prior sessions, preferences, project state, conventions, decisions, or long-running tasks.
- Phrase memory searches as concise topics, not questions.
- Do not search memory to repeat something the user just said in the current conversation.
- When search_context returns results, always incorporate them into your response. The results are real memories from previous conversations.
- Treat recalled memories as helpful context, not guaranteed truth. If a memory is important for an irreversible action, confirm with the user.`;

const MEMORY_PROFILE_NAME = "demo-user";

export class ChatAgent extends Agent {
	initialState = { cursor: 0, nextIngestAt: null };

	session = Session.create(this)
		.withContext("instructions", {
			provider: { get: async () => INSTRUCTIONS },
		})
		.withContext("memory", {
			description:
				"Searchable durable memory: facts, events, instructions, and tasks from prior conversations.",
			provider: {
				get: async () => MEMORY_CONTEXT,
				search: async (query) => {
					const profile = await this.env.MEMORY.getProfile(MEMORY_PROFILE_NAME);
					const { answer } = await profile.recall(query, {
						responseLength: "short",
					});
					return answer || "No relevant memories found.";
				},
			},
		})
		.withCachedPrompt();
}

export default {
	async fetch(request, env) {
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
src/server.ts
import { Agent, routeAgentRequest } from "agents";
import { Session } from "agents/experimental/memory/session";

const INSTRUCTIONS = "You are a helpful assistant.";

const MEMORY_CONTEXT = `Long-term memory is available through the search_context tool.

MEMORY POLICY
- Search memory with search_context when the user asks what you know or remember about them.
- Search memory when the request depends on prior sessions, preferences, project state, conventions, decisions, or long-running tasks.
- Phrase memory searches as concise topics, not questions.
- Do not search memory to repeat something the user just said in the current conversation.
- When search_context returns results, always incorporate them into your response. The results are real memories from previous conversations.
- Treat recalled memories as helpful context, not guaranteed truth. If a memory is important for an irreversible action, confirm with the user.`;

const MEMORY_PROFILE_NAME = "demo-user";

type ChatAgentState = {
	cursor: number;
	nextIngestAt: number | null;
};

export class ChatAgent extends Agent<Env, ChatAgentState> {
	initialState: ChatAgentState = { cursor: 0, nextIngestAt: null };

	session = Session.create(this)
		.withContext("instructions", {
			provider: { get: async () => INSTRUCTIONS },
		})
		.withContext("memory", {
			description:
				"Searchable durable memory: facts, events, instructions, and tasks from prior conversations.",
			provider: {
				get: async () => MEMORY_CONTEXT,
				search: async (query: string) => {
					const profile = await this.env.MEMORY.getProfile(MEMORY_PROFILE_NAME);
					const { answer } = await profile.recall(query, {
						responseLength: "short",
					});
					return answer || "No relevant memories found.";
				},
			},
		})
		.withCachedPrompt();
}

export default {
	async fetch(request: Request, env: Env) {
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

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

5. Извлеките записи памяти из разговора

Далее дайте агенту возможность добавлять устойчивые воспоминания. В чат-агенте обычный путь такой: сохранить разговор в Session, а затем вызвать ingest() после того, как пользователь становится неактивным.

Измените agents импорт и добавьте импорты AI SDK. Сохраните Session импорт из шага 4.

src/server.js
import { Agent, getAgentByName, routeAgentRequest } from "agents";
import { convertToModelMessages, generateText, stepCountIs } from "ai";

import { createWorkersAI } from "workers-ai-provider";
src/server.ts
import { Agent, getAgentByName, routeAgentRequest } from "agents";
import { convertToModelMessages, generateText, stepCountIs } from "ai";
import type { UIMessage } from "ai";
import { createWorkersAI } from "workers-ai-provider";

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

src/server.js
const MEMORY_INGEST_DELAY_SECONDS = 10;
src/server.ts
const MEMORY_INGEST_DELAY_SECONDS = 10;

Затем обновите ChatAgent со следующей структурой. Комментарий отмечает место, где нужно сохранить настройку Session из шага 4.

src/server.js
export class ChatAgent extends Agent {
	initialState = { cursor: 0, nextIngestAt: null };

	// Keep the `session = Session.create(this)` setup from step 4 here.

	async chat(message) {
		const userMessage = {
			id: `user-${crypto.randomUUID()}`,
			role: "user",
			parts: [{ type: "text", text: message }],
		};
		await this.session.appendMessage(userMessage);

		await this.scheduleIngest();

		const workersai = createWorkersAI({ binding: this.env.AI });
		const result = await generateText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			system: await this.session.freezeSystemPrompt(),
			messages: await convertToModelMessages(await this.session.getHistory()),
			tools: await this.session.tools(),
			stopWhen: stepCountIs(5),
		});

		const assistantMessage = {
			id: `assistant-${crypto.randomUUID()}`,
			role: "assistant",
			parts: [{ type: "text", text: result.text }],
		};
		await this.session.appendMessage(assistantMessage);

		return result.text;
	}

	async ingestScheduledMemory() {
		await this.runIngest();
	}

	async scheduleIngest() {
		await this.cancelPendingIngest();
		await this.schedule(
			MEMORY_INGEST_DELAY_SECONDS,
			"ingestScheduledMemory",
			{},
		);
		this.setState({
			...this.state,
			nextIngestAt: Date.now() + MEMORY_INGEST_DELAY_SECONDS * 1000,
		});
	}

	async cancelPendingIngest() {
		const pending = await this.listSchedules();
		for (const schedule of pending) {
			if (schedule.callback === "ingestScheduledMemory") {
				await this.cancelSchedule(schedule.id);
			}
		}
	}

	async runIngest() {
		const history = await this.session.getHistory();
		const messages = history
			.slice(this.state.cursor)
			.filter(
				(message) => message.role === "user" || message.role === "assistant",
			)
			.map((message) => ({
				role: message.role,
				content: message.parts
					.map((part) => (part.type === "text" ? part.text : ""))
					.filter(Boolean)
					.join("\n\n"),
			}))
			.filter((message) => message.content);

		if (messages.length === 0) {
			this.setState({ ...this.state, nextIngestAt: null });
			return { ingested: 0 };
		}

		const profile = await this.env.MEMORY.getProfile(MEMORY_PROFILE_NAME);
		await profile.ingest(messages, { sessionId: this.name });

		this.setState({
			...this.state,
			cursor: history.length,
			nextIngestAt: null,
		});

		return { ingested: messages.length };
	}
}
src/server.ts
export class ChatAgent extends Agent<Env, ChatAgentState> {
	initialState: ChatAgentState = { cursor: 0, nextIngestAt: null };

	// Keep the `session = Session.create(this)` setup from step 4 here.

	async chat(message: string): Promise<string> {
		const userMessage: UIMessage = {
			id: `user-${crypto.randomUUID()}`,
			role: "user",
			parts: [{ type: "text", text: message }],
		};
		await this.session.appendMessage(userMessage);

		await this.scheduleIngest();

		const workersai = createWorkersAI({ binding: this.env.AI });
		const result = await generateText({
			model: workersai("@cf/zai-org/glm-4.7-flash"),
			system: await this.session.freezeSystemPrompt(),
			messages: await convertToModelMessages(
				(await this.session.getHistory()) as UIMessage[],
			),
			tools: await this.session.tools(),
			stopWhen: stepCountIs(5),
		});

		const assistantMessage: UIMessage = {
			id: `assistant-${crypto.randomUUID()}`,
			role: "assistant",
			parts: [{ type: "text", text: result.text }],
		};
		await this.session.appendMessage(assistantMessage);

		return result.text;
	}

	async ingestScheduledMemory() {
		await this.runIngest();
	}

	private async scheduleIngest() {
		await this.cancelPendingIngest();
		await this.schedule(
			MEMORY_INGEST_DELAY_SECONDS,
			"ingestScheduledMemory",
			{},
		);
		this.setState({
			...this.state,
			nextIngestAt: Date.now() + MEMORY_INGEST_DELAY_SECONDS * 1000,
		});
	}

	private async cancelPendingIngest() {
		const pending = await this.listSchedules();
		for (const schedule of pending) {
			if (schedule.callback === "ingestScheduledMemory") {
				await this.cancelSchedule(schedule.id);
			}
		}
	}

	private async runIngest(): Promise<{ ingested: number }> {
		const history = (await this.session.getHistory()) as UIMessage[];
		const messages = history
			.slice(this.state.cursor)
			.filter(
				(message) => message.role === "user" || message.role === "assistant",
			)
			.map((message) => ({
				role: message.role,
				content: message.parts
					.map((part) => (part.type === "text" ? part.text : ""))
					.filter(Boolean)
					.join("\n\n"),
			}))
			.filter((message) => message.content);

		if (messages.length === 0) {
			this.setState({ ...this.state, nextIngestAt: null });
			return { ingested: 0 };
		}

		const profile = await this.env.MEMORY.getProfile(MEMORY_PROFILE_NAME);
		await profile.ingest(messages, { sessionId: this.name });

		this.setState({
			...this.state,
			cursor: history.length,
			nextIngestAt: null,
		});

		return { ingested: messages.length };
	}
}

Замените экспорт по умолчанию небольшой тестовой конечной точкой. Каждый conversationId соответствует отдельному экземпляру Agent с собственной историей Session.

src/server.js
export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		if (request.method === "POST" && url.pathname === "/chat") {
			const { message, conversationId = "default" } = await request.json();

			if (!message) {
				return Response.json({ error: "Missing message" }, { status: 400 });
			}

			const agent = await getAgentByName(env.ChatAgent, conversationId);
			const response = await agent.chat(message);
			return Response.json({ response });
		}

		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
src/server.ts
export default {
	async fetch(request: Request, env: Env) {
		const url = new URL(request.url);

		if (request.method === "POST" && url.pathname === "/chat") {
			const { message, conversationId = "default" } =
				(await request.json()) as {
					message?: string;
					conversationId?: string;
				};
			if (!message) {
				return Response.json({ error: "Missing message" }, { status: 400 });
			}

			const agent = await getAgentByName(env.ChatAgent, conversationId);
			const response = await agent.chat(message);
			return Response.json({ response });
		}

		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

Путь приёма данных: scheduleIngest() и runIngest(). Каждое новое сообщение пользователя отменяет ранее запланированную загрузку и планирует новую, поэтому Agent Memory обрабатывает разговор после того, как пользователь становится неактивным, а не после каждого хода агента.

runIngest() использует курсор, поэтому каждая партия включает только сообщения, которые ещё не были загружены. sessionId группирует записи памяти по разговору внутри общего профиля памяти.

В этом демонстрационном примере используется один профиль Agent Memory, demo-user, в разных разговорах. В продакшене выбирайте имена профилей, соответствующие области вашего приложения, например пользователи, команды, тенанты или организации.

Вы также можете вызывать логику приёма данных из Session compaction hook. Важное ограничение: данные нужно принимать пакетами в естественных контрольных точках, а не после каждого хода агента. В рабочей среде выбирайте задержку приёма данных исходя из того, каким должен быть пользовательский опыт вашего приложения.

6. (Необязательно) Сохраняйте явные записи памяти при необходимости

Автоматического приёма достаточно для большинства приложений. Если нужно, чтобы модель сразу сохраняла конкретную запись, добавьте серверный инструмент, функция execute которого вызывает remember().

Используйте это, когда агент уже точно знает, какое воспоминание нужно сохранить. Например, модель может вызвать rememberMemory инструмент после того, как пользователь скажет: «Запомни, что я предпочитаю краткие ответы.»

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

7. Протестируйте приложение

Начните локальную разработку:

npx wrangler dev

Попросите первый разговор запомнить долговременное предпочтение:

curl -X POST "http://localhost:8787/chat" \
  -H "Content-Type: application/json" \
  -d '{"conversationId":"first-chat","message":"I prefer TypeScript examples and concise answers."}'

Подождите не менее 30 секунд, прежде чем отправлять следующий запрос. Код планирует приём данных через 10 секунд после перехода пользователя в состояние простоя, а затем Agent Memory требуется дополнительное время, чтобы извлечь, классифицировать и проиндексировать воспоминания, прежде чем они станут доступны для использования.

Задайте вопрос в другом разговоре, ответ на который зависит от долговременной памяти. В этом запросе используется другая история сессии, но тот же профиль Agent Memory.

curl -X POST "http://localhost:8787/chat" \
  -H "Content-Type: application/json" \
  -d '{"conversationId":"second-chat","message":"What do you know or remember about me and my preferences?"}'

Модель должна вызывать search_context, получает вызванные записи памяти от Agent Memory и использует этот контекст в своём ответе. Второй разговор не имеет общей истории Session с первым, поэтому любые сведения о предпочтениях пользователя поступают из Agent Memory.

Следующие шаги

Как работает Agent Memory

Узнайте, как Agent Memory извлекает, хранит и находит долговременные записи.

Пространства имён и профили

Проектируйте области действия памяти для пользователей, команд, арендаторов или организаций.

API Workers

Используйте `ingest()`, `remember()`, `recall()` и `getSummary()` из Workers.