feat: add on-demand memory storage and recall tools#193
Conversation
Signed-off-by: kyteinsky <kyteinsky@gmail.com> Assisted-by: Github Copilot:qwen-3-6-35b-a3b Assisted-by: Github Copilot:claude-sonnet-4-6
|
do we want specific tests for this feature in the integration tests? |
Signed-off-by: kyteinsky <kyteinsky@gmail.com>
Signed-off-by: kyteinsky <kyteinsky@gmail.com>
|
Nice! We can now make the memory tools dependent on the assistant app being installed and it should be ok |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new “Memories” toolset that lets the agent persist, browse, load, delete, and (optionally) semantically search user-scoped memory files stored under the Assistant folder in Nextcloud. It also updates TaskProcessing output validation to support the new Context Chat search output shape.
Changes:
- Added
ex_app/lib/all_tools/memory.pyimplementing memory storage/recall tools (list/load/store/delete/delete-folder/search). - Added DAV folder-creation logic to ensure the Memories base folder and subfolders exist when storing memories.
- Updated TaskProcessing output-key validation to accept
sourcesoutputs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| ex_app/lib/all_tools/memory.py | Adds new memory management tools + path validation + DAV folder creation + Context Chat-backed semantic search. |
| ex_app/lib/all_tools/lib/task_processing.py | Extends accepted TaskProcessing output keys to include sources for Context Chat search results. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| path_parts = [p for p in decoded.strip('/').split('/') if p] | ||
| if len(path_parts) > MAX_MEMORY_FOLDER_DEPTH + 1: # +1 for the filename itself | ||
| raise AgentFacingError(f'Memory path exceeds maximum depth of {MAX_MEMORY_FOLDER_DEPTH}') |
| base_parts = [p for p in base_memories_path.split('/') if p] | ||
| all_parts = base_parts + scoped_folder_parts | ||
| for i in range(len(base_parts) + 1, len(all_parts) + 1): | ||
| folder_path = '/'.join(all_parts[:i]) |
| fsnode = await files_handle.by_id(file_id) | ||
| filepath = '/' + fsnode.user_path.removeprefix(prefix).rstrip('/') | ||
|
|
||
| if fsnode is None: | ||
| await log(nc, logging.WARNING, f'Could not fetch file by id: {file_id}') | ||
| return {'path': filepath, 'content': ''} |
| try: | ||
| full_path, _ = __validate_memory_path(path, memories_folder_path, allow_folder_path=True) |
|
|
||
| if not isinstance(task.output, dict) or all(x not in ["file", "output", "images", "slide_deck"] for x in task.output): | ||
| if not isinstance(task.output, dict) or all(x not in ["file", "output", "images", "slide_deck", "sources"] for x in task.output): | ||
| raise Exception('"output" key not found in Nextcloud TaskProcessing task result') |
| async def __is_context_chat_available(nc: AsyncNextcloudApp, memories_folder_path: str): | ||
| tasktypes = (await nc.ocs('GET', '/ocs/v2.php/taskprocessing/tasktypes'))['types'].keys() | ||
| return CONTEXT_CHAT_SEARCH_TASK_TYPE in tasktypes |
|
Works nicely. I didn't test the context_chat integration though, because I don't want to set it up 😇 |
needs nextcloud/context_chat#247
🤖 AI (if applicable)