Skip to content

Conversation

@uzaxirr
Copy link
Contributor

@uzaxirr uzaxirr commented Oct 29, 2025

Summary

Describe key changes, mention related issues or motivation for the changes.

3 main classes:

MemoryOptimizationStrategyType (Enum)

  • catalog of available strategies
  • Prevents typos and misunderstanding giving a better DX
  • from_string() method for backward compatibility allows to use stratergies as simple string Eg: stratergy="merge" as well. This gives good DX
MemoryOptimizationStrategyType.SUMMARIZE 
MemoryOptimizationStrategyType.MERGE

MemoryOptimizationStrategy (Abstract Base Class)

  • Defines contract for all strategies
  • Provides shared utilities (count_tokens())

MemoryOptimizationStrategyFactory

  • Centralized strategy instantiation
  • similar to ChunkingStrategyFactory

Users can also define their custom strategy.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Improvement
  • Model update
  • Other:

Checklist

  • Code complies with style guidelines
  • Ran format/validation scripts (./scripts/format.sh and ./scripts/validate.sh)
  • Self-review completed
  • Documentation updated (comments, docstrings)
  • Examples and guides: Relevant cookbook examples have been included or updated (if applicable)
  • Tested in clean environment
  • Tests added/updated (if applicable)

Additional Notes

Add any important context (deployment instructions, screenshots, security considerations, etc.)

@uzaxirr uzaxirr requested a review from a team as a code owner October 29, 2025 06:14
@uzaxirr uzaxirr self-assigned this Oct 29, 2025
raise ValueError(f"Unsupported optimization strategy: {strategy_name}. Valid options: {[e.value for e in cls]}")


class MemoryOptimizationStrategy(ABC):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base class should be in strategies.

class MemoryOptimizationStrategy(ABC):
"""Base class for memory optimization strategies."""

def count_tokens(self, memories: List["UserMemory"]) -> int:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a general utility function. We can use it in many places.

from agno.utils.log import log_debug


class MergeStrategy(MemoryOptimizationStrategy):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the only strategy that makes sense for now. Replace all memories with a single memory.


# Concatenate input and feedback fields
input_parts = [mem.input for mem in memories if mem.input]
merged_input = "\n---\n".join(input_parts) if input_parts else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that doesn't make sense to keep. The input kinda falls away if you optimize

merged_input = "\n---\n".join(input_parts) if input_parts else None

feedback_parts = [mem.feedback for mem in memories if mem.feedback]
merged_feedback = "\n---\n".join(feedback_parts) if feedback_parts else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with feedback (although we don't use it)

feedback_parts = [mem.feedback for mem in memories if mem.feedback]
merged_feedback = "\n---\n".join(feedback_parts) if feedback_parts else None

# Check if agent_id and team_id are consistent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These as well. Since a whole new memory is created the original memory creator is lost

self,
user_id: Optional[str] = None,
token_limit: int = 2000,
strategy: Union[str, MemoryOptimizationStrategyType, MemoryOptimizationStrategy] = "summarize",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only support MemoryOptimizationStrategyType and MemoryOptimizationStrategy

Args:
user_id: User ID to optimize memories for. Defaults to "default".
token_limit: Maximum tokens for all memories combined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work for summarize, because there the token limit is per memory?

In general I don't think we should have token limit, it doesn't really make sense. The Model should just summarize.

- String: "summarize", "merge"
- Enum: MemoryOptimizationStrategyType.SUMMARIZE, MemoryOptimizationStrategyType.MERGE
- Instance: Custom MemoryOptimizationStrategy instance
model: Model to use for optimization. Defaults to manager's model.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a parameter. You should just create the memory manager with a model and call optimize.


# Get strategy instance
if isinstance(strategy, str):
strategy_type = MemoryOptimizationStrategyType.from_string(strategy)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need a from_string, it is an enum so MemoryOptimizationStrategyType[strategy] should work

current_tokens = strategy_instance.count_tokens(memories)
log_debug(f"Current token count: {current_tokens}, Target: {token_limit}")

if current_tokens <= token_limit:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bit overengineerd. Let the user decide themselves when they want to optimize. We can give them the util for counting the tokens if needed.

for opt_mem in optimized_memories:
if opt_mem.memory_id:
# Update existing memory
self.replace_user_memory(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe replace is upsert, so we should be able to just call that for all cases.

raise NotImplementedError

@abstractmethod
def optimize(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And aoptimize

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should move to the init of the strategies directory

from uuid import uuid4

from agno.db.schemas import UserMemory
from agno.memory.strategies.base import MemoryOptimizationStrategy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no base?

self,
memories: List[UserMemory],
model: Model,
user_id: Optional[str] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to be a parameter. You can just grab it from the first memory?

strategy: Union[
MemoryOptimizationStrategyType, MemoryOptimizationStrategy
] = MemoryOptimizationStrategyType.SUMMARIZE,
apply: bool = False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be true by default. That seems like the default behaviour and that a "dry run" should be opt-in?

mem for mem in memories if mem.memory_id and mem.memory_id not in optimized_memory_ids
]

# Delete removed memories
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we rather just clear all the user's memories and add the new ones? So it is a complete replace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is doing the same thing. It's looping through all the user's memory and then deleting them. Do you want me to call any specific function which already pre-exists?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants