AIFCC
記事一覧へ
agent-opsharness-designclaude-workflow

記憶を持つエージェントの設計原則

43158
## エージェントメモリの基礎から理解する:Pythonリストからマークダウンファイル、ベクター検索、グラフ・ベクターハイブリッドへ、そしてオープンソースの解決策まで LLMは設計上ステートレスです。全てのAPI呼び出しは新鮮に始まります。ChatGPTとチャットするときに感じる「メモリ」は、リクエストごとに会話履歴全体を再送信することで作られる錯覚です。 このトリックはカジュアルなチャットには機能します。本物のエージェントを構築しようとした途端に崩壊します。 メモリをスキップすると7つの失敗モードが発生します: - コンテキスト健忘症:エージェントがあなたがすでに提供した情報を尋ねる - ゼロパーソナライゼーション:全てのインタラクションが汎用的に感じられる - マルチステップタスクの失敗:中間状態がタスクの途中で静かに消える - 繰り返すミス:エピソード的な記憶がないので同じエラーが永遠に続く - 知識の蓄積なし:全てのセッションがゼロから始まる - ギャップからの幻覚:コンテキストがオーバーフローすると、モデルが発明する - アイデンティティの崩壊:継続性なし、信頼なし 明らかな反応は「より多くのコンテキストを投入する」ことです。128Kと200Kトークンウィンドウが全てを解決するはずのように感じられる理由です。 解決しません。 関連情報が長いコンテキストの真ん中にある場合、精度が30%以上低下します。これは「ロストインザミドル」効果として文書化されています。 コンテキストは共有予算です:システムプロンプト、取得されたドキュメント、会話履歴、出力が全て同じトークンのために争います。 生のコンテキスト長だけでは不十分です。メモリはプロンプトに多くのテキストを詰め込むことではありません。エージェントが記憶することを構造化することで、重要なものを見つけられるようにすることです。 ## 実際に役立つ認知科学のフレーム Lilian Wengの2023年の定式化がデフォルトのフレームワークになっています: エージェント = LLM + メモリ + 計画 + ツール使用。4つの同等の柱。 彼女の分類法は認知科学から借用しており、人間のメモリは3つのシステムに分かれます: - **感覚メモリ**:生の知覚入力をキャプチャし、一瞬保持。注意を払った部分だけが前に送られる。 - **ワーキングメモリ**:アクティブな思考が起きる場所。一度に約7±2の項目を保持(Millerの1956年の発見)。集中力を失うと内容が消える。 - **長期メモリ**:実用的な容量制限のない永続的なストレージ。検索がボトルネック:何百万ものものを保存でき、それでも必要な1つを思い出せない場合がある。 それぞれが現代のエージェントアーキテクチャのコンポーネントに直接マッピングされます。 長期メモリはさらに分かれます: - エピソード的:過去の具体的なイベント(「火曜日にPostgreSQLクラスターがダウンした」) - セマンティック:事実と概念(「PostgreSQLはリレーショナルデータベース」) - 手続き的:スキルとワークフロー(「ユーザーが払い戻しを求めるとき、まず購入日を確認」) エピソード的とセマンティックの橋渡しはメモリ統合です:繰り返す具体的なイベントが一般的な知識に蒸留される。「ユーザーは一貫してエグゼクティブサマリーを好む」と気づくエージェントは、それを再利用可能なルールに変えるべきです。統合なしでは、エージェントは学習する代わりに個々のイベントを再生します。 ## 最小限のエージェント、そして最初に何が壊れるか フレームワークを取り除くと、エージェントはループです:知覚、思考、行動。 ```python class Agent: """最小限のAIエージェント:知覚、思考、行動""" def __init__(self): self.client = anthropic.Anthropic() self.model = "claude-sonnet-4-20250514" def run(self, user_input: str) -> str: response = self.client.messages.create( model=self.model, max_tokens=1024, messages=[{"role": "user", "content": user_input}], ) return response.content[0].text ``` 「私はリンゴを4つ持っています」と言って、「1つ食べた、何個残っていますか?」と聞くと、どのリンゴのことか分かりません。各呼び出しは独立して存在します。 ## レイヤー1:Pythonリスト 最初に誰もが手を伸ばす修正: ```python class Agent: def __init__(self): self.client = anthropic.Anthropic() self.messages = [] # 「メモリ」全体はリスト def chat(self, user_input: str) -> str: self.messages.append({"role": "user", "content": user_input}) response = self.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=self.messages, # 毎回全履歴を送信 ) reply = response.content[0].text self.messages.append({"role": "assistant", "content": reply}) return reply ``` マルチターンが機能するようになります。リンゴの質問は正しく答えられます、完全な会話が全ての呼び出しで再送されるから。 2つの問題がすぐに現れます: - リストが無制限に成長します。ターン200あたりでコンテキストの上限に達し、最も古いメッセージが静かに落ちます。ターン1のユーザーの名前は昨日の何気ないジョークより前に消えます。優先順位付けなし、ただの厳密な時系列順。 - 全てがRAMに存在します。Pythonプロセスが終了した瞬間、エージェントはあなたが誰か分かりません。 ## レイヤー2:永続性のためのマークダウンファイル 次のステップはメモリをディスクに書き込むことです。マークダウンは自然な適合:人間が読める、Git対応、エージェントはプレーンテキストとして読み返せます。Claude Codeは CLAUDE.mdとMEMORY.mdファイルでまさにこのパターンを使います。 ```python class MarkdownMemoryAgent: def __init__(self): self.client = anthropic.Anthropic() self.history_file = Path("memory/conversation_history.md") self.facts_file = Path("memory/known_facts.md") def save_to_disk(self, role: str, content: str) -> None: with open(self.history_file, "a") as f: f.write(f"### {role} at {datetime.now().isoformat()} {content} ") def load_history(self) -> str: if self.history_file.exists(): return self.history_file.read_text() return "" def chat(self, user_input: str) -> str: self.save_to_disk("user", user_input) history = self.load_history() response = self.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=f"過去の会話: {history}", messages=[{"role": "user", "content": user_input}], ) reply = response.content[0].text self.save_to_disk("assistant", reply) return reply ``` 永続性が解決されます。スクリプトを再起動しても会話はディスクにあります。エージェントが時間をかけて抽出した別のファクトファイルを維持することもできます: ``` - ユーザーの名前はサラ - サラはAcme CorpのバックエンドチームのマネージャーI - Acme CorpはB2B SaaSの会社 - 現在、本番データベースを新しいAWSリージョンに移行中 ``` 4つのファクトでは完璧に機能します。3ヶ月後に進めてください。エージェントには2,000の抽出されたファクトと200の会話ログがあります。それは500K以上のトークンのマークダウンです。全部を読み込むことができなくなります。フラットファイルでの唯一のオプションはキーワード検索: ```python # ユーザーが「クラウド移行のステータスは?」と尋ねる grep("cloud migration", facts_file) # 戻り値: [] # ディスク上のファクトは「本番データベースを新しいAWSリージョンに移行中」と書かれています # 「クラウド移行」という言葉はどこにも現れません ``` キーワードは同義語、言い換え、ファクト間の接続を処理できません。 ## レイヤー3:ベクター検索とそれが当たる壁 埋め込みを追加します。マークダウンをチャンクして、チャンクを埋め込み、コサイン類似性で検索します。「データベース」は「PostgreSQL」にマッチするようになります、埋め込み空間で近くに存在するから。同義語の問題が消えます。 その後、新しい壁に当たります。ベクターDBのこれらの3つのファクトを考えてください: ``` - 「アリスはプロジェクトアトラスのテックリード」 - 「プロジェクトアトラスは主要なデータストアにPostgreSQLを使用」 - 「PostgreSQLクラスターは火曜日に停止を経験した」 ``` ユーザーが尋ねます:「アリスのプロジェクトは火曜日の停止の影響を受けましたか?」 クエリはアリスと火曜日の停止を言及しているので、ベクター検索は1番目と3番目のファクトを高くランク付けします。しかし重要な橋、「プロジェクトアトラスはPostgreSQLを使用」はアリスも火曜日も言及していません。それが接続するピースで、浮上しないものです。 各ファクトは埋め込み空間の孤立した点です。それらを結ぶ結合組織はベクターには見えません。 これはエッジケースではありません。現実世界の質問の通常の形状です。ビジネス知識は本質的に関係的です:人はチームに属し、チームはプロジェクトを所有し、プロジェクトはシステムに依存し、システムはインシデントを持ちます。 ## 機能マトリックス 各レイヤーは前の痛みを修正しますが、より深いものを明らかにします: | | Python List | Markdown | Vector | |---|---|---|---| | 持続性 | ❌ | ✅ | ✅ | | セマンティック検索 | ❌ | ❌ | ✅ | | リレーショナル推論 | ❌ | ❌ | ❌ | | スケール | ❌ | ❌ | △ | 永続性、セマンティック理解、リレーショナル推論を単一のメモリレイヤーに必要です。 ## Cognee:3つのストア、1つのエンジン、4つの呼び出し Cogneeはエージェントメモリのために構築されたオープンソースのナレッジエンジンです。ベクター検索をナレッジグラフとリレーショナルプロベナンスレイヤーと組み合わせて単一のシステムにしています。 APIサーフェス全体は4つの非同期呼び出しです: ```python import cognee await cognee.add("Your document here") # 何でも取り込む await cognee.cognify() # ナレッジグラフ + 埋め込みを構築 await cognee.memify() # メモリを自己改善 await cognee.search("Your query") # 推論で取得 ``` なぜ3つのストアで1つではないか? 各ストアは他のものでは捉えられないナレッジの次元をキャプチャします: - **リレーショナルストア** → プロベナンス:データがどこから来たか、いつ取り込まれたか、誰がアクセスできるか - **ベクターストア** → セマンティクス:コンテンツが何を意味するか、何に類似しているか - **グラフストア** → 関係:エンティティがどのように接続されているか、何が何を引き起こすか、誰が誰に報告するか デフォルトのスタックはSQLite + LanceDB + Kuzuで、完全に埋め込まれてファイルベース。`pip install cognee`とLLM APIキーで実行できます。Docker不要、外部サービス不要。 本番環境では、SQLiteをPostgresに、LanceDBAをQdrant/Pinecone/pgvectorに、KuzuをNeo4j/FalkorDB/Neptuneに交換。どちらの場合も同じ4つの呼び出しAPI。 ## cognifyが実際にすること `cognee.cognify()`は生のテキストを構造化された相互接続されたナレッジに変換するマルチステージパイプラインを実行します: - タイプとドメインによるドキュメント分類 - マルチテナントアクセス制御の権限確認 - 段落構造を尊重したチャンク抽出(固定サイズのカットではなく) - コンテンツハッシュによる自動重複排除を含むLLMでのエンティティと関係の抽出 - 効率的な取得のためのサマリー生成 - ベクターストア(埋め込み)とグラフストア(エッジ)への二重インデックス 重複排除ステップは聞こえる以上に重要です。同じエンティティが50のドキュメントにわたって現れる場合、Cogneeはそれを50の受信エッジを持つ単一のグラフノードにマージします。 ## Memify:学習するメモリ `memify()`はCogneeを全ての「取り込んでI検索する」ツールから分離するものです。グラフに対してRL感化された最適化パスを実行します: - 良い取得につながった有用なパスを強化する - 触れられていない古いノードを剪定する - 実際の使用に基づいてエッジの重みを自動調整する - 暗黙の関係を特定することで派生したファクトを追加する ## Cogneeメモリを持つ本物のエージェントを構築する ```python import cognee from cognee import SearchType class CogneeMemoryAgent: """グラフ・ベクターハイブリッド永続メモリを持つエージェント""" def __init__(self, session_id: str = "default"): self.llm_client = OpenAI() self.session_id = session_id async def ingest(self, text: str, dataset: str = "main"): await cognee.add(text, dataset) await cognee.cognify([dataset]) async def recall(self, query: str) -> str: results = await cognee.search( query_text=query, query_type=SearchType.GRAPH_COMPLETION, session_id=self.session_id, ) return results[0] if results else "" async def chat(self, user_input: str) -> str: context = await self.recall(user_input) messages = [ {"role": "system", "content": "あなたは役に立ちます。メモリコンテキストを使用してください。"}, {"role": "system", "content": f"メモリコンテキスト: {context}"}, {"role": "user", "content": user_input}, ] response = self.llm_client.chat.completions.create( model="gpt-4o-mini", messages=messages ) reply = response.choices[0].message.content await cognee.add( f"User: {user_input} Assistant: {reply}", "conversations" ) await cognee.cognify(["conversations"]) return reply ``` メモリサイクル:取り込み、抽出、保存、取得、応答、再保存。各ターンがナレッジグラフを豊かにし、インクリメンタル処理は新しいコンテンツのみのインデックス作成に支払います。 ## 実践的な道のり クエリがエンティティの境界を越える場合(「アリスのプロジェクトは火曜日の停止の影響を受けましたか?」)、グラフトラバーサルが必要です。 別個のベクター、グラフ、リレーショナルストアを自分で接続できます。このルートを選ぶチームは、それ自体の使用から学ばないメモリレイヤーのためのインフラに通常数週間を費やします。 Cogneeはそれを4つのAPI呼び出しに圧縮します。埋め込みデフォルトで数分で実行できます。スワップ可能なバックエンド(Postgres、Qdrant、Neo4j)でエージェントコードを変更せずに本番環境に移行できます。 知性は単なるストレージではなく、構造を必要とします。3つのストレージパラダイム(リレーショナル、ベクター、グラフ)は競合するオプションではありません。同じメモリシステムの補完的なレイヤーです。そのように扱うことが、ステートレスなLLMラッパーを実際に学習するものに変えます。 あなたのエージェントが明日覚えてほしいもので、今日忘れるものは何ですか?そこから始めましょう。 Cogneeをぜひチェックしてください(GitHub)。スターを付けて、次のエージェントに接続してみてください。 4つの非同期呼び出し、pip install、そして実行できます。 @akshay_pachaarで毎日AI、機械学習、バイブコーディングのベストプラクティスのチュートリアルとインサイトを共有しています。
原文を表示 / Show original
A first-principles walk through agent memory: from Python lists to markdown files to vector search to graph-vector hybrids, and finally, a clean, open-source solution for all of this. An LLM is stateless by design. Every API call starts fresh. The "memory" you feel when chatting with ChatGPT is an illusion created by re-sending the entire conversation history with every request. That trick works for casual chat. It falls apart the moment you try to build a real agent. Here are 7 failure modes show up the instant you skip memory: Context amnesia: the agent asks for information you already gave it Zero personalization: every interaction feels generic Multi-step task failure: intermediate state silently drops mid-task Repeated mistakes: no episodic recall means the same errors, forever No knowledge accumulation: every session starts from scratch Hallucination from gaps: when context overflows, the model invents Identity collapse: no continuity, no trust The obvious response is "throw more context at it." That's why 128K and 200K token windows feel like they should solve everything. They don't. Accuracy drops over 30% when relevant information sits in the middle of a long context. This is the well-documented "lost in the middle" effect. Context is a shared budget: system prompts, retrieved docs, conversation history, and output all fight for the same tokens. Even at 100K tokens, the absence of persistence, prioritization, and salience makes raw context length insufficient. Memory isn't about cramming more text into the prompt. It's about structuring what the agent remembers so it can find what matters. The cognitive science frame that actually helps Lilian Weng's 2023 formulation has become the default framework: Agent = LLM + Memory + Planning + Tool Use. The four co-equal pillars. Her taxonomy borrows from cognitive science, where human memory splits into three systems: Sensory memory captures raw perceptual input and holds it for a fraction of a second. Only the portions you pay attention to get passed forward. Working memory is where active thinking happens. It holds roughly 7±2 items at a time (Miller's 1956 finding). Lose focus, and the contents disappear. Long-term memory is durable storage with no practical capacity limit. Retrieval is the bottleneck: you can store millions of things and still fail to recall the one you need. Each maps directly to a component in modern agent architectures: Long-term memory itself splits further: Episodic: specific past events ("on Tuesday, the PostgreSQL cluster went down") Semantic: facts and concepts ("PostgreSQL is a relational database") Procedural: skills and workflows ("when a user asks for a refund, first check the purchase date") The bridge between episodic and semantic is memory consolidation: repeated specific events distilling into general knowledge. An agent that notices "users consistently prefer executive summaries" across dozens of interactions should turn that into a reusable rule. Without consolidation, your agent replays individual events rather than learning from them. The minimal agent, and what breaks first Strip away the frameworks and an agent is a loop: perceive, think, act. python class Agent: """Minimal AI agent: perceive, think, act""" def __init__(self): self.client = anthropic.Anthropic() self.model = "claude-sonnet-4-20250514" def run(self, user_input: str) -> str: response = self.client.messages.create( model=self.model, max_tokens=1024, messages=[{"role": "user", "content": user_input}], ) return response.content[0].text Tell it "I have 4 apples," then ask "I ate one, how many left?" and it has no idea what apples you're talking about. Each call exists in isolation. Layer 1: The Python list The first fix everyone reaches for: python class Agent: def __init__(self): self.client = anthropic.Anthropic() self.messages = [] # The entire "memory" is a list def chat(self, user_input: str) -> str: self.messages.append({"role": "user", "content": user_input}) response = self.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=self.messages, # Full history sent every time ) reply = response.content[0].text self.messages.append({"role": "assistant", "content": reply}) return reply Multi-turn works now. The apples question gets answered correctly because the full conversation re-ships with every call. Two problems show up fast: The list grows unbounded. Around turn 200, you hit the context ceiling and the oldest messages silently drop. The user's name from turn 1 disappears long before yesterday's throwaway joke. No prioritization, just strict chronological order. Everything lives in RAM. The moment the Python process ends, your agent has no idea who you are. Layer 2: Markdown files for persistence The next move is writing memory to disk. Markdown is a natural fit: human-readable, Git-friendly, and the agent can read it back as plain text. Claude Code uses exactly this pattern with CLAUDE.md and MEMORY.md files. python class MarkdownMemoryAgent: def __init__(self): self.client = anthropic.Anthropic() self.history_file = Path("memory/conversation_history.md") self.facts_file = Path("memory/known_facts.md") def save_to_disk(self, role: str, content: str) -> None: with open(self.history_file, "a") as f: f.write(f"### {role} at {datetime.now().isoformat()}\n{content}\n\n") def load_history(self) -> str: if self.history_file.exists(): return self.history_file.read_text() return "" def chat(self, user_input: str) -> str: self.save_to_disk("user", user_input) history = self.load_history() response = self.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=f"Previous conversation:\n{history}", messages=[{"role": "user", "content": user_input}], ) reply = response.content[0].text self.save_to_disk("assistant", reply) return reply Persistence is solved. Restart the script, and the conversation is still on disk. You could also maintain a separate facts file that the agent extracts over time: plaintext - User's name is Sarah - Sarah manages the backend team at Acme Corp - Acme Corp is a B2B SaaS company - Currently migrating production database to a new AWS region You can open the file in any editor, see exactly what the agent knows, and fix it by hand. Genuinely useful for prototyping. With 4 facts, this works perfectly. Load the entire file into context and the LLM handles any question about Sarah, her company, or her industry. Now fast-forward three months. Your agent has 2,000 extracted facts and 200 conversation logs. That's 500K+ tokens of markdown on disk, and your context window is 128K. You can no longer load everything. You need to selectively retrieve only the facts relevant to the current query. With flat files, your only option is keyword search: python # User asks: "What's the status of our cloud migration?" grep("cloud migration", facts_file) # Returns: [] # The fact on disk says "migrating production database to a new AWS region." # The words "cloud migration" appear nowhere. # User asks: "Which team is handling the database work?" grep("database team", facts_file) # Returns: [] # One fact says Sarah "manages the backend team." Another says the team # is "migrating production database." But no single line contains # both "database" and "team" together. At small scale, markdown files work. At real scale, they force keyword retrieval, and keywords can't handle synonyms, paraphrases, or connections across facts. The information is on disk. But you can't load all of it, and keyword search is too brittle to find the right pieces. If you've used OpenClaw, you've seen this play out. It stores memory as markdown checkpoint files, and over weeks of daily use, earlier facts quietly slip away as context accumulates and gets compacted. The storage is there. The retrieval isn't. Storage without intelligent retrieval is a library with no catalog. Layer 3: Vector search and the wall it hits Bolt on embeddings. Chunk your markdown, embed the chunks, search by cosine similarity. Now "database" matches "PostgreSQL" because their vectors live close together in embedding space. The synonym problem dissolves. Then you hit a new wall. Consider these three facts in your vector DB: plaintext - "Alice is the tech lead on Project Atlas" - "Project Atlas uses PostgreSQL for its primary datastore" - "The PostgreSQL cluster experienced an outage on Tuesday" User asks: "Was Alice's project affected by Tuesday's outage?" The query mentions Alice and Tuesday's outage, so vector search ranks the first and third facts high. But the critical bridge, "Project Atlas uses PostgreSQL," mentions neither Alice nor Tuesday. It's the connecting piece, and it's the one that won't surface. Each fact is an isolated point in embedding space. The connective tissue linking them is invisible to vectors. This isn't an edge case. It's the normal shape of real-world questions. Business knowledge is inherently relational: people belong to teams, teams own projects, projects depend on systems, systems have incidents. Any question that crosses two or more hops exceeds what flat vector retrieval can answer. The capability matrix Each layer fixes the previous pain but reveals a deeper one: You need persistence, semantic understanding, and relational reasoning in a single memory layer. Building this yourself means gluing together a vector database, a graph database, a relational store, an entity extractor, a deduplication pipeline, and an edge-weighting system. That's weeks of infrastructure work before you write a single line of agent logic. I've been using a solution that fills this gap cleanly. It's fully open-source, handles all three storage paradigms under one roof, and you can get it running in minutes. Let's talk about Cognee. Cognee: three stores, one engine, four calls Cognee is an open-source knowledge engine built for agent memory. It combines vector search with knowledge graphs and a relational provenance layer into a single system. The entire API surface is four async calls: python import cognee await cognee.add("Your document here") # Ingest anything await cognee.cognify() # Build knowledge graph + embeddings await cognee.memify() # Self-improve the memory await cognee.search("Your query") # Retrieve with reasoning Behind those four calls sits a three-store architecture. Why three stores and not one ? Each store captures a dimension of knowledge the others can't: Relational store → provenance: where data came from, when it was ingested, who has access Vector store → semantics: what content means, what it's similar to Graph store → relationships: how entities connect, what causes what, who reports to whom Flatten any of these and you lose information that matters for retrieval accuracy. The default stack is SQLite + LanceDB + Kuzu, entirely embedded and file-based. pip install cognee plus an LLM API key and you're running. No Docker, no external services. For production, swap SQLite for Postgres, LanceDB for Qdrant/Pinecone/pgvector, and Kuzu for Neo4j/FalkorDB/Neptune. Same four-call API either way. What cognify actually does? cognee.cognify() runs a multi-stage pipeline that converts raw text into structured, interconnected knowledge: Document classification by type and domain Permission checking for multi-tenant access control Chunk extraction that respects paragraph structure (not fixed-size cuts) Entity and relationship extraction via LLM, with automatic deduplication through content hashing Summary generation for efficient retrieval Dual indexing into the vector store (embeddings) and graph store (edges) The deduplication step matters more than it sounds. If the same entity shows up across 50 documents, Cognee merges it into a single graph node with 50 inbound edges. Your agent no longer sees "Alice" as 50 different strangers. And the pipeline is incremental by default: only new or updated files get reprocessed. Every graph node has a corresponding embedding. This dual representation is the core trick: enter through vectors (find semantically similar content) and exit through the graph (follow relationships to connected entities), or the reverse. That's what makes multi-hop queries work without sacrificing semantic search. Memify: memory that learns memify() is what separates Cognee from every "ingest and search" tool. It runs an RL-inspired optimization pass over the graph: Strengthening useful paths that led to good retrieval Pruning stale nodes that haven't been touched Auto-tuning edge weights based on real usage Adding derived facts by identifying implicit relationships A customer support agent's graph naturally strengthens paths through product docs and refund policies while letting rarely-queried HR edges decay. The graph develops its own sense of relevance over time. Fourteen retrieval modes Cognee ships 14 search modes. The ones you'll actually reach for: Building a real agent with Cognee memory Here's the complete pattern wiring Cognee into the perceive-think-act loop: python import cognee from cognee import SearchType class CogneeMemoryAgent: """Agent with graph-vector hybrid persistent memory.""" def __init__(self, session_id: str = "default"): self.llm_client = OpenAI() self.session_id = session_id async def ingest(self, text: str, dataset: str = "main"): await cognee.add(text, dataset) await cognee.cognify([dataset]) async def recall(self, query: str) -> str: results = await cognee.search( query_text=query, query_type=SearchType.GRAPH_COMPLETION, session_id=self.session_id, ) return results[0] if results else "" async def chat(self, user_input: str) -> str: context = await self.recall(user_input) messages = [ {"role": "system", "content": "You are helpful. Use memory context."}, {"role": "system", "content": f"Memory context:\n{context}"}, {"role": "user", "content": user_input}, ] response = self.llm_client.chat.completions.create( model="gpt-4o-mini", messages=messages ) reply = response.choices[0].message.content await cognee.add( f"User: {user_input}\nAssistant: {reply}", "conversations" ) await cognee.cognify(["conversations"]) return reply The memory cycle: ingest, extract, store, retrieve, respond, store again. Each turn enriches the knowledge graph, and incremental processing means you only pay to index new content. Session memory handles pronoun resolution automatically: python await cognee.search(query_text="Where does Alice live?", session_id="conv_1") await cognee.search(query_text="What does she do for work?", session_id="conv_1") # "she" resolves to Alice from session context Multi-tenancy is built in at the graph level with per-dataset permissions (read, write, delete, share). Not namespace separation, actual graph-level isolation. The practical path forward If you're building an agent today, the real starting question is: "what does my agent need to remember, and what kind of questions will it answer?" If your queries only need similarity search ("find conversations like this one"), vector-only memory works. The moment queries cross entity boundaries ("Was Alice's project affected by Tuesday's outage?"), you need graph traversal. You can wire together separate vector, graph, and relational stores yourself. Teams that go this route typically burn weeks on infrastructure for a memory layer that still doesn't learn from its own usage. Cognee collapses that into four API calls. Embedded defaults get you running in minutes. Swappable backends (Postgres, Qdrant, Neo4j) take you to production without changing your agent code. Intelligence requires structure, not just storage. The three storage paradigms (relational, vector, graph) aren't competing options. They're complementary layers of the same memory system. Treating them that way is what turns a stateless LLM wrapper into something that actually learns. What's the next thing you'd want your agent to remember tomorrow that it forgot today? Start there. 👉 Check out Cognee on GitHub →, give it a star, and try wiring it into your next agent. Four async calls, a pip install, and you're running. That's a wrap! If you enjoyed reading this: Find me →@akshay_pachaar ✔️ Every day, I share tutorials and insights on AI, Machine Learning, and vibe coding best practices.

AIFCC — AI Fluent CxO Club

読み書きそろばん、AI。経営者が AI を自分で動かせるようになるコミュニティ。

記憶を持つエージェントの設計原則 | AIFCC