Compare commits
3 Commits
fix/syntax
...
feat/mempa
| Author | SHA1 | Date | |
|---|---|---|---|
| 00a8221088 | |||
| ec501b7d7b | |||
| 41b69d63a8 |
@@ -158,6 +158,7 @@ def _discover_tools():
|
|||||||
"tools.send_message_tool",
|
"tools.send_message_tool",
|
||||||
# "tools.honcho_tools", # Removed — Honcho is now a memory provider plugin
|
# "tools.honcho_tools", # Removed — Honcho is now a memory provider plugin
|
||||||
"tools.homeassistant_tool",
|
"tools.homeassistant_tool",
|
||||||
|
"tools.mempalace_tool",
|
||||||
]
|
]
|
||||||
import importlib
|
import importlib
|
||||||
for mod_name in _modules:
|
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_nudge_interval = 10
|
||||||
self._memory_flush_min_turns = 6
|
self._memory_flush_min_turns = 6
|
||||||
self._turns_since_memory = 0
|
self._turns_since_memory = 0
|
||||||
|
self._turns_since_palace = 0
|
||||||
self._iters_since_skill = 0
|
self._iters_since_skill = 0
|
||||||
if not skip_memory:
|
if not skip_memory:
|
||||||
try:
|
try:
|
||||||
@@ -1035,6 +1036,7 @@ class AIAgent:
|
|||||||
self._user_profile_enabled = mem_config.get("user_profile_enabled", False)
|
self._user_profile_enabled = mem_config.get("user_profile_enabled", False)
|
||||||
self._memory_nudge_interval = int(mem_config.get("nudge_interval", 10))
|
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._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:
|
if self._memory_enabled or self._user_profile_enabled:
|
||||||
from tools.memory_tool import MemoryStore
|
from tools.memory_tool import MemoryStore
|
||||||
self._memory_store = MemoryStore(
|
self._memory_store = MemoryStore(
|
||||||
@@ -1797,6 +1799,16 @@ class AIAgent:
|
|||||||
"If nothing is worth saving, just say 'Nothing to save.' and stop."
|
"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 = (
|
_COMBINED_REVIEW_PROMPT = (
|
||||||
"Review the conversation above and consider two things:\n\n"
|
"Review the conversation above and consider two things:\n\n"
|
||||||
"**Memory**: Has the user revealed things about themselves — their persona, "
|
"**Memory**: Has the user revealed things about themselves — their persona, "
|
||||||
@@ -1816,6 +1828,7 @@ class AIAgent:
|
|||||||
messages_snapshot: List[Dict],
|
messages_snapshot: List[Dict],
|
||||||
review_memory: bool = False,
|
review_memory: bool = False,
|
||||||
review_skills: bool = False,
|
review_skills: bool = False,
|
||||||
|
review_palace: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Spawn a background thread to review the conversation for memory/skill saves.
|
"""Spawn a background thread to review the conversation for memory/skill saves.
|
||||||
|
|
||||||
@@ -1826,13 +1839,20 @@ class AIAgent:
|
|||||||
"""
|
"""
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
|
||||||
# Pick the right prompt based on which triggers fired
|
# Pick the right prompt based on which triggers fired
|
||||||
if review_memory and review_skills:
|
prompts = []
|
||||||
prompt = self._COMBINED_REVIEW_PROMPT
|
if review_memory: prompts.append(self._MEMORY_REVIEW_PROMPT)
|
||||||
elif review_memory:
|
if review_skills: prompts.append(self._SKILL_REVIEW_PROMPT)
|
||||||
prompt = self._MEMORY_REVIEW_PROMPT
|
if review_palace: prompts.append(self._PALACE_REVIEW_PROMPT)
|
||||||
else:
|
|
||||||
prompt = self._SKILL_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():
|
def _run_review():
|
||||||
import contextlib, os as _os
|
import contextlib, os as _os
|
||||||
@@ -6151,6 +6171,13 @@ class AIAgent:
|
|||||||
# Reset nudge counters
|
# Reset nudge counters
|
||||||
if function_name == "memory":
|
if function_name == "memory":
|
||||||
self._turns_since_memory = 0
|
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":
|
elif function_name == "skill_manage":
|
||||||
self._iters_since_skill = 0
|
self._iters_since_skill = 0
|
||||||
|
|
||||||
@@ -7002,6 +7029,7 @@ class AIAgent:
|
|||||||
# Skill trigger is checked AFTER the agent loop completes, based on
|
# Skill trigger is checked AFTER the agent loop completes, based on
|
||||||
# how many tool iterations THIS turn used.
|
# how many tool iterations THIS turn used.
|
||||||
_should_review_memory = False
|
_should_review_memory = False
|
||||||
|
_should_review_palace = False
|
||||||
if (self._memory_nudge_interval > 0
|
if (self._memory_nudge_interval > 0
|
||||||
and "memory" in self.valid_tool_names
|
and "memory" in self.valid_tool_names
|
||||||
and self._memory_store):
|
and self._memory_store):
|
||||||
@@ -9155,12 +9183,13 @@ class AIAgent:
|
|||||||
|
|
||||||
# Background memory/skill review — runs AFTER the response is delivered
|
# Background memory/skill review — runs AFTER the response is delivered
|
||||||
# so it never competes with the user's task for model attention.
|
# 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:
|
try:
|
||||||
self._spawn_background_review(
|
self._spawn_background_review(
|
||||||
messages_snapshot=list(messages),
|
messages_snapshot=list(messages),
|
||||||
review_memory=_should_review_memory,
|
review_memory=_should_review_memory,
|
||||||
review_skills=_should_review_skills,
|
review_skills=_should_review_skills,
|
||||||
|
review_palace=_should_review_palace,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # Background review is best-effort
|
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