147 lines
4.6 KiB
Python
147 lines
4.6 KiB
Python
from typing import List, Optional, Callable, Set
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from .entry import Entry
|
|
|
|
class WorkingMemory:
|
|
"""
|
|
Manages a collection of entries that represent the current state of the system.
|
|
|
|
The working memory stores different types of entries (scripts, reasoning, errors, etc.)
|
|
and provides methods to add, remove, and update them. It also generates XML context
|
|
representing the current state.
|
|
|
|
Notifies observers of entry additions, deletions and changes. During update(),
|
|
changes are batched and a single notification is sent after completion.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize an empty working memory."""
|
|
self._entries: List[Entry] = []
|
|
self._change_handlers: List[Callable[[], None]] = []
|
|
self._updating = False
|
|
self._changed_during_update: Set[Entry] = set()
|
|
|
|
def add_change_handler(self, handler: Callable[[], None]) -> None:
|
|
"""Add a callback for working memory changes."""
|
|
if handler not in self._change_handlers:
|
|
self._change_handlers.append(handler)
|
|
|
|
def _notify_change(self) -> None:
|
|
"""Notify all handlers of working memory changes."""
|
|
self._sort_entries()
|
|
for handler in self._change_handlers:
|
|
handler()
|
|
|
|
def _handle_entry_change(self, entry: Entry) -> None:
|
|
"""Handle changes from individual entries."""
|
|
if self._updating:
|
|
self._changed_during_update.add(entry)
|
|
else:
|
|
self._notify_change()
|
|
|
|
def _sort_entries(self) -> None:
|
|
"""Sort entries by ID in chronological order."""
|
|
self._entries.sort(key=lambda x: x.id)
|
|
|
|
def add_entry(self, entry: Entry) -> None:
|
|
"""
|
|
Add a new entry to working memory.
|
|
|
|
Args:
|
|
entry: Entry object to add
|
|
"""
|
|
if not isinstance(entry, Entry):
|
|
raise TypeError("Entry must be an instance of Entry class")
|
|
entry.add_change_handler(self._handle_entry_change)
|
|
self._entries.append(entry)
|
|
self._sort_entries()
|
|
self._notify_change()
|
|
|
|
def remove_entry(self, id: str) -> None:
|
|
"""
|
|
Remove an entry from working memory by its ID.
|
|
Ensures cleanup is performed before removal.
|
|
|
|
Args:
|
|
id: Unique identifier of entry to remove
|
|
"""
|
|
entry = self.get_entry(id)
|
|
if entry is not None:
|
|
entry.cleanup()
|
|
self._entries = [e for e in self._entries if e.id != id]
|
|
self._sort_entries()
|
|
self._notify_change()
|
|
|
|
def clear(self) -> None:
|
|
"""
|
|
Remove all entries from working memory.
|
|
Ensures cleanup is performed on all entries.
|
|
"""
|
|
for entry in self._entries:
|
|
entry.cleanup()
|
|
self._entries.clear()
|
|
self._notify_change()
|
|
|
|
def __del__(self):
|
|
"""Clean up all entries when memory is deleted."""
|
|
self._change_handlers.clear()
|
|
self.clear()
|
|
|
|
def get_entry(self, id: str) -> Optional[Entry]:
|
|
"""
|
|
Get an entry by its ID.
|
|
|
|
Args:
|
|
id: Unique identifier of entry to retrieve
|
|
|
|
Returns:
|
|
Entry if found, None otherwise
|
|
"""
|
|
for entry in self._entries:
|
|
if entry.id == id:
|
|
return entry
|
|
return None
|
|
|
|
def get_entries(self) -> List[Entry]:
|
|
"""
|
|
Get all entries in working memory.
|
|
|
|
Returns:
|
|
List[Entry]: List of all entries in chronological order
|
|
"""
|
|
return self._entries.copy()
|
|
|
|
def get_entries_count(self) -> int:
|
|
"""
|
|
Get the total number of entries.
|
|
|
|
Returns:
|
|
int: Number of entries in working memory
|
|
"""
|
|
return len(self._entries)
|
|
|
|
def update(self) -> None:
|
|
"""
|
|
Update all entries in working memory.
|
|
Batches change notifications and sends single update after completion.
|
|
"""
|
|
self._updating = True
|
|
self._changed_during_update.clear()
|
|
|
|
for entry in self._entries:
|
|
entry.update()
|
|
|
|
self._updating = False
|
|
if self._changed_during_update:
|
|
self._notify_change()
|
|
|
|
def generate_context(self) -> List[ET.Element]:
|
|
"""
|
|
Generate XML Elements representing all entries.
|
|
|
|
Returns:
|
|
List[ET.Element]: List of XML elements for each entry
|
|
"""
|
|
return [entry.generate_context() for entry in self._entries]
|