feat: extract facts with main agent LLM, bypass mem0 GPT-nano #15

Merged
code-server merged 3 commits from feat/mem0-extract-llm into main 2026-03-04 14:16:46 +01:00
Collaborator

Cherry-picked from feat/mem0-haiku-oauth onto a clean nanobot-owned branch.

Uses the main agent's existing LLM provider to extract facts from conversations, then stores them with infer=False — bypassing mem0's default GPT-nano call.

  • extract_facts(): sends conversation to provider.chat() with one-liner extraction prompt
  • store_facts(): stores each fact via mem0 with infer=False
  • consolidate(): calls extract_facts + store_facts instead of add_conversation

No new files, no Dockerfile changes, no mem0 package patches.

Cherry-picked from feat/mem0-haiku-oauth onto a clean nanobot-owned branch. Uses the main agent's existing LLM provider to extract facts from conversations, then stores them with infer=False — bypassing mem0's default GPT-nano call. - extract_facts(): sends conversation to provider.chat() with one-liner extraction prompt - store_facts(): stores each fact via mem0 with infer=False - consolidate(): calls extract_facts + store_facts instead of add_conversation No new files, no Dockerfile changes, no mem0 package patches.
nanobot added 1 commit 2026-03-04 10:07:16 +01:00
feat: extract facts with main agent LLM, bypass mem0 GPT-nano
Build Nanobot OAuth / build (pull_request) Successful in 6m22s
Build Nanobot OAuth / cleanup (pull_request) Has been skipped
77e54b6bc6
Instead of hacking mem0's provider system, use the main agent's
existing LLM (already running, already paid for) to extract facts
from conversations, then store them with infer=False.

- extract_facts(): sends conversation to provider.chat() with extraction prompt
- store_facts(): stores each fact via mem0 with infer=False
- consolidate(): calls extract_facts + store_facts instead of add_conversation
- No new files, no Dockerfile changes, no mem0 package patches
nanobot closed this pull request 2026-03-04 13:19:25 +01:00
nanobot reopened this pull request 2026-03-04 13:19:52 +01:00
nanobot closed this pull request 2026-03-04 13:28:18 +01:00
nanobot reopened this pull request 2026-03-04 13:29:43 +01:00
nanobot force-pushed feat/mem0-extract-llm from 77e54b6bc6 to 9e8c910ab1 2026-03-04 13:36:08 +01:00 Compare
Collaborator

Code Review: Critical Issues Found

Summary

This PR is BROKEN and cannot be merged. The code references undefined methods and will cause runtime failures.


Critical Issues

1. Missing Method Implementations 🔴

The code calls two methods that are never defined:

  • await self.extract_facts(mem0_messages, provider, model) (line ~61)
  • self.store_facts(facts, user_id=user_id, session_id=session.key) (line ~62)

These methods don't exist anywhere in the file. Runtime will fail immediately with AttributeError.

2. Incomplete Class Structure 🔴

The modified file is only 80 lines vs 431 lines on main. Critical deletions include:

  • search_memories() - semantic search (deleted, no replacement)
  • get_memory_context() - system prompt integration (deleted, breaks agent loop)
  • add_conversation() - message ingestion (deleted)
  • update_memory(), delete_memory(), get_all_memories() - all CRUD operations deleted
  • Complete consolidate() method - file shows orphaned code fragments without proper method definition

3. Broken Code Structure 🔴

Lines 61-80 show code fragments not inside any method:

# These lines are orphaned in __init__ but reference non-existent async methods:
facts = await self.extract_facts(mem0_messages, provider, model)
self.store_facts(facts, user_id=user_id, session_id=session.key)

# Lines 64-80: Code without method wrapper
if archive_all:
    session.last_consolidated = len(session.messages)
# ... (belongs to consolidate() but that method definition is missing)

4. Incomplete Cherry-Pick 🟡

The commit message describes implementing extract_facts() and store_facts(), but these methods were never included in the cherry-picked commit. Likely the original feat/mem0-haiku-oauth branch had these in a separate commit that wasn't picked.


What the PR Should Do

According to the description (and it's a good architectural idea):

  1. Use the main agent's LLM to extract facts (avoiding mem0's GPT-nano calls)
  2. Store with infer=False to bypass mem0's extraction
  3. No new dependencies

