feat(memory): add JSON index and runtime memory_lookup/read/write tools

Gives Pyra an active memory brain: memory_index.json tracks summary +
keywords per file (like an inode table), and three built-in tools let
the AI look up, read, and overwrite memory mid-session. write_memory
accepts summary/keywords; update_index() merges the JSON index without
losing existing metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-17 23:01:54 +02:00
parent ad024807bc
commit e56e9779ec
4 changed files with 163 additions and 7 deletions
+80 -1
View File
@@ -17,7 +17,8 @@ from pyra.chat.renderer import (
from pyra.chat.planner import TaskPlanner
from pyra.config.manager import load_config
from pyra.config.schema import PyraConfig
from pyra.memory.reader import list_memories
from pyra.memory.reader import list_memories, lookup_memories, read_memory
from pyra.memory.writer import write_memory
from pyra.plugins.base import Tool
from pyra.plugins.executor import ToolExecutor
from pyra.plugins.registry import PluginRegistry
@@ -36,6 +37,32 @@ _STATIC_COMMANDS = {
}
def _handle_memory_lookup(query: str) -> str:
results = lookup_memories(query)
if not results:
return f"No memory entries found matching '{query}'."
lines = [
f"- {r['file']}: {r['summary']} (keywords: {', '.join(r['keywords'])})"
for r in results
]
return "\n".join(lines)
def _handle_memory_read(file: str) -> str:
try:
return read_memory(file)
except (FileNotFoundError, PermissionError) as exc:
return f"Error: {exc}"
def _handle_memory_write(file: str, content: str, summary: str, keywords: list) -> str:
try:
write_memory(file, content, summary=summary, keywords=list(keywords))
return f"Memory saved: {file}"
except (ValueError, PermissionError) as exc:
return f"Error: {exc}"
def start_chat() -> None:
try:
cfg = load_config()
@@ -77,6 +104,58 @@ def start_chat() -> None:
handler=planner.make_tool_handler(),
requires_approval=False,
))
registry.register_builtin(Tool(
name="memory_lookup",
description=(
"Search the memory index by keyword or topic. "
"Always call this BEFORE memory_write to check whether a matching entry already exists."
),
parameters={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Keyword or topic to search for."},
},
"required": ["query"],
},
handler=_handle_memory_lookup,
requires_approval=False,
))
registry.register_builtin(Tool(
name="memory_read",
description="Read the full content of a memory file by its relative path (e.g. 'user/profile.md').",
parameters={
"type": "object",
"properties": {
"file": {"type": "string", "description": "Relative path to the memory file."},
},
"required": ["file"],
},
handler=_handle_memory_read,
requires_approval=False,
))
registry.register_builtin(Tool(
name="memory_write",
description=(
"Write or overwrite a memory file. Always call memory_lookup first to avoid duplicates. "
"If an existing file covers the same topic, read it first and merge the content."
),
parameters={
"type": "object",
"properties": {
"file": {"type": "string", "description": "Relative path, e.g. 'user/profile.md' or 'knowledge/python_tips.md'."},
"content": {"type": "string", "description": "Full Markdown content to write."},
"summary": {"type": "string", "description": "One-sentence summary of what this memory file stores."},
"keywords": {
"type": "array",
"items": {"type": "string"},
"description": "Keywords for index lookup (38 terms).",
},
},
"required": ["file", "content", "summary", "keywords"],
},
handler=_handle_memory_write,
requires_approval=False,
))
history = ConversationHistory(cfg, registry)
session: PromptSession = PromptSession(