关于 entity-memory-management
用于跨 Agent 会话提取、保存和检索分类实体的技能,适合需要持续跟踪广告活动、指标、决策、偏好和反复问题的 Agent。它通过重要性评分与相关性检索构建结构化上下文。
主要功能
- 01按类型提取实体
- 02重要性评分
- 03使用 Cloudflare KV 持久化
- 04按上下文检索
- 05更新实体并处理矛盾
适用场景
- 01跨会话延续广告活动上下文
- 02回忆过往决策及其原因
- 03跟踪用户偏好和反复问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ itallstartedwithaidea/ agent-skills --skill entity-memory-management安装后在 Claude Code 中输入/entity-memory-management确认已加载。
详细介绍
实体记忆管理
属于 Agent Skills™,由 googleadsagent.ai™ 提供
描述
实体记忆管理是对跨 Agent 会话的命名实体进行系统化提取、持久化和检索,使 Agent 能够长期感知上下文,将转瞬即逝的对话转化为持久关系。通用记忆系统存储会话摘要,而实体记忆则在更细粒度上运作——将具体的广告系列、指标、决策、用户痛点和偏好作为独立且可查询的对象进行追踪。这种精确度让 Agent 能记住“广告系列 Alpha 上周二改用了目标 CPA”,而不只是“我们最近讨论了出价调整”。
这项技能是 Buddy™ 的生产级实体记忆架构,由 googleadsagent.ai™ 提供。实体记忆会在数百个用户会话中追踪数千个命名实体。系统维护预定义类型的实体——广告系列(及其策略、预算和效果趋势)、指标(具体 KPI 及其历史值)、决策(所做的更改及其理由)和痛点(用户反复表达不满的问题)。每个实体都会经过提取、类型标注和重要性评分,存储在 Cloudflare KV 中,并在未来的会话中根据上下文检索。
最终,Agent 真正“记住”了用户——并非通过回放对话,而是依靠持久保存并不断演变的结构化类型知识。这带来了截然不同的用户体验:Agent 能基于先前的上下文继续工作,而不是从头开始。
适用场景
- Agent 服务于希望跨会话保持连续体验的回访用户
- 需要长期追踪领域实体(广告系列、账户、项目)
- 过去会话中的决策应为未来的建议提供参考
- 用户偏好和痛点应持久保存,无需用户重复说明
- 需要检测跨会话的趋势(指标改善、反复出现的问题)
- 合规或审计要求需要对 Agent 与用户的交互进行结构化记录
工作原理
graph TD
A[Conversation Stream] --> B[Entity Extractor]
B --> C{Entity Type}
C -->|Campaign| D[Campaign Schema]
C -->|Metric| E[Metric Schema]
C -->|Decision| F[Decision Schema]
C -->|Pain Point| G[Pain Point Schema]
C -->|Preference| H[Preference Schema]
D --> I[Importance Scorer]
E --> I
F --> I
G --> I
H --> I
I --> J[KV Store Writer]
J --> K[Entity Index]
L[New Session Start] --> M[Context Builder]
M --> N[Entity Retriever]
N --> K
K --> O[Relevance Filter]
O --> P[Context Injection]
实体提取流水线会通过类型感知提取器处理对话流,识别命名实体并将其归类到预定义架构中。每个提取出的实体都会根据对话中的强调程度、可操作性和新颖性获得重要性评分(新信息的评分高于重复信息)。经过评分的实体会使用结构化键写入 Cloudflare KV,以便高效检索。每次新会话开始时,上下文构建器都会查询当前用户的实体索引,根据与新任务的相关性进行筛选,并将最相关的实体注入 Agent 的上下文。
实现
实体类型架构:
interface BaseEntity {
id: string;
userId: string;
type: EntityType;
name: string;
content: string;
importance: number;
createdAt: number;
updatedAt: number;
sessionId: string;
version: number;
}
type EntityType = "campaign" | "metric" | "decision" | "pain_point" | "preference";
interface CampaignEntity extends BaseEntity {
type: "campaign";
metadata: {
campaignId?: string;
strategy?: string;
budget?: number;
status?: string;
lastPerformance?: Record<string, number>;
};
}
interface DecisionEntity extends BaseEntity {
type: "decision";
metadata: {
action: string;
rationale: string;
affectedEntities: string[];
reversible: boolean;
outcome?: string;
};
}
interface PainPointEntity extends BaseEntity {
type: "pain_point";
metadata: {
severity: "low" | "medium" | "high";
frequency: number;
resolved: boolean;
relatedCampaigns: string[];
};
}
interface PreferenceEntity extends BaseEntity {
type: "preference";
metadata: {
category: "communication" | "analysis" | "risk" | "reporting";
value: string;
confidence: number;
};
}
实体提取提示词:
EXTRACTION_PROMPT = """Analyze this conversation and extract structured entities.
Entity types and what to capture:
- CAMPAIGN: Campaign names, strategies, budgets, performance data, status changes
- METRIC: Specific KPI values mentioned (CPA, ROAS, CTR, etc.) with context
- DECISION: Choices made or agreed upon, with rationale and affected entities
- PAIN_POINT: Frustrations, recurring issues, or problems the user expressed
- PREFERENCE: Stated preferences for communication style, risk tolerance, analysis depth
Conversation:
{conversation}
Extract entities as JSON:
{{
"entities": [
{{
"type": "campaign|metric|decision|pain_point|preference",
"name": "short identifier",
"content": "full description of the entity",
"importance": 0.0-1.0,
"metadata": {{}}
}}
]
}}
Rules:
- Only extract entities with clear evidence in the conversation
- Importance 0.9+: explicit decisions, critical metrics, stated preferences
- Importance 0.5-0.8: mentioned campaigns, context metrics, implied preferences
- Importance <0.5: background information, passing mentions
- Merge with existing entities when updating rather than creating duplicates"""
使用 Cloudflare KV 的实体存储:
class EntityStore {
constructor(private kv: KVNamespace) {}
async upsert(entity: BaseEntity): Promise<void> {
const key = `entity:${entity.userId}:${entity.type}:${entity.id}`;
const existing = await this.kv.get<BaseEntity>(key, "json");
if (existing) {
entity.version = existing.version + 1;
entity.createdAt = existing.createdAt;
if (entity.importance < existing.importance) {
entity.importance = (entity.importance + existing.importance) / 2;
}
}
await this.kv.put(key, JSON.stringify(entity), {
expirationTtl: 60 * 60 * 24 * 180,
metadata: {
type: entity.type,
importance: entity.importance,
updatedAt: entity.updatedAt,
},
});
await this.updateIndex(entity);
}
private async updateIndex(entity: BaseEntity): Promise<void> {
const indexKey = `index:${entity.userId}:entities`;
const index = await this.kv.get<EntityIndex>(indexKey, "json") || { entries: [] };
const existingIdx = index.entries.findIndex(
(e) => e.id === entity.id && e.type === entity.type
);
const entry = {
id: entity.id,
type: entity.type,
name: entity.name,
importance: entity.importance,
updatedAt: entity.updatedAt,
};
if (existingIdx >= 0) {
index.entries[existingIdx] = entry;
} else {
index.entries.push(entry);
}
await this.kv.put(indexKey, JSON.stringify(index));
}
async retrieveForContext(userId: string, taskContext: string, limit = 20): Promise<BaseEntity[]> {
const indexKey = `index:${userId}:entities`;
const index = await this.kv.get<EntityIndex>(indexKey, "json");
if (!index) return [];
const ranked = index.entries
.map((entry) => ({
...entry,
score: this.contextRelevance(entry, taskContext) * 0.6 + entry.importance * 0.3 + this.recencyScore(entry.updatedAt) * 0.1,
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
const entities: BaseEntity[] = [];
for (const entry of ranked) {
const key = `entity:${userId}:${entry.type}:${entry.id}`;
const entity = await this.kv.get<BaseEntity>(key, "json");
if (entity) entities.push(entity);
}
return entities;
}
private contextRelevance(entry: { name: string; type: string }, context: string): number {
const contextLower = context.toLowerCase();
const nameWords = entry.name.toLowerCase().split(/\s+/);
const matches = nameWords.filter((w) => contextLower.includes(w)).length;
return Math.min(1, matches / Math.max(nameWords.length, 1));
}
private recencyScore(timestamp: number): number {
const ageMs = Date.now() - timestamp;
const ageDays = ageMs / (1000 * 60 * 60 * 24);
return Math.max(0, 1 - ageDays / 180);
}
}
根据实体组装上下文:
def assemble_entity_context(entities: list[dict], max_tokens: int = 4000) -> str:
grouped = {}
for entity in entities:
grouped.setdefault(entity["type"], []).append(entity)
type_labels = {
"campaign": "Known Campaigns",
"metric": "Historical Metrics",
"decision": "Prior Decisions",
"pain_point": "Known Pain Points",
"preference": "User Preferences",
}
sections = ["<entity_memory>"]
token_count = 0
for entity_type in ["preference", "pain_point", "decision", "campaign", "metric"]:
items = grouped.get(entity_type, [])
if not items:
continue
label = type_labels.get(entity_type, entity_type.title())
section = f"\n### {label}\n"
for item in items[:5]:
entry = f"- **{item['name']}**: {item['content']}\n"
entry_tokens = len(entry.split()) * 1.3
if token_count + entry_tokens > max_tokens:
break
section += entry
token_count += entry_tokens
sections.append(section)
sections.append("</entity_memory>")
return "\n".join(sections)
最佳实践
- 预先定义实体类型 — 使用固定分类体系(campaign、metric、decision、pain_point、preference),比开放式实体识别能产生更一致的提取结果。
- 执行更新插入,而非重复创建 — 同一实体在新会话中出现时,应更新现有记录,而不是新建记录;保留版本历史以供审计。
- 在提取时评估重要性 — 模型掌握完整对话上下文时,重要性评分最准确;不要等到检索时再评分。
- 优先注入偏好和痛点 — 这些实体类型对用户体验的影响最大;只要有,就始终注入。
- 按类型设置实体 TTL — 偏好可能保留 180 天,而具体指标可能在 30 天后过期;应让 TTL 与实体的有效期限相匹配。
- 按上下文检索,而非穷举检索 — 只注入与当前任务相关的实体;完整倾倒所有实体会浪费上下文窗口容量。
- 检测实体矛盾 — 当新提取的内容与已存储实体冲突时(例如策略发生变化),更新已存储的实体并标记变更。
平台兼容性
| 功能 | Claude Code | Cursor | Codex | Gemini CLI |
|---|---|---|---|---|
| 实体提取 | ✅ 完整支持 | ✅ 完整支持 | ✅ 完整支持 | ✅ 完整支持 |
| KV 存储 | ✅ 任意 KV/文件 | ✅ 任意 KV/文件 | ✅ 任意 KV/文件 | ✅ 任意 KV/文件 |
| 会话钩子 | ✅ Stop hooks | ✅ Extensions | ⚠️ Custom | ⚠️ Custom |
| 上下文注入 | ✅ CLAUDE.md | ✅ Skills | ✅ Instructions | ✅ System prompts |
| 索引管理 | ✅ 完整支持 | ✅ 完整支持 | ✅ 完整支持 | ✅ 完整支持 |
相关技能
- 记忆持久化 - 提供实体持久化所需的底层存储和检索层
- 知识库注入 - 汇总实体上下文,并与领域知识模式一同注入
- 持续学习 - 提取的实体可用于挖掘模式,自动推导技能
- Google Ads 审计 - Campaign、metric 和 decision 实体可跨会话跟踪审计发现
关键词
entity-memory、named-entities、cross-session-continuity、cloudflare-kv、entity-extraction、entity-types、importance-scoring、contextual-retrieval、user-preferences、agent-skills
© 2026 googleadsagent.ai™ | Agent Skills™ | MIT License