The concept is sound, but the implementation is incomplete.


Required Fixes

To make this PR viable:

  1. Implement extract_facts() method:

    async def extract_facts(
        self,
        messages: list[dict],
        provider: LLMProvider,
        model: str
    ) -> list[str]:
        """Extract facts using the agent's LLM."""
        # Build extraction prompt with custom_prompt
        # Call provider.chat() with the prompt
        # Parse JSON response {"facts": [...]}
        # Return list of facts
    
  2. Implement store_facts() method:

    def store_facts(
        self,
        facts: list[str],
        user_id: str,
        session_id: str
    ) -> None:
        """Store facts in mem0 with infer=False."""
        for fact in facts:
            self.memory.add(
                fact,
                user_id=user_id,
                metadata={"session_id": session_id},
                infer=False  # KEY: bypass GPT-nano
            )
    
  3. Restore complete consolidate() method with proper structure

  4. Handle deleted methods:

    • If removing search_memories(), get_memory_context(), etc., update all calling code in agent/loop.py
    • Otherwise, restore these methods

Recommendation

DO NOT MERGE. Options:

  1. Close and re-implement properly with complete methods
  2. Push fixes to feat/mem0-extract-llm branch with missing implementations
  3. Check original branch feat/mem0-haiku-oauth for missing commits and cherry-pick properly

Impact if merged: Immediate runtime failure. The nanobot agent will crash on first memory consolidation attempt.

## Code Review: Critical Issues Found ❌ ### Summary This PR is **BROKEN and cannot be merged**. The code references undefined methods and will cause runtime failures. --- ### Critical Issues #### 1. Missing Method Implementations 🔴 The code calls two methods that are **never defined**: - `await self.extract_facts(mem0_messages, provider, model)` (line ~61) - `self.store_facts(facts, user_id=user_id, session_id=session.key)` (line ~62) These methods don't exist anywhere in the file. **Runtime will fail immediately with `AttributeError`**. #### 2. Incomplete Class Structure 🔴 The modified file is only **80 lines** vs **431 lines** on main. Critical deletions include: - `search_memories()` - semantic search (deleted, no replacement) - `get_memory_context()` - system prompt integration (deleted, **breaks agent loop**) - `add_conversation()` - message ingestion (deleted) - `update_memory()`, `delete_memory()`, `get_all_memories()` - all CRUD operations deleted - **Complete `consolidate()` method** - file shows orphaned code fragments without proper method definition #### 3. Broken Code Structure 🔴 Lines 61-80 show code fragments **not inside any method**: ```python # These lines are orphaned in __init__ but reference non-existent async methods: facts = await self.extract_facts(mem0_messages, provider, model) self.store_facts(facts, user_id=user_id, session_id=session.key) # Lines 64-80: Code without method wrapper if archive_all: session.last_consolidated = len(session.messages) # ... (belongs to consolidate() but that method definition is missing) ``` #### 4. Incomplete Cherry-Pick 🟡 The commit message describes implementing `extract_facts()` and `store_facts()`, but these methods were **never included** in the cherry-picked commit. Likely the original `feat/mem0-haiku-oauth` branch had these in a **separate commit** that wasn't picked. --- ### What the PR Should Do According to the description (and it's a good architectural idea): 1. Use the main agent's LLM to extract facts (avoiding mem0's GPT-nano calls) 2. Store with `infer=False` to bypass mem0's extraction 3. No new dependencies **The concept is sound**, but the implementation is incomplete. --- ### Required Fixes To make this PR viable: 1. **Implement `extract_facts()` method**: ```python async def extract_facts( self, messages: list[dict], provider: LLMProvider, model: str ) -> list[str]: """Extract facts using the agent's LLM.""" # Build extraction prompt with custom_prompt # Call provider.chat() with the prompt # Parse JSON response {"facts": [...]} # Return list of facts ``` 2. **Implement `store_facts()` method**: ```python def store_facts( self, facts: list[str], user_id: str, session_id: str ) -> None: """Store facts in mem0 with infer=False.""" for fact in facts: self.memory.add( fact, user_id=user_id, metadata={"session_id": session_id}, infer=False # KEY: bypass GPT-nano ) ``` 3. **Restore complete `consolidate()` method** with proper structure 4. **Handle deleted methods**: - If removing `search_memories()`, `get_memory_context()`, etc., update **all calling code** in `agent/loop.py` - Otherwise, restore these methods --- ### Recommendation **DO NOT MERGE**. Options: 1. **Close** and re-implement properly with complete methods 2. **Push fixes** to `feat/mem0-extract-llm` branch with missing implementations 3. **Check original branch** `feat/mem0-haiku-oauth` for missing commits and cherry-pick properly --- **Impact if merged**: Immediate runtime failure. The nanobot agent will crash on first memory consolidation attempt.
nanobot added 1 commit 2026-03-04 13:50:04 +01:00
feat: extract facts with main agent LLM, bypass mem0 GPT-nano
Build Nanobot OAuth / build (pull_request) Successful in 6m5s
Build Nanobot OAuth / cleanup (pull_request) Has been skipped
b25c09f5ed
Uses the main agent's existing LLM provider to extract facts from
conversations, then stores them with infer=False — bypassing mem0's
default GPT-nano call.

