Skip to content

Memory module proposition - v2 #2744

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
wants to merge 4 commits into from

Conversation

colinfrisch
Copy link
Collaborator

@colinfrisch colinfrisch commented Apr 1, 2025

Memory PR upgrade

Based on the feedbacks from the previous PR (#2735) that I made on a proposition for a memory module in mesa (thanks again @EwoutH, @Corvince, @quaquel, if you also have insights for this one, im open to discussion !)

About the upgrade

Switching to a object format for optimized storage : MemoryEntry is now an object. One of the perks of using an object is not being obligated to have a uniqueID creation module as an object already has an intrinsic uniqueid id(my_object). It saves storage space as well as lines of code.

image

ShortTermMemory : more memory-efficient and reactive (efficient store and pop functionality)

  • Holds fixed number of recent MemoryEntry objects (automatic removing with dque)
  • Optimized for quick retrieval of recent interactions similar to the previous proposition : get(), get_by_entry_type(), etc.
  • Can be cleared/reset when an agent session ends

LongTermMemory : more computational-efficient (efficient navigation)

  • Hash map storage for better organization
  • Can be searched (soon, still working on it... I'd greatly appreciate your feedbacks and ideas)

Justification of memory transfering

tell_to() feature still here implemented directly in the memory (yay), renamed as communicate() to be more explicit
I see this as "communicating in general is just a way to make a memory of something (like a sentence) and transmitting it to another being". I understand it's unusual, but if the memory module works well, it could mean opening agent communication without building other complex modules. I'm very open to discussion, so please tell me if you have any suggestions regarding this way of doing things !

A couple of examples of the structure of the new memory

# Observation from environment
MemoryItem(
    entry_content="The space next to me looks crowded",
    entry_type = "coordinates",
    entry_step = 43,
    metadata={
        "conversation_id": "conv_789",
        "current_loc": agent.pos,
    }
)

# coordinates transfered from another agent
MemoryItem(
    entry_content= (1,3),
    entry_type = "coordinates",
    entry_step = 43,
    metadata={
        "external_agent_id" : external.id,
    }
)

Example usage

# Creating a memory for an agent
agent.memory = Memory(model=self.model, stm_capacity=10)

# Storing information
entry_content= "The space next to me looks crowded",
entry_type = "coordinates",
entry_step = 43,
entry_metadata={
    "conversation_id": "conv_789",
    "current_loc": agent.pos,
}

entry = agent.memory.remember_short_term(
    model,
    entry_content,
    entry_type,
    entry_step,
    entry_metadata
)

# Recalling information
position_data = agent.memory.recall(position_entry_id)

# Finding entries by type in the short term memory
all_position_entries = agent.memory.get_by_type_st("position", include_short_term = True, include_long_term = False,)

# Sharing memory with another agent
agent.memory.tell_to(position_id, external_agent)

# Removing an entry
agent.memory.forget(position_entry_id)

A little bit of performance analysis

The comparaison was done between the last memory structure (from #2735) and the new ShortTermMemory datastructure.

image

What I want to do next

  • I would like to transform get_by_entry_type into a more general search() method looking like this :
def search(self, query: str = None, entry_type: List[str] = None, 
            limit: int = 5) -> List[Tuple[MemoryEntry]]:
    """
    Search in short-term memory.
    Implementation could return the n-first entries with relevance scores based on :
        - Exact search
        - Substring search
        - Regex

    Useful for LLM-based agents, but not only. 
    I would 
    """
    return

Summary by CodeRabbit

  • New Features
    • Introduced a comprehensive memory management module that integrates immediate (short-term) and persistent (long-term) capabilities for improved agent performance.
    • Enhanced event handling with prioritized scheduling, efficient recording, and flexible filtering of memory entries to deliver more dynamic simulation behavior.
    • Optimized overall system efficiency by providing advanced control over memory events, ensuring more reliable and responsive agent interactions.

Copy link

coderabbitai bot commented Apr 1, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes introduce a new memory module within the Mesa experimental package. This update implements event management using a priority queue and weak references for handling simulation events. Additionally, it establishes a comprehensive memory management system with short-term and long-term memory for agents. New classes and methods support memory entry creation, retrieval, filtering, consolidation, serialization, and communication. Public entities such as MemoryEntry, Memory, ShortTermMemory, and LongTermMemory are now exported, providing a structured way to manage and schedule events and memory logs in simulations.

Changes

File(s) Change Summary
mesa/exp.../memory/__init__.py Introduces event management functionality including a Priority enumeration, SimulationEvent class, and an EventList (a heap-based priority queue) for event scheduling and cancellation (using weak references). Exports key memory-related entities.
mesa/exp.../memory/memory.py Implements a memory management system for agents. Adds MemoryEntry (with serialization/deserialization), ShortTermMemory (deque-based FIFO operations), LongTermMemory (dictionary-based management), and an integrated Memory class. Provides methods to add, retrieve, filter, forget, consolidate, and communicate memory entries.

Sequence Diagram(s)

sequenceDiagram
    participant Agent
    participant Memory
    participant STM as ShortTermMemory
    participant LTM as LongTermMemory

    Agent->>Memory: remember_short_term(entry)
    Memory->>STM: add(entry)
    STM-->>Memory: confirmation
    Agent->>Memory: remember_long_term(entry)
    Memory->>LTM: add(entry)
    LTM-->>Memory: confirmation
    Agent->>Memory: consolidate(entry)
    Memory->>STM: remove(entry)
    Memory->>LTM: add(entry)
Loading
sequenceDiagram
    participant Simulation
    participant EventManager as EventList
    participant Event as SimulationEvent

    Simulation->>EventManager: schedule event (with priority)
    EventManager->>Event: initialize & manage event
    Simulation->>EventManager: cancel event request
    EventManager-->>Simulation: confirm cancellation
Loading

Poem

I'm a rabbit in a code-filled maze,
Hopping through memory in innovative ways.
Events and entries in a neat parade,
In heaps and deques, all perfectly made.
I cheer these changes with a joyful bound,
Every byte and beat makes my heart pound!
🐇💻✨


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
mesa/experimental/memory/__init__.py (2)

5-5: Fix the “dque” typo in the docstring.

Line 5 refers to “dque data structure,” but the correct term is “deque.”

- (dque data structure)
+ (deque data structure)

23-25: Consider adding tests for the module exports.

Static analysis shows these lines are uncovered by tests. If practical, add a lightweight test (e.g., verifying imports behave as expected) to maintain or increase coverage.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 23-23: mesa/experimental/memory/init.py#L23
Added line #L23 was not covered by tests


[warning] 25-25: mesa/experimental/memory/init.py#L25
Added line #L25 was not covered by tests

mesa/experimental/memory/memory.py (3)

3-5: Correct the docstring grammar.

“Then objective” should be “The objective” or “Its objective.”

- Then objective is to implement a very simple and efficient system...
+ The objective is to implement a very simple and efficient system...

24-24: Consider adding a __repr__ method to ease debugging.

When working with logs or interactive sessions, a customized __repr__ for MemoryEntry can make debugging easier.

 class MemoryEntry:
     ...
+    def __repr__(self) -> str:
+        return f"<MemoryEntry type={self.entry_type}, step={self.entry_step}>"
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 24-24: mesa/experimental/memory/memory.py#L24
Added line #L24 was not covered by tests


19-287: Add unit tests for uncovered lines.

Static analysis indicates these lines lack test coverage. Consider creating tests for core functionalities like forget, get_by_type, and the memory consolidation flow.

Would you like help generating sample test cases to increase overall coverage?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 19-21: mesa/experimental/memory/memory.py#L19-L21
Added lines #L19 - L21 were not covered by tests


[warning] 24-24: mesa/experimental/memory/memory.py#L24
Added line #L24 was not covered by tests


[warning] 27-27: mesa/experimental/memory/memory.py#L27
Added line #L27 was not covered by tests


[warning] 35-38: mesa/experimental/memory/memory.py#L35-L38
Added lines #L35 - L38 were not covered by tests


[warning] 40-40: mesa/experimental/memory/memory.py#L40
Added line #L40 was not covered by tests


[warning] 42-42: mesa/experimental/memory/memory.py#L42
Added line #L42 was not covered by tests


[warning] 49-50: mesa/experimental/memory/memory.py#L49-L50
Added lines #L49 - L50 were not covered by tests


[warning] 52-52: mesa/experimental/memory/memory.py#L52
Added line #L52 was not covered by tests


[warning] 58-58: mesa/experimental/memory/memory.py#L58
Added line #L58 was not covered by tests


[warning] 61-61: mesa/experimental/memory/memory.py#L61
Added line #L61 was not covered by tests


[warning] 67-67: mesa/experimental/memory/memory.py#L67
Added line #L67 was not covered by tests


[warning] 69-71: mesa/experimental/memory/memory.py#L69-L71
Added lines #L69 - L71 were not covered by tests


[warning] 73-73: mesa/experimental/memory/memory.py#L73
Added line #L73 was not covered by tests


[warning] 83-84: mesa/experimental/memory/memory.py#L83-L84
Added lines #L83 - L84 were not covered by tests


[warning] 86-87: mesa/experimental/memory/memory.py#L86-L87
Added lines #L86 - L87 were not covered by tests


[warning] 93-94: mesa/experimental/memory/memory.py#L93-L94
Added lines #L93 - L94 were not covered by tests


[warning] 96-96: mesa/experimental/memory/memory.py#L96
Added line #L96 was not covered by tests


[warning] 98-98: mesa/experimental/memory/memory.py#L98
Added line #L98 was not covered by tests


[warning] 100-100: mesa/experimental/memory/memory.py#L100
Added line #L100 was not covered by tests


[warning] 102-102: mesa/experimental/memory/memory.py#L102
Added line #L102 was not covered by tests


[warning] 104-104: mesa/experimental/memory/memory.py#L104
Added line #L104 was not covered by tests


[warning] 106-106: mesa/experimental/memory/memory.py#L106
Added line #L106 was not covered by tests


[warning] 108-108: mesa/experimental/memory/memory.py#L108
Added line #L108 was not covered by tests


[warning] 110-111: mesa/experimental/memory/memory.py#L110-L111
Added lines #L110 - L111 were not covered by tests


[warning] 113-113: mesa/experimental/memory/memory.py#L113
Added line #L113 was not covered by tests


[warning] 116-117: mesa/experimental/memory/memory.py#L116-L117
Added lines #L116 - L117 were not covered by tests


[warning] 119-119: mesa/experimental/memory/memory.py#L119
Added line #L119 was not covered by tests


[warning] 121-121: mesa/experimental/memory/memory.py#L121
Added line #L121 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5166670 and 34e991a.

📒 Files selected for processing (2)
  • mesa/experimental/memory/__init__.py (1 hunks)
  • mesa/experimental/memory/memory.py (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
mesa/experimental/memory/__init__.py (1)
mesa/experimental/memory/memory.py (4)
  • LongTermMemory (148-204)
  • Memory (207-286)
  • MemoryEntry (24-58)
  • ShortTermMemory (61-145)
🪛 GitHub Check: codecov/patch
mesa/experimental/memory/__init__.py

[warning] 23-23: mesa/experimental/memory/init.py#L23
Added line #L23 was not covered by tests


[warning] 25-25: mesa/experimental/memory/init.py#L25
Added line #L25 was not covered by tests

mesa/experimental/memory/memory.py

[warning] 19-21: mesa/experimental/memory/memory.py#L19-L21
Added lines #L19 - L21 were not covered by tests


[warning] 24-24: mesa/experimental/memory/memory.py#L24
Added line #L24 was not covered by tests


[warning] 27-27: mesa/experimental/memory/memory.py#L27
Added line #L27 was not covered by tests


[warning] 35-38: mesa/experimental/memory/memory.py#L35-L38
Added lines #L35 - L38 were not covered by tests


[warning] 40-40: mesa/experimental/memory/memory.py#L40
Added line #L40 was not covered by tests


[warning] 42-42: mesa/experimental/memory/memory.py#L42
Added line #L42 was not covered by tests


[warning] 49-50: mesa/experimental/memory/memory.py#L49-L50
Added lines #L49 - L50 were not covered by tests


[warning] 52-52: mesa/experimental/memory/memory.py#L52
Added line #L52 was not covered by tests


[warning] 58-58: mesa/experimental/memory/memory.py#L58
Added line #L58 was not covered by tests


[warning] 61-61: mesa/experimental/memory/memory.py#L61
Added line #L61 was not covered by tests


[warning] 67-67: mesa/experimental/memory/memory.py#L67
Added line #L67 was not covered by tests


[warning] 69-71: mesa/experimental/memory/memory.py#L69-L71
Added lines #L69 - L71 were not covered by tests


[warning] 73-73: mesa/experimental/memory/memory.py#L73
Added line #L73 was not covered by tests


[warning] 83-84: mesa/experimental/memory/memory.py#L83-L84
Added lines #L83 - L84 were not covered by tests


[warning] 86-87: mesa/experimental/memory/memory.py#L86-L87
Added lines #L86 - L87 were not covered by tests


[warning] 93-94: mesa/experimental/memory/memory.py#L93-L94
Added lines #L93 - L94 were not covered by tests


[warning] 96-96: mesa/experimental/memory/memory.py#L96
Added line #L96 was not covered by tests


[warning] 98-98: mesa/experimental/memory/memory.py#L98
Added line #L98 was not covered by tests


[warning] 100-100: mesa/experimental/memory/memory.py#L100
Added line #L100 was not covered by tests


[warning] 102-102: mesa/experimental/memory/memory.py#L102
Added line #L102 was not covered by tests


[warning] 104-104: mesa/experimental/memory/memory.py#L104
Added line #L104 was not covered by tests


[warning] 106-106: mesa/experimental/memory/memory.py#L106
Added line #L106 was not covered by tests


[warning] 108-108: mesa/experimental/memory/memory.py#L108
Added line #L108 was not covered by tests


[warning] 110-111: mesa/experimental/memory/memory.py#L110-L111
Added lines #L110 - L111 were not covered by tests


[warning] 113-113: mesa/experimental/memory/memory.py#L113
Added line #L113 was not covered by tests


[warning] 116-117: mesa/experimental/memory/memory.py#L116-L117
Added lines #L116 - L117 were not covered by tests


[warning] 119-119: mesa/experimental/memory/memory.py#L119
Added line #L119 was not covered by tests


[warning] 121-121: mesa/experimental/memory/memory.py#L121
Added line #L121 was not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build (windows, 3.13)
  • GitHub Check: run-benchmarks
🔇 Additional comments (2)
mesa/experimental/memory/__init__.py (1)

14-21: Confirm reference to event scheduling components.

The docstring mentions Priority, SimulationEvent, and EventList, but this file does not define or import them. Please verify if these references are part of another module or have yet to be implemented here.

mesa/experimental/memory/memory.py (1)

73-80:

❓ Verification inconclusive

Verify the repeated model parameter usage.

Each add method (in both ShortTermMemory and LongTermMemory) plus the remember_short_term/remember_long_term methods accept a model parameter but also store self.model. Confirm if passing model explicitly is necessary or if using self.model suffices. Removing the extra parameter could simplify the API.

Also applies to: 159-166, 220-226


Action: Review and Clarify model Parameter Usage

  • In mesa/experimental/memory/memory.py, the add method (lines 73–80) accepts a model parameter even though the instance already maintains self.model. This pattern is repeated in the corresponding remember_short_term and remember_long_term methods (lines 159–166 and 220–226).
  • Please verify whether the intent is to allow callers to override the stored self.model on a per-call basis. If not, refactoring the API to rely solely on self.model could simplify the implementation and reduce ambiguity.
  • Reconcile the design across both the short-term and long-term memory interfaces to ensure consistency.
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 73-73: mesa/experimental/memory/memory.py#L73
Added line #L73 was not covered by tests

Copy link

github-actions bot commented Apr 1, 2025

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔴 +5.0% [+3.9%, +6.1%] 🔵 -0.8% [-1.0%, -0.6%]
BoltzmannWealth large 🔵 -0.3% [-0.9%, +0.4%] 🔵 +1.9% [-1.4%, +5.8%]
Schelling small 🔵 -0.5% [-0.7%, -0.3%] 🔵 -0.1% [-0.2%, +0.0%]
Schelling large 🔵 -0.0% [-0.4%, +0.4%] 🔵 -1.1% [-2.5%, +0.1%]
WolfSheep small 🔵 +0.7% [+0.2%, +1.1%] 🔵 -2.5% [-2.6%, -2.3%]
WolfSheep large 🔵 +0.2% [-0.4%, +0.8%] 🔵 -0.6% [-1.6%, +0.6%]
BoidFlockers small 🔵 -1.7% [-2.2%, -1.3%] 🔵 -1.0% [-1.2%, -0.8%]
BoidFlockers large 🔵 -2.2% [-2.8%, -1.8%] 🔵 -1.0% [-1.2%, -0.7%]

@wang-boyu
Copy link
Member

Thanks for the extensive work on this memory module. I had a similar question as @Corvince mentioned in the first PR (#2735 (comment)), about how this can be useful or necessary for all agent types in general. I understand that it is commonly used in LLM-based agents, but I'm trying to understand it from a more general perspective. For example, in our gis examples Rainfall and Urban Growth, agents are raindrops and land parcels. How do these inanimate objects have memories?

Would it make sense to relate the current design of memory module, to the history of agents' attributes? Or if they are not the same, what's the difference between the two?

There is an experimental feature for observables: #2291. Imagine I set pos (i.e., agents' position) as an observable and I save each agent's past positions as a list inside each one of them. What's the difference of using the observable feature and the memory module here? Is it possible to combine this two together?

@colinfrisch
Copy link
Collaborator Author

colinfrisch commented Apr 2, 2025

I see what you mean @wang-boyu. I didn't really design it to fit in all scenarios the same way we don't necessarily us mesa signals for all simulations. The idea was to give the users a new tool and open a simple new range of experimentations with this. Examples I gave in answer to Corvince's feedback were :

  • prey/predator models where remembering certain areas could be useful to the agent (and allow to model patterns like migrations, etc.)
  • Basic social interaction models, where remembering who is trustworthy are not could also allow to model a few interesting effects (social networks forming, basic approach strategies, etc.)

These two examples use basic memory (and communication), but require the agents to have individual memories to make it interesting. The memory sharing feature also sets the base for communication without having to build another complicated module. I understand that it would be particularly useful with LLMs, but my opinion is that it can go beyond that.

I also think that it could completely be compatible with the observables presented in #2291, I'll try to include this in the next commit for this PR !

I'll definitely try to make a model more complex than the foraging ants one to try to show more complex agent patterns with this memory module in the near future !

@EwoutH
Copy link
Member

EwoutH commented Apr 2, 2025

@colinfrisch I appreciate your continued work on this!

I’m convinced that these use cases are relevant and a good fit for Mesa.

I’m not convinced (yet) that we need a completely new module. What would really help me is two examples, in which you for each:

  • Show the most elegant way it can be done with current Mesa / Python
  • Show show it can be done with the new memory module

That would allow me to weigh the proportionality of the added complexity vs the user benefits.

@colinfrisch colinfrisch force-pushed the memory_module branch 2 times, most recently from 4f27d88 to 8982579 Compare April 2, 2025 14:52
@EwoutH
Copy link
Member

EwoutH commented Apr 7, 2025

Are you still working on this PR? Can I help move it forward?

@colinfrisch
Copy link
Collaborator Author

Are you still working on this PR? Can I help move it forward?

I'm currently working on a model using this memory module to compare it with the same model without. I should finish it by the end of the week. If it's not concluding, I'll post a recap of what can be learned from this PR and close it.

@EwoutH
Copy link
Member

EwoutH commented Apr 10, 2025

Awesome, very curious!

@colinfrisch
Copy link
Collaborator Author

Back with some insights from building more models depending on agents storing and exchanging information.

I built a virus vs antibody model to simulate an immune reaction declined as a confrontation between antibody agents and virus agents. The antibodies use a short term and a long term memory. I find the result quite interesting, and I'll open a PR to mesa_examples soon, but it's already available on my model repository if you want to take a look.

The conclusion I had is that indeed, a complex memory module is not necessary for such models because memory in agents can simply be created in the init method, and they usually don't need complex retrieval features or very different types of storing :

self.st_memory : deque = deque()
self.lt_memory : list = []

These experimentations also helped me to reflect on the added value of this memory module. All in all, it has some, but it resides in :

  • Easily storing extensive metadata for each souvenir (type, and other metadata chosen by the user)
  • The ability to store very different types of information in a homogenous way
  • The searching features (get_by_type, search with keywords, etc.)
  • The efficient memory structure and performance overhead in case of extensive storing of different big memory entries (cf. figure in the PR comment comparing the new short-term memory structure to the old one)

Which are not necessarily needed in most basic agent simulations.

Overall, it’s really what we are looking for in terms of memory LLM because they will need memories of very different types to be easily retrievable (text, reflexions, coordinates, states, etc.).

Thanks again @EwoutH and @wang-boyu for your help and feedbacks, I'm closing this PR until further notice.

@EwoutH
Copy link
Member

EwoutH commented Apr 15, 2025

Thanks for the detailed update!

Have you discovered any feature that might be missing or worth adding to the AgentSet?

@colinfrisch
Copy link
Collaborator Author

Have you discovered any feature that might be missing or worth adding to the AgentSet?

@EwoutH I actually struggled quite a bit in terms of agent referencing (problems with agents still referenced even after being removed). I don't know yet if it's specific to the way I built my model ou to the way that weak referencing is handled in the AgentSet class, but I'll definitely look into it.

Also, from what I understood, we have to create a new AgentSet each time that we want to create new agents in the simulation : for example, in the virus/antibody model, the agents can duplicate under certain conditions. There is already an _update method in AgentSet, but it is private. Could it be interesting, or simply cleaner for agent organisation and statistics, to be able to add agents to a specific AgentSet (to have only 2 big AgentSets in my model : one for the antibodies, one for the viruses) ? Is there a feature that I am missing ?

@EwoutH
Copy link
Member

EwoutH commented Apr 15, 2025

model.agents is initialized by default and handled fully automatically. You shouldn’t (have to) modify it manually. If you find a reproduceable bug, please report.

Aside from that, you can assign any agent to any agentset whenever you want. Have you found bugs there or are missing features?

Some (reproduceable) code examples would really help!

@colinfrisch
Copy link
Collaborator Author

colinfrisch commented Apr 16, 2025

Aside from that, you can assign any agent to any agentset whenever you want. Have you found bugs there or are missing features?

Alright, definitely missed that. I had a not very clean but working code so I thought that was it. Definitely going to modify this, thanks for the help !

model.agents is initialized by default and handled fully automatically. You shouldn’t (have to) modify it manually. If you find a reproduceable bug, please report.

I'm going to refactor my code to hadle agents more cleanly in their AgentSets. If I still have issues with agent referencing that don't appear to be linked to my code, I will make a reproducible example.

@quaquel
Copy link
Member

quaquel commented Apr 16, 2025

Just a couple of pointers which might be helpfult.

  1. You can get all agents in a model via model.agents
  2. You can get all agents of a particular class via model.agents_by_type[Wolf]
  3. Explicitly use agent.remove() when an agent is "killed"

In my experience, with these 3 pointers, it is rare that you need to manage your own AgentSet instances.

@colinfrisch
Copy link
Collaborator Author

@EwoutH @quaquel thanks for the tips !
After a little digging, I found out that my errors came from the fact that i stored the agents objects in other agents as targets (first_agent.target = second_agent), but when the second agent is removed, first_agent.target is still the second_agent object.

If fixed it directly into my program, but I don't know if it's something that could be handled directly in the mesa code (since the second agent doesn't exist, could there be a code snipped turning all the references pointing to him to None ?).

I also built a small program to reproduce the error if you are interested. Don't hesitate to let me know if you have some feedback !

import os
import sys
from mesa import Model
from mesa.experimental.continuous_space import ContinuousSpace, ContinuousSpaceAgent

class MyAgent(ContinuousSpaceAgent):
    def __init__(self, model, space):
        super().__init__(model=model, space=space)
        self.position = (0, 0)
        self.target = None
    
    def step(self):
        # Find a target for agent 1
        if self.target is None and len(self.model.agents_list) > 1:
            self.target = [a for a in self.model.agents_list if a != self][0]
            print(f"Agent {self.unique_id} targets {self.target.unique_id}")
        
        # Show status and remove if first agent
        if self.target:
            print(f"Target space: {self.target.space}")
            if self.unique_id == 1 and self.target.space is not None:
                print(f"Removing target")
                self.model.agents_list.remove(self.target)
                self.target.remove()
                # We don't set self.target = None - this causes the error
            
            # Detect the error condition
            if self.target and self.target.space is None:
                print(f"Target exists but space is None ***********************")

class MyModel(Model):
    def __init__(self):
        super().__init__()
        self.space = ContinuousSpace([[0, 10], [0, 10]], torus=True, random=self.random)
        self.agents_list = []
        
        # Create 2 agents
        for _ in range(2):
            agent = MyAgent(self, self.space)
            self.agents_list.append(agent)
    
    def step(self):
        for agent in list(self.agents_list):
            agent.step()

# Run the model
if __name__ == "__main__":
    model = MyModel()
    model.step()  # First agent targets second agent

@quaquel
Copy link
Member

quaquel commented Apr 16, 2025

thanks for the example. The simplest solution is to turn target into a weakref: target = weakref.ref(some_agent). If you want to access the agent you need to do if (agent := target()) is not None: and then you are fine. In this way, agents can still be removed from the model as normal while all weakrefs resolve to none once the weak referenced agent has been removed. The other solution is that agents know who holds a reference to them and extend remove so that all those references are cleared.

Let me know if this is not clear enough. It took me a while to get my head around weakrefs when I first learned about them.

as an aside, in the example you don't need the agent_list. Just replace it with model.agents inside the agent class.

@colinfrisch
Copy link
Collaborator Author

colinfrisch commented Apr 16, 2025

@quaquel
Alright I get it, thanks ! I had encountered this concept of weak referencing before in the mesa docs but didn't get the full sense of it...
Do you think that it could be an interesting idea to explore the automating of weak referencing when a user makes an agent refer to another, or are there cases were "strong" referencing is needed ?

(Thank you also for the model.agents tip - used it a few times while building but it slipped out of my mind)

@quaquel
Copy link
Member

quaquel commented Apr 16, 2025

There is, as far as I know, no way of making refs weak automatically. However, it might be good to cover weakrefs and the importance of agent.remove() and how to extend this yourself in the docs under best practices.

@colinfrisch
Copy link
Collaborator Author

There is, as far as I know, no way of making refs weak automatically. However, it might be good to cover weakrefs and the importance of agent.remove() and how to extend this yourself in the docs under best practices.

Alright ! I'm going to work on finding useful tips to write about it in the docs, and I'll open a PR if I do encounter interesting things.

@quaquel
Copy link
Member

quaquel commented Apr 17, 2025

Ok, if I have some time I might also open a PR specifically on best practices regarding weakrefs, model.agents, and agent.remove.

@colinfrisch
Copy link
Collaborator Author

@EwoutH
I initiated a PR (mesa-examples#253) with a model that I have developed using weak referencing. It also showcases the use of memory in agents, and give results that I find quite interesting.

@quaquel if you plan to do a PR on weak referencing, it could even mention this example.

I'd love to hear your feedbacks on it if you have time :)

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants