跳转到内容
搜索文档

快速入门

最后更新 查看 MarkdownAgent 设置

为 Agent 添加 Agent Memory,以便其可以跨对话召回持久上下文。

本指南使用 Agents SDK 及其 Session API,将内存召回作为模型可调用的工具提供。如果您使用其他 Agent 框架,这也同样适用:使用 ingest()remember() 存储内存,通过您的 Agent 工具之一公开 recall(),并使用系统提示词告诉模型何时搜索内存。

先决条件

  1. 注册 Cloudflare 账户
  2. 安装 Node.js

Node.js 版本管理器

使用 Voltanvm 等 Node 版本管理器,以避免权限问题并切换 Node.js 版本。本指南后续将介绍的 Wrangler 需要 Node 版本 16.17.0 或更高。

您还需要获得 Agent Memory 的使用权限。

Agent Memory 的工作原理

当模型需要相关内存来回答或行动时,使用 recall()。当您拥有对话消息,并希望 Agent Memory 自动提取持久内存时,使用 ingest()。当您的 Agent 已经知道要存储的确切内存时,使用 remember()

不要在每次模型轮次后都调用 ingest()。相反,应在用户处于空闲状态、对话被压缩时,或在其他自然的检查点进行批量摄取。

1. 创建项目

创建一个 Worker 项目:

npm create cloudflare@latest -- memory-agent

进行设置时,请选择以下选项:

  • 对于 What would you like to start with?,选择 Hello World example
  • 对于 Which template would you like to use?,选择 Worker only
  • 对于 Which language do you want to use?,选择 TypeScript
  • 对于 Do you want to use git for version control?,选择 Yes
  • 对于 Do you want to deploy your application?,选择 No(部署前我们还会做一些修改)。

进入项目目录:

cd memory-agent

安装本指南中使用的依赖:

npm i agents ai workers-ai-provider

2. 创建命名空间

命名空间 (Namespace) 限制了您的应用程序的内存配置文件的范围。使用 Wrangler 创建一个:

npx wrangler agent-memory namespace create my-agent

您将在 Worker 绑定中使用该命名空间名称 my-agent

3. 配置绑定

在您的 Wrangler 配置中添加 agent_memory 绑定。如果您使用的是 Agents SDK,还要注册您的 Agent 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-17",
  "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-17"
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,添加一个可搜索的上下文提供者 (Context Provider)。Session 会将该提供者的 search() 方法转换为模型的 search_context 工具。

创建 src/server.ts 并添加召回设置:

src/server.jsjs
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.tsts
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. 从对话中提取内存

接下来,为您的 Agent 提供一种添加持久内存的方式。在聊天 Agent 中,通常的做法是将对话存储在 Session 中,然后当用户空闲后调用 ingest()

修改 agents 导入并添加 AI SDK 导入。保留第 4 步中的 Session 导入。

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

import { createWorkersAI } from "workers-ai-provider";
src/server.tsts
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.jsjs
const MEMORY_INGEST_DELAY_SECONDS = 10;
src/server.tsts
const MEMORY_INGEST_DELAY_SECONDS = 10;

然后,使用以下结构更新 ChatAgent。注释标记了保留第 4 步中的 Session 设置的位置。

src/server.jsjs
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.tsts
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 都会映射到拥有其自己 Session 历史记录的独立 Agent 实例。

src/server.jsjs
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.tsts
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 会在用户进入空闲状态后处理对话,而不是在每个 Agent 轮次后都进行处理。

runIngest() 使用了游标 (cursor),因此每个批次仅包含尚未摄取的消息。sessionId 在共享的内存配置文件内按对话对内存进行分组。

本演示在多个对话中跨用了一个 Agent Memory 配置文件 demo-user。在生产环境中,请选择与您的应用范围(如用户、团队、租户或组织)相匹配的配置文件名称。

您还可以从 Session 压缩钩子 (compaction hook) 中调用摄取逻辑。重要的约束是在自然检查点以批处理方式摄取,而不是在每个 Agent 轮次后都进行。在生产环境中,请选择与您应用程序的用户体验相匹配的摄取延迟。

6. (可选)根据需要存储显式内存

对大多数应用而言,自动摄取已足够。如果您希望模型立即存储特定内存,请添加一个服务器端工具,其执行函数会调用 remember()

当 Agent 已经知道要存储的准确内存时,可以使用此方法。例如,在用户说“记住我更喜欢简洁的答案”后,模型可能会调用 rememberMemory 工具。

如果模型能够调用内存写入工具,请添加系统提示词指令,定义什么值得被记住以及何时请求确认。对于许多 Agent 而言,自动对话摄取比直接给模型一个写入内存的工具更简单、更安全。

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 需要额外的时间来提取、分类并索引这些内存,之后这些内容才可供召回。

在另一个对话中提出一个依赖于持久内存的问题。此请求使用不同的 Session 历史记录,但使用同一个 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 如何提取、存储和检索持久内存。

Workers API

从 Workers 中使用 `ingest()`、`remember()`、`recall()` 和 `getSummary()`。

这篇文档对您有帮助吗?