entity-memory-management について
エージェントのセッションをまたいで、型付きエンティティを抽出・保存・検索するスキルです。キャンペーンや指標、決定、好み、繰り返し発生する問題を重要度評価と文脈に応じた検索で扱います。
主な機能
- 01型付きエンティティの抽出
- 02重要度の評価
- 03Cloudflare KVへの保存
- 04文脈に応じた検索
- 05エンティティの更新と矛盾処理
活用シーン
- 01セッション間でキャンペーン情報を引き継ぐ
- 02過去の決定と理由を参照する
- 03ユーザーの好みや反復する問題を追跡する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ itallstartedwithaidea/ agent-skills --skill entity-memory-managementインストール後、Claude Code で/entity-memory-managementを入力して読み込まれたか確認します。
詳細
エンティティメモリ管理
googleadsagent.ai™ による Agent Skills™ の一部です
説明
エンティティメモリ管理とは、エージェントのセッションをまたいで名前付きエンティティを体系的に抽出、永続化、取得することで、長期的なコンテキスト認識を実現し、一時的な会話を永続的な関係へと変えるものです。一般的なメモリシステムがセッションの要約を保存するのに対し、エンティティメモリはより細かなレベルで機能します。具体的なキャンペーン、指標、意思決定、ユーザーの悩み、好みを、個別にクエリできるオブジェクトとして追跡します。この精度により、エージェントは単に「最近、入札の変更について話し合った」ではなく、「キャンペーン Alpha は先週火曜日に目標 CPA に切り替えた」と思い出せます。
このスキルは、googleadsagent.ai™ の Buddy™ を支える本番環境向けエンティティメモリアーキテクチャです。エンティティメモリは、数百のユーザーセッションにわたり、何千もの名前付きエンティティを追跡します。このシステムは、定義済みの種類のエンティティを管理します。キャンペーン(戦略、予算、パフォーマンスの推移を含む)、指標(特定の KPI とその履歴値)、意思決定(行われた変更とその理由)、悩み(ユーザーが繰り返し不満を示している問題)などです。各エンティティは抽出され、型が設定され、重要度がスコア化されたうえで Cloudflare KV に保存され、将来のセッションで状況に応じて取得されます。
その結果、エージェントは会話を再生するのではなく、永続し進化する構造化された型付き知識を通じて、ユーザーのことを本当に「覚えている」ようになります。これにより、エージェントが一から始めるのではなく、以前のコンテキストを踏まえて応答するという、根本的に異なるユーザー体験が生まれます。
使用する場面
- エージェントが、セッションをまたいだ継続性を期待するリピーターユーザーに対応する場合
- ドメインエンティティ(キャンペーン、アカウント、プロジェクト)を長期にわたって追跡する必要がある場合
- 過去のセッションでの意思決定を、将来の推奨事項に反映する必要がある場合
- ユーザーの好みや悩みを、再説明なしで保持する必要がある場合
- セッションをまたいだ傾向(指標の改善、繰り返し発生する問題)を検出する必要がある場合
- コンプライアンスや監査の要件により、エージェントとユーザーのやり取りを構造化して記録する必要がある場合
仕組み
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 に書き込まれます。新しいセッションの開始時に、コンテキストビルダーは現在のユーザーのエンティティインデックスを照会し、新しいタスクとの関連性に基づいて絞り込み、最も関連性の高いエンティティをエージェントのコンテキストに挿入します。
実装
エンティティタイプのスキーマ:
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 | ✅ 拡張機能 | ⚠️ カスタム | ⚠️ カスタム |
| コンテキスト注入 | ✅ CLAUDE.md | ✅ Skills | ✅ Instructions | ✅ System prompts |
| インデックス管理 | ✅ 完全対応 | ✅ 完全対応 | ✅ 完全対応 | ✅ 完全対応 |
関連スキル
- メモリの永続化 - エンティティの永続化に必要なストレージと検索レイヤーを提供します
- ナレッジベースの注入 - ドメイン知識のパターンとともにエンティティのコンテキストを組み立てて注入します
- 継続的な学習 - 抽出されたエンティティをパターンマイニングに活用し、スキルの自動導出に役立てます
- Google Ads 監査 - キャンペーン、指標、判断に関するエンティティを使い、監査結果をセッション間で追跡します
キーワード
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