From f5ad59317cf8b933158dc3e0a81733a1bf379b19 Mon Sep 17 00:00:00 2001 From: pewdiepie-archdaemon Date: Thu, 11 Jun 2026 09:46:34 +0900 Subject: [PATCH] Tool retrieval: HARD drop manage_memory when query is a contact-save pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description-level steering wasn't enough — even with the explicit 'DO NOT use for info about another person' in manage_memory's description, models kept choosing memory over manage_contact. They can't if memory isn't in the toolset. New logic in ToolIndex.get_tools_for_query: detect three contact-save patterns and discard manage_memory from the returned set (overriding ALWAYS_AVAILABLE): 1. 'save [up to 3 words] for/to ' where isn't a timing / pronoun stopword (later, tomorrow, me, you, future, etc.). Catches the canonical 'save this for X' and the wider 'save this address for X', 'save it for X'. 2. 'to/in/into (my) contacts' or 'address book'. Catches both 'add X to my contacts' and 'put this in my address book for X'. 3. Possessive: 'save (his/her/their) (address/phone/email/...)'. Stronger signal — also force-adds manage_contact to the set in case the keyword fallback missed it. Verified: 8 positive contact patterns all drop memory, 10 false- positive 'save X for later/tomorrow/me/the next thing' all keep it. --- src/tool_index.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/tool_index.py b/src/tool_index.py index 36ae78b5b..15784d1a7 100644 --- a/src/tool_index.py +++ b/src/tool_index.py @@ -514,6 +514,53 @@ class ToolIndex: # prompts do not drag web schemas into the agent context. if self._WEB_RE.search(query): base.update({"web_search", "web_fetch"}) + # Hard steering: when the query is a clear "save info about a specific + # person" pattern (address paste + name, phone next to a name, etc.), + # the model has been observed defaulting to manage_memory even with + # manage_contact in the toolset. Pull memory out for these queries so + # the model literally cannot pick it. ALWAYS_AVAILABLE includes + # manage_memory by default; we override that here. + # The "for/to " check needs to allow lowercase names (users + # don't always capitalize) but filter out timing/pronoun stopwords + # so "save this for later" / "save for tomorrow" don't trigger. + _CONTACT_STOPWORDS_AFTER_FOR = { + "later", "tomorrow", "yesterday", "now", "then", "today", + "tonight", "me", "us", "you", "him", "her", "them", "myself", + "yourself", "next", "this", "that", "the", "a", "an", "future", + "real", "use", "uses", "another", "future", "reference", + } + # Regex catches "save (this|it|the|her|...|) for " / "to my + # contacts" patterns. More forgiving than literal-keyword matching — + # 'save this address for Alex' uses one extra word between 'save' and + # 'for' that breaks the contiguous 'save this for' phrase. + save_for_match = re.search( + r"\bsave\b(?:\s+\w+){0,3}\s+(?:for|to)\s+([A-Za-z]+)", + ql, + ) + # "to my contacts", "into my contacts", "in my address book", etc. + to_contacts = re.search(r"\b(?:to|in|into)\s+(?:my\s+)?(?:contacts|address\s+book)\b", ql) + # Possessive: "save (his|her|their) (address|phone|email|number) ..." + # — strong contact signal even without "for ". Force-include + # manage_contact here too since the keyword fallback misses this + # construction. + possessive_contact = re.search( + r"\bsave\b(?:\s+\w+){0,2}\s+(?:his|her|their)\s+(?:address|phone|number|email|contact|details)", + ql, + ) + word_after = ( + save_for_match.group(1).lower() if save_for_match else None + ) + contact_only_signal = ( + (save_for_match is not None + and word_after is not None + and word_after not in _CONTACT_STOPWORDS_AFTER_FOR) + or to_contacts is not None + or possessive_contact is not None + ) + if possessive_contact is not None: + base.add("manage_contact") + if contact_only_signal and "manage_contact" in base: + base.discard("manage_memory") return base