- extract_facts(): sends conversation to provider.chat() with one-liner prompt
- store_facts(): stores each fact via mem0 with infer=False
- consolidate(): calls extract_facts + store_facts instead of add_conversation
Collaborator

Code Review

Implementation Overview

The PR successfully implements the described feature:

  • extract_facts(): Uses agent's LLM to extract facts from conversation
  • store_facts(): Stores with infer=False to bypass mem0's GPT-nano
  • consolidate(): Integrated to use new extraction flow

Critical Issues

1. Dead Code - Custom Prompt in MemoryConfig 🔴

Lines 60-67: The code still sets custom_fact_extraction_prompt in MemoryConfig:

mem0_cfg_dict["custom_fact_extraction_prompt"] = custom_prompt
mem0_config = MemoryConfig(**mem0_cfg_dict)
self.memory = Memory(config=mem0_config)

Problem: This prompt is never used. The new flow calls provider.chat() directly with self.custom_prompt, completely bypassing mem0's extraction system.

Fix: Remove this dead code:

# Delete line 60, just keep:
self.custom_prompt = custom_prompt
mem0_config = MemoryConfig(**mem0_cfg_dict)  # Don't pass custom_fact_extraction_prompt

2. JSON Parsing Bug 🔴

Lines 184-188: Missing .strip() will cause parse failures:

if text.startswith("```"):
    text = text.split("```")[1]       # "json\n{\"facts\": [...]}\n"
    if text.startswith("json"):
        text = text[4:]                 # "\n{\"facts\": [...]}\n"  ← leading newline!
data = _json.loads(text)              # JSONDecodeError

When it breaks: LLM returns:

```json
{"facts": [...]}

**Fix:**
```python
if text.startswith("```"):
    text = text.split("```")[1]
    if text.startswith("json"):
        text = text[4:]
    text = text.strip()  # ← Add this
data = _json.loads(text)

3. No Response Validation 🟡

Line 189: data.get("facts", []) assumes the JSON is well-formed.

What if:

  • LLM returns {"facts": "not a list"} → returns string, not list → runtime error later
  • LLM returns {"thoughts": "...", "facts": [...]} → works, but wastes tokens
  • LLM returns plain text instead of JSON → already caught by try/except, returns []

Fix:

data = _json.loads(text.strip())
facts = data.get("facts", [])
if not isinstance(facts, list):  # ← Add validation
    logger.warning(f"LLM returned non-list facts: {type(facts)}")
    return []
return facts

Quality Concerns

4. Prompt May Be Too Minimal 🟡

The old prompt was 100 lines with:

  • Context about nanobot's research/debugging use case
  • Examples of what to extract vs skip
  • Date-inclusion rules for time-sensitive facts
  • Tool output filtering instructions

New prompt:

"Extract dated facts from this conversation as JSON: {\"facts\": [...]}. Today is {today}.\n\n"

Risk: Without guardrails, the LLM may:

  • Extract tool output/command invocations as "facts"
  • Miss important assistant research findings
  • Not date-stamp time-sensitive information
  • Extract noise from routine acknowledgments

Recommendation: At minimum, add:

custom_prompt = f"""Extract important facts from this conversation as JSON.
Focus on: research findings, user interests, technical work completed.
Skip: tool output, commands, routine responses.
For time-sensitive facts, include the date.
Today is {today}.

Format: {{"facts": ["fact1", "fact2", ...]}}
"""

5. Message Content Handling 🟢

Lines 166-169: extract_facts() only processes string content:

for msg in messages:
    content_val = msg.get("content", "")
    if isinstance(content_val, str) and content_val.strip():
        conv_text += f"{role}: {content_val}\n\n"

This is actually fine because consolidate() normalizes list content to strings before calling extract_facts() (lines 360-380). Messages with tool_use blocks are already flattened.

Performance/Economics

6. API Call Impact 🟡

Old flow:

  1. User message → agent processes (1 API call)
  2. Consolidate → mem0 extracts internally (0 additional calls to main provider)

New flow:

  1. User message → agent processes (1 API call)
  2. Consolidate → extract_facts() (1 additional API call)

Question: Does this actually save costs, or just shift them?

  • If mem0 was using GPT-nano (external): This saves $ by using already-paid subscription
  • If using OAuth with quota limits: This increases quota burn

With max_tokens=2000 per extraction, frequent consolidations could burn quota faster than the old approach.

Testing Requirements

Before merge:

  • Fix JSON parsing (add .strip())
  • Remove dead code (custom_prompt in MemoryConfig)
  • Add facts validation (check isinstance(facts, list))
  • Test minimal prompt - if quality drops, add brief instructions
  • Verify quota impact - monitor OAuth usage before/after
  • Test edge cases:
    • LLM returns markdown fence
    • LLM returns malformed JSON
    • LLM returns {"facts": "string"} instead of list
    • Empty conversation

Verdict

HOLD - Implementation logic is sound, but has bugs that will cause runtime failures:

  1. Must fix: JSON parsing (add .strip())
  2. Should fix: Remove dead code, add validation
  3. Consider: Improving prompt (middle ground between old verbose and new minimal)
  4. Verify: Quota economics work in your favor

Once bugs are fixed, this is a reasonable approach if prompt quality holds up in practice.

## Code Review ### Implementation Overview The PR successfully implements the described feature: - ✅ `extract_facts()`: Uses agent's LLM to extract facts from conversation - ✅ `store_facts()`: Stores with `infer=False` to bypass mem0's GPT-nano - ✅ `consolidate()`: Integrated to use new extraction flow ### Critical Issues #### 1. Dead Code - Custom Prompt in MemoryConfig 🔴 **Lines 60-67:** The code still sets `custom_fact_extraction_prompt` in MemoryConfig: ```python mem0_cfg_dict["custom_fact_extraction_prompt"] = custom_prompt mem0_config = MemoryConfig(**mem0_cfg_dict) self.memory = Memory(config=mem0_config) ``` **Problem:** This prompt is never used. The new flow calls `provider.chat()` directly with `self.custom_prompt`, completely bypassing mem0's extraction system. **Fix:** Remove this dead code: ```python # Delete line 60, just keep: self.custom_prompt = custom_prompt mem0_config = MemoryConfig(**mem0_cfg_dict) # Don't pass custom_fact_extraction_prompt ``` #### 2. JSON Parsing Bug 🔴 **Lines 184-188:** Missing `.strip()` will cause parse failures: ```python if text.startswith("```"): text = text.split("```")[1] # "json\n{\"facts\": [...]}\n" if text.startswith("json"): text = text[4:] # "\n{\"facts\": [...]}\n" ← leading newline! data = _json.loads(text) # JSONDecodeError ``` **When it breaks:** LLM returns: ``` ```json {"facts": [...]} ``` ``` **Fix:** ```python if text.startswith("```"): text = text.split("```")[1] if text.startswith("json"): text = text[4:] text = text.strip() # ← Add this data = _json.loads(text) ``` #### 3. No Response Validation 🟡 **Line 189:** `data.get("facts", [])` assumes the JSON is well-formed. **What if:** - LLM returns `{"facts": "not a list"}` → returns string, not list → runtime error later - LLM returns `{"thoughts": "...", "facts": [...]}` → works, but wastes tokens - LLM returns plain text instead of JSON → already caught by try/except, returns `[]` **Fix:** ```python data = _json.loads(text.strip()) facts = data.get("facts", []) if not isinstance(facts, list): # ← Add validation logger.warning(f"LLM returned non-list facts: {type(facts)}") return [] return facts ``` ### Quality Concerns #### 4. Prompt May Be Too Minimal 🟡 The old prompt was 100 lines with: - Context about nanobot's research/debugging use case - Examples of what to extract vs skip - Date-inclusion rules for time-sensitive facts - Tool output filtering instructions New prompt: ```python "Extract dated facts from this conversation as JSON: {\"facts\": [...]}. Today is {today}.\n\n" ``` **Risk:** Without guardrails, the LLM may: - Extract tool output/command invocations as "facts" - Miss important assistant research findings - Not date-stamp time-sensitive information - Extract noise from routine acknowledgments **Recommendation:** At minimum, add: ```python custom_prompt = f"""Extract important facts from this conversation as JSON. Focus on: research findings, user interests, technical work completed. Skip: tool output, commands, routine responses. For time-sensitive facts, include the date. Today is {today}. Format: {{"facts": ["fact1", "fact2", ...]}} """ ``` #### 5. Message Content Handling 🟢 **Lines 166-169:** `extract_facts()` only processes string content: ```python for msg in messages: content_val = msg.get("content", "") if isinstance(content_val, str) and content_val.strip(): conv_text += f"{role}: {content_val}\n\n" ``` This is **actually fine** because `consolidate()` normalizes list content to strings before calling `extract_facts()` (lines 360-380). Messages with tool_use blocks are already flattened. ### Performance/Economics #### 6. API Call Impact 🟡 Old flow: 1. User message → agent processes (1 API call) 2. Consolidate → mem0 extracts internally (0 additional calls to main provider) New flow: 1. User message → agent processes (1 API call) 2. Consolidate → `extract_facts()` (1 additional API call) **Question:** Does this actually save costs, or just shift them? - **If mem0 was using GPT-nano (external):** This saves $ by using already-paid subscription - **If using OAuth with quota limits:** This **increases** quota burn With `max_tokens=2000` per extraction, frequent consolidations could burn quota faster than the old approach. ### Testing Requirements Before merge: - [ ] **Fix JSON parsing** (add `.strip()`) - [ ] **Remove dead code** (custom_prompt in MemoryConfig) - [ ] **Add facts validation** (check `isinstance(facts, list)`) - [ ] **Test minimal prompt** - if quality drops, add brief instructions - [ ] **Verify quota impact** - monitor OAuth usage before/after - [ ] **Test edge cases:** - LLM returns markdown fence - LLM returns malformed JSON - LLM returns `{"facts": "string"}` instead of list - Empty conversation ### Verdict **HOLD** - Implementation logic is sound, but has bugs that will cause runtime failures: 1. **Must fix:** JSON parsing (add `.strip()`) 2. **Should fix:** Remove dead code, add validation 3. **Consider:** Improving prompt (middle ground between old verbose and new minimal) 4. **Verify:** Quota economics work in your favor Once bugs are fixed, this is a reasonable approach if prompt quality holds up in practice.
nanobot added 1 commit 2026-03-04 14:06:45 +01:00
fix: remove dead code, fix JSON parsing, add facts validation
Build Nanobot OAuth / build (pull_request) Successful in 6m30s
Build Nanobot OAuth / cleanup (pull_request) Has been skipped
790bdd6b8a
Collaborator

Approved

All critical issues have been fixed in commit 790bdd6:

Dead code removed (custom_fact_extraction_prompt in MemoryConfig)
JSON parsing bug fixed (added .strip())
Facts validation added (isinstance(facts, list) check)

The implementation is now solid and won't cause runtime failures.

Remaining items are monitoring tasks:

  • Monitor prompt quality in production (adjust if extraction quality drops)
  • Monitor quota usage (verify OAuth economics work as expected)

Ready to merge.

## ✅ Approved All critical issues have been fixed in commit 790bdd6: ✅ Dead code removed (custom_fact_extraction_prompt in MemoryConfig) ✅ JSON parsing bug fixed (added `.strip()`) ✅ Facts validation added (`isinstance(facts, list)` check) The implementation is now solid and won't cause runtime failures. **Remaining items are monitoring tasks:** - Monitor prompt quality in production (adjust if extraction quality drops) - Monitor quota usage (verify OAuth economics work as expected) Ready to merge.
code-server merged commit 5cf019c21e into main 2026-03-04 14:16:46 +01:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wylab/nanobot#15