From 3cc6516ae5145558efd9ac2051f2fc9d0c1f16ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 4 Mar 2026 09:19:07 -0800 Subject: [PATCH] Memory overall improvements (#4688) * feat: enhance memory recall limits and update documentation - Increased the memory recall limit in the Agent class from 5 to 15. - Updated the RecallMemoryTool to allow a recall limit of 20. - Expanded the documentation for the recall_memory feature to emphasize the importance of multiple queries for comprehensive results. * feat: increase memory recall limit and enhance memory context documentation - Increased the memory recall limit in the Agent class from 15 to 20. - Updated the memory context message to clarify the nature of the memories presented and the importance of using the Search memory tool for comprehensive results. * refactor: remove inferred_categories from RecallState and update category merging logic - Removed the inferred_categories field from RecallState to simplify state management. - Updated the _merged_categories method to only merge caller-supplied categories, enhancing clarity in category handling. * refactor: simplify category handling in RecallFlow - Updated the _merged_categories method to return only caller-supplied categories, removing the previous merging logic for inferred categories. This change enhances clarity and maintains consistency in category management. --- lib/crewai/src/crewai/agent/core.py | 2 +- lib/crewai/src/crewai/memory/recall_flow.py | 13 ++----------- lib/crewai/src/crewai/tools/memory_tools.py | 2 +- lib/crewai/src/crewai/translations/en.json | 6 +++--- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 9c9b43c06..418ebe73d 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -1268,7 +1268,7 @@ class Agent(BaseAgent): ), ) start_time = time.time() - matches = agent_memory.recall(formatted_messages, limit=5) + matches = agent_memory.recall(formatted_messages, limit=20) memory_block = "" if matches: memory_block = "Relevant memories:\n" + "\n".join( diff --git a/lib/crewai/src/crewai/memory/recall_flow.py b/lib/crewai/src/crewai/memory/recall_flow.py index 053eb8d97..e0f238861 100644 --- a/lib/crewai/src/crewai/memory/recall_flow.py +++ b/lib/crewai/src/crewai/memory/recall_flow.py @@ -2,7 +2,6 @@ Implements adaptive-depth retrieval with: - LLM query distillation into targeted sub-queries -- Keyword-driven category filtering - Time-based filtering from temporal hints - Parallel multi-query, multi-scope search - Confidence-based routing with iterative deepening (budget loop) @@ -37,7 +36,6 @@ class RecallState(BaseModel): query: str = "" scope: str | None = None categories: list[str] | None = None - inferred_categories: list[str] = Field(default_factory=list) time_cutoff: datetime | None = None source: str | None = None include_private: bool = False @@ -82,11 +80,8 @@ class RecallFlow(Flow[RecallState]): # ------------------------------------------------------------------ def _merged_categories(self) -> list[str] | None: - """Merge caller-supplied and LLM-inferred categories.""" - merged = list( - set((self.state.categories or []) + self.state.inferred_categories) - ) - return merged or None + """Return caller-supplied categories, or None if empty.""" + return self.state.categories or None def _do_search(self) -> list[dict[str, Any]]: """Run parallel search across (embeddings x scopes) with filters. @@ -212,10 +207,6 @@ class RecallFlow(Flow[RecallState]): ) self.state.query_analysis = analysis - # Wire keywords -> category filter - if analysis.keywords: - self.state.inferred_categories = analysis.keywords - # Parse time_filter into a datetime cutoff if analysis.time_filter: try: diff --git a/lib/crewai/src/crewai/tools/memory_tools.py b/lib/crewai/src/crewai/tools/memory_tools.py index f088fef73..9e4df03e9 100644 --- a/lib/crewai/src/crewai/tools/memory_tools.py +++ b/lib/crewai/src/crewai/tools/memory_tools.py @@ -49,7 +49,7 @@ class RecallMemoryTool(BaseTool): all_lines: list[str] = [] seen_ids: set[str] = set() for query in queries: - matches = self.memory.recall(query) + matches = self.memory.recall(query, limit=20) for m in matches: if m.record.id not in seen_ids: seen_ids.add(m.record.id) diff --git a/lib/crewai/src/crewai/translations/en.json b/lib/crewai/src/crewai/translations/en.json index 1eb02c746..833f6e9e7 100644 --- a/lib/crewai/src/crewai/translations/en.json +++ b/lib/crewai/src/crewai/translations/en.json @@ -7,7 +7,7 @@ "slices": { "observation": "\nObservation:", "task": "\nCurrent Task: {input}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:", - "memory": "\n\n# Useful context: \n{memory}", + "memory": "\n\n# Memories from past conversations:\n{memory}\n\nIMPORTANT: The memories above are an automatic selection and may be INCOMPLETE. If the task involves counting, listing, or summing items (e.g. 'how many', 'total', 'list all'), you MUST use the Search memory tool with several different queries before answering — do NOT rely solely on the memories shown above. Enumerate each distinct item you find before giving a final count.", "role_playing": "You are {role}. {backstory}\nYour personal goal is: {goal}", "tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nIMPORTANT: Use the following format in your response:\n\n```\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce all necessary information is gathered, return the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n```", "no_tools": "", @@ -60,12 +60,12 @@ "description": "See image to understand its content, you can optionally ask a question about the image", "default_action": "Please provide a detailed description of this image, including all visual elements, context, and any notable details you can observe." }, - "recall_memory": "Search through the team's shared memory for relevant information. Pass one or more queries to search for multiple things at once. Use this when you need to find facts, decisions, preferences, or past results that may have been stored previously.", + "recall_memory": "Search through the team's shared memory for relevant information. Pass one or more queries to search for multiple things at once. Use this when you need to find facts, decisions, preferences, or past results that may have been stored previously. IMPORTANT: For questions that require counting, summing, or listing items across multiple conversations (e.g. 'how many X', 'total Y', 'list all Z'), you MUST search multiple times with different phrasings to ensure you find ALL relevant items before giving a final count or total. Do not rely on a single search — items may be described differently across conversations.", "save_to_memory": "Store one or more important facts, decisions, observations, or lessons in memory so they can be recalled later by you or other agents. Pass multiple items at once when you have several things worth remembering." }, "memory": { "query_system": "You analyze a query for searching memory.\nGiven the query and available scopes, output:\n1. keywords: Key entities or keywords that can be used to filter by category.\n2. suggested_scopes: Which available scopes are most relevant (empty for all).\n3. complexity: 'simple' or 'complex'.\n4. recall_queries: 1-3 short, targeted search phrases distilled from the query. Each should be a concise phrase optimized for semantic vector search. If the query is already short and focused, return it as-is in a single-item list. For long task descriptions, extract the distinct things worth searching for.\n5. time_filter: If the query references a time period (like 'last week', 'yesterday', 'in January'), return an ISO 8601 date string for the earliest relevant date (e.g. '2026-02-01'). Return null if no time constraint is implied.", - "extract_memories_system": "You extract discrete, reusable memory statements from raw content (e.g. a task description and its result).\n\nFor the given content, output a list of memory statements. Each memory must:\n- Be one clear sentence or short statement\n- Be understandable without the original context\n- Capture a decision, fact, outcome, preference, lesson, or observation worth remembering\n- NOT be a vague summary or a restatement of the task description\n- NOT duplicate the same idea in different words\n\nIf there is nothing worth remembering (e.g. empty result, no decisions or facts), return an empty list.\nOutput a JSON object with a single key \"memories\" whose value is a list of strings.", + "extract_memories_system": "You extract discrete, reusable memory statements from raw content (e.g. a task description and its result, or a conversation between a user and an assistant).\n\nFor the given content, output a list of memory statements. Each memory must:\n- Be one clear sentence or short statement\n- Be understandable without the original context\n- Capture a decision, fact, outcome, preference, lesson, or observation worth remembering\n- NOT be a vague summary or a restatement of the task description\n- NOT duplicate the same idea in different words\n\nWhen the content is a conversation, pay special attention to facts stated by the user (first-person statements). These personal facts are HIGH PRIORITY and must always be extracted:\n- What the user did, bought, made, visited, attended, or completed\n- Names of people, pets, places, brands, and specific items the user mentions\n- Quantities, durations, dates, and measurements the user states\n- Subordinate clauses and casual asides often contain important personal details (e.g. \"by the way, it took me 4 hours\" or \"my Golden Retriever Max\")\n\nPreserve exact names and numbers — never generalize (e.g. keep \"lavender gin fizz\" not just \"cocktail\", keep \"12 largemouth bass\" not just \"fish caught\", keep \"Golden Retriever\" not just \"dog\").\n\nAdditional extraction rules:\n- Presupposed facts: When the user reveals a fact indirectly in a question (e.g. \"What collar suits a Golden Retriever like Max?\" presupposes Max is a Golden Retriever), extract that fact as a separate memory.\n- Date precision: Always preserve the full date including day-of-month when stated (e.g. \"February 14th\" not just \"February\", \"March 5\" not just \"March\").\n- Life events in passing: When the user mentions a life event (birth, wedding, graduation, move, adoption) while discussing something else, extract the life event as its own memory (e.g. \"my friend David had a baby boy named Jasper\" is a birth fact, even if mentioned while planning to send congratulations).\n\nIf there is nothing worth remembering (e.g. empty result, no decisions or facts), return an empty list.\nOutput a JSON object with a single key \"memories\" whose value is a list of strings.", "extract_memories_user": "Content:\n{content}\n\nExtract memory statements as described. Return structured output.", "query_user": "Query: {query}\n\nAvailable scopes: {available_scopes}\n{scope_desc}\n\nReturn the analysis as structured output.", "save_system": "You analyze content to be stored in a hierarchical memory system.\nGiven the content and the existing scopes and categories, output:\n1. suggested_scope: The best matching existing scope path, or a new path if none fit (use / for root).\n2. categories: A list of categories (reuse existing when relevant, add new ones if needed).\n3. importance: A number from 0.0 to 1.0 indicating how significant this memory is.\n4. extracted_metadata: A JSON object with any entities, dates, or topics you can extract.",