Compare commits
3 Commits
main
...
feat/mempa
| Author | SHA1 | Date | |
|---|---|---|---|
| 00a8221088 | |||
| ec501b7d7b | |||
| 41b69d63a8 |
@@ -158,6 +158,7 @@ def _discover_tools():
|
||||
"tools.send_message_tool",
|
||||
# "tools.honcho_tools", # Removed — Honcho is now a memory provider plugin
|
||||
"tools.homeassistant_tool",
|
||||
"tools.mempalace_tool",
|
||||
]
|
||||
import importlib
|
||||
for mod_name in _modules:
|
||||
|
||||
43
run_agent.py
43
run_agent.py
@@ -1027,6 +1027,7 @@ class AIAgent:
|
||||
self._memory_nudge_interval = 10
|
||||
self._memory_flush_min_turns = 6
|
||||
self._turns_since_memory = 0
|
||||
self._turns_since_palace = 0
|
||||
self._iters_since_skill = 0
|
||||
if not skip_memory:
|
||||
try:
|
||||
@@ -1035,6 +1036,7 @@ class AIAgent:
|
||||
self._user_profile_enabled = mem_config.get("user_profile_enabled", False)
|
||||
self._memory_nudge_interval = int(mem_config.get("nudge_interval", 10))
|
||||
self._memory_flush_min_turns = int(mem_config.get("flush_min_turns", 6))
|
||||
self._palace_nudge_interval = int(mem_config.get("palace_nudge_interval", 15))
|
||||
if self._memory_enabled or self._user_profile_enabled:
|
||||
from tools.memory_tool import MemoryStore
|
||||
self._memory_store = MemoryStore(
|
||||
@@ -1797,6 +1799,16 @@ class AIAgent:
|
||||
"If nothing is worth saving, just say 'Nothing to save.' and stop."
|
||||
)
|
||||
|
||||
|
||||
_PALACE_REVIEW_PROMPT = (
|
||||
"Review the conversation above and archive a summary of the key events, "
|
||||
"decisions, and outcomes to the MemPalace.\n\n"
|
||||
"This ensures that future sessions can recall the context of this work "
|
||||
"even after the current session history is lost.\n\n"
|
||||
"Use the mempalace tool with action='record' and room='nexus' or 'workspace'. "
|
||||
"Include a concise but informative summary of what was achieved."
|
||||
)
|
||||
|
||||
_COMBINED_REVIEW_PROMPT = (
|
||||
"Review the conversation above and consider two things:\n\n"
|
||||
"**Memory**: Has the user revealed things about themselves — their persona, "
|
||||
@@ -1816,6 +1828,7 @@ class AIAgent:
|
||||
messages_snapshot: List[Dict],
|
||||
review_memory: bool = False,
|
||||
review_skills: bool = False,
|
||||
review_palace: bool = False,
|
||||
) -> None:
|
||||
"""Spawn a background thread to review the conversation for memory/skill saves.
|
||||
|
||||
@@ -1826,13 +1839,20 @@ class AIAgent:
|
||||
"""
|
||||
import threading
|
||||
|
||||
|
||||
# Pick the right prompt based on which triggers fired
|
||||
if review_memory and review_skills:
|
||||
prompt = self._COMBINED_REVIEW_PROMPT
|
||||
elif review_memory:
|
||||
prompt = self._MEMORY_REVIEW_PROMPT
|
||||
else:
|
||||
prompt = self._SKILL_REVIEW_PROMPT
|
||||
prompts = []
|
||||
if review_memory: prompts.append(self._MEMORY_REVIEW_PROMPT)
|
||||
if review_skills: prompts.append(self._SKILL_REVIEW_PROMPT)
|
||||
if review_palace: prompts.append(self._PALACE_REVIEW_PROMPT)
|
||||
|
||||
if not prompts:
|
||||
return
|
||||
|
||||
prompt = "\n\n".join(prompts)
|
||||
if len(prompts) > 1:
|
||||
prompt = "Review the conversation above and perform the following tasks:\n\n" + prompt
|
||||
|
||||
|
||||
def _run_review():
|
||||
import contextlib, os as _os
|
||||
@@ -6151,6 +6171,13 @@ class AIAgent:
|
||||
# Reset nudge counters
|
||||
if function_name == "memory":
|
||||
self._turns_since_memory = 0
|
||||
|
||||
if (self._palace_nudge_interval > 0
|
||||
and "mempalace" in self.valid_tool_names):
|
||||
self._turns_since_palace += 1
|
||||
if self._turns_since_palace >= self._palace_nudge_interval:
|
||||
_should_review_palace = True
|
||||
self._turns_since_palace = 0
|
||||
elif function_name == "skill_manage":
|
||||
self._iters_since_skill = 0
|
||||
|
||||
@@ -7002,6 +7029,7 @@ class AIAgent:
|
||||
# Skill trigger is checked AFTER the agent loop completes, based on
|
||||
# how many tool iterations THIS turn used.
|
||||
_should_review_memory = False
|
||||
_should_review_palace = False
|
||||
if (self._memory_nudge_interval > 0
|
||||
and "memory" in self.valid_tool_names
|
||||
and self._memory_store):
|
||||
@@ -9155,12 +9183,13 @@ class AIAgent:
|
||||
|
||||
# Background memory/skill review — runs AFTER the response is delivered
|
||||
# so it never competes with the user's task for model attention.
|
||||
if final_response and not interrupted and (_should_review_memory or _should_review_skills):
|
||||
if final_response and not interrupted and (_should_review_memory or _should_review_skills or _should_review_palace):
|
||||
try:
|
||||
self._spawn_background_review(
|
||||
messages_snapshot=list(messages),
|
||||
review_memory=_should_review_memory,
|
||||
review_skills=_should_review_skills,
|
||||
review_palace=_should_review_palace,
|
||||
)
|
||||
except Exception:
|
||||
pass # Background review is best-effort
|
||||
|
||||
133
tools/mempalace_tool.py
Normal file
133
tools/mempalace_tool.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
mempalace_tool.py — Interface for the MemPalace vector memory system.
|
||||
|
||||
This tool allows the agent to search and record memories in a ChromaDB-backed
|
||||
vector database via the fleet_api.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
from tools.registry import registry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default to the fleet_api port defined in the-nexus
|
||||
MEMPALACE_API_URL = os.environ.get("MEMPALACE_API_URL", "http://127.0.0.1:7771")
|
||||
|
||||
def mempalace_tool(
|
||||
action: str,
|
||||
query: str = "",
|
||||
text: str = "",
|
||||
room: Optional[str] = None,
|
||||
wing: Optional[str] = None,
|
||||
n_results: int = 10,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Interface for the MemPalace vector memory system.
|
||||
|
||||
Actions:
|
||||
search: Search the palace for relevant memories.
|
||||
record: Add a new memory to the palace.
|
||||
wings: List available wizard wings.
|
||||
"""
|
||||
try:
|
||||
if action == "search":
|
||||
if not query:
|
||||
return json.dumps({"success": False, "error": "Query is required for search."})
|
||||
|
||||
params = {"q": query, "n": n_results}
|
||||
if room:
|
||||
params["room"] = room
|
||||
|
||||
response = requests.get(f"{MEMPALACE_API_URL}/search", params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
return json.dumps({"success": True, "data": response.json()})
|
||||
|
||||
elif action == "record":
|
||||
if not text:
|
||||
return json.dumps({"success": False, "error": "Text is required for record."})
|
||||
|
||||
payload = {
|
||||
"text": text,
|
||||
"room": room or "general",
|
||||
"wing": wing,
|
||||
"metadata": metadata or {}
|
||||
}
|
||||
|
||||
response = requests.post(f"{MEMPALACE_API_URL}/record", json=payload, timeout=10)
|
||||
response.raise_for_status()
|
||||
return json.dumps({"success": True, "data": response.json()})
|
||||
|
||||
elif action == "wings":
|
||||
response = requests.get(f"{MEMPALACE_API_URL}/wings", timeout=10)
|
||||
response.raise_for_status()
|
||||
return json.dumps({"success": True, "data": response.json()})
|
||||
|
||||
else:
|
||||
return json.dumps({"success": False, "error": f"Unknown action: {action}"})
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
return json.dumps({
|
||||
"success": False,
|
||||
"error": f"Could not connect to MemPalace API at {MEMPALACE_API_URL}. Ensure fleet_api.py is running."
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"MemPalace tool error: {e}")
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
MEMPALACE_SCHEMA = {
|
||||
"name": "mempalace",
|
||||
"description": (
|
||||
"Search or record memories in the MemPalace vector database. "
|
||||
"Use this for long-term, high-volume memory that exceeds the curated memory limits. "
|
||||
"The palace is organized into 'rooms' (e.g., forge, hermes, nexus, issues, experiments)."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["search", "record", "wings"],
|
||||
"description": "The action to perform."
|
||||
},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "The search query (required for 'search')."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The memory text to record (required for 'record')."
|
||||
},
|
||||
"room": {
|
||||
"type": "string",
|
||||
"description": "Optional room filter or target room (e.g., forge, hermes, nexus, issues, experiments)."
|
||||
},
|
||||
"wing": {
|
||||
"type": "string",
|
||||
"description": "Optional wizard wing filter."
|
||||
},
|
||||
"n_results": {
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Maximum number of results to return."
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"description": "Optional metadata for the memory (only for 'record')."
|
||||
}
|
||||
},
|
||||
"required": ["action"]
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
name="mempalace",
|
||||
toolset="mempalace",
|
||||
schema=MEMPALACE_SCHEMA,
|
||||
handler=lambda args, **kw: mempalace_tool(**args),
|
||||
emoji="🏛️",
|
||||
)
|
||||
Reference in New Issue
Block a user