mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-23 10:42:00 +00:00
fix(memory): honor configured char limits in the no-agent on-disk store
Follow-up to the /memory approve fresh-store fix. Both the CLI fallback and the messaging-gateway handler built a bare MemoryStore() with the hardcoded default char limits (2200/1375), ignoring the user's configured memory.memory_char_limit / user_char_limit. A live agent honors those overrides (agent/agent_init.py), so an approval applied without a live agent could accept a write the user's lower cap would reject, or vice versa. Extract a shared tools.memory_tool.load_on_disk_store() factory that reads the configured limits (falling back to defaults if config can't load) and wire both the CLI and gateway handlers to it, closing the gap on both surfaces and de-duplicating the construction block.
This commit is contained in:
parent
3147cbb136
commit
0e69cd4b37
4 changed files with 66 additions and 6 deletions
|
|
@ -2343,7 +2343,7 @@ class GatewaySlashCommandsMixin:
|
|||
from gateway.run import _hermes_home
|
||||
from hermes_cli.write_approval_commands import handle_pending_subcommand
|
||||
from tools import write_approval as wa
|
||||
from tools.memory_tool import MemoryStore
|
||||
from tools.memory_tool import load_on_disk_store
|
||||
|
||||
raw_args = event.get_command_args().strip()
|
||||
args = raw_args.split() if raw_args else []
|
||||
|
|
@ -2363,8 +2363,8 @@ class GatewaySlashCommandsMixin:
|
|||
|
||||
# Apply approved writes against a fresh on-disk store (the gateway has
|
||||
# no long-lived agent; the store persists to the same MEMORY/USER.md).
|
||||
store = MemoryStore()
|
||||
store.load_from_disk()
|
||||
# load_on_disk_store() honors the user's configured char limits.
|
||||
store = load_on_disk_store()
|
||||
|
||||
out = handle_pending_subcommand(
|
||||
wa.MEMORY, args, memory_store=store, set_mode_fn=_set_approval,
|
||||
|
|
|
|||
|
|
@ -1368,9 +1368,10 @@ class CLICommandsMixin:
|
|||
# (gateway/slash_commands.py): it persists to the same MEMORY/USER.md
|
||||
# and creates MEMORY.md on the first approved write. Without this the
|
||||
# shared handler returns "memory store unavailable". See #46783.
|
||||
from tools.memory_tool import MemoryStore
|
||||
store = MemoryStore()
|
||||
store.load_from_disk()
|
||||
# load_on_disk_store() honors the user's configured char limits, so
|
||||
# an approval here enforces the same caps as the live agent would.
|
||||
from tools.memory_tool import load_on_disk_store
|
||||
store = load_on_disk_store()
|
||||
out = handle_pending_subcommand(
|
||||
wa.MEMORY, args,
|
||||
memory_store=store,
|
||||
|
|
|
|||
|
|
@ -137,6 +137,33 @@ def test_cli_memory_approve_without_live_agent_uses_fresh_store(hermes_home, cap
|
|||
assert any("remember the launch date" in e for e in reloaded.memory_entries)
|
||||
|
||||
|
||||
def test_load_on_disk_store_honors_configured_char_limits(hermes_home, monkeypatch):
|
||||
"""load_on_disk_store() must read memory.memory_char_limit /
|
||||
user_char_limit from config so approvals applied without a live agent
|
||||
enforce the SAME caps as the live agent (agent_init.py). Falls back to
|
||||
defaults when config can't be loaded.
|
||||
"""
|
||||
from tools.memory_tool import load_on_disk_store
|
||||
|
||||
# Config override path: helper picks up the configured limits.
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
lambda: {"memory": {"memory_char_limit": 999, "user_char_limit": 444}},
|
||||
)
|
||||
store = load_on_disk_store()
|
||||
assert store.memory_char_limit == 999
|
||||
assert store.user_char_limit == 444
|
||||
|
||||
# Failure path: config raises → defaults, never blows up.
|
||||
def _boom():
|
||||
raise RuntimeError("no config")
|
||||
|
||||
monkeypatch.setattr("hermes_cli.config.load_config", _boom)
|
||||
fallback = load_on_disk_store()
|
||||
assert fallback.memory_char_limit == 2200
|
||||
assert fallback.user_char_limit == 1375
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Skill gate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -731,6 +731,38 @@ class MemoryStore:
|
|||
raise RuntimeError(f"Failed to write memory file {path}: {e}")
|
||||
|
||||
|
||||
def load_on_disk_store() -> "MemoryStore":
|
||||
"""Build a fresh on-disk :class:`MemoryStore`, honoring configured char limits.
|
||||
|
||||
Use this from any context that has no live agent (the messaging gateway, the
|
||||
Desktop GUI, the bare CLI ``/memory`` handler) but still needs to read or
|
||||
apply approved memory writes. Mirrors how the live agent constructs its store
|
||||
in ``agent/agent_init.py`` — including the user's ``memory.memory_char_limit``
|
||||
/ ``memory.user_char_limit`` overrides — so an approval applied without a live
|
||||
agent enforces the SAME caps as one applied with one.
|
||||
|
||||
Falls back to the built-in defaults if config can't be loaded, so this can
|
||||
never raise on a missing/unreadable config.
|
||||
"""
|
||||
memory_char_limit = 2200
|
||||
user_char_limit = 1375
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
mem_cfg = (load_config() or {}).get("memory", {}) or {}
|
||||
memory_char_limit = int(mem_cfg.get("memory_char_limit", memory_char_limit))
|
||||
user_char_limit = int(mem_cfg.get("user_char_limit", user_char_limit))
|
||||
except Exception:
|
||||
pass # config optional — fall back to defaults rather than break /memory
|
||||
|
||||
store = MemoryStore(
|
||||
memory_char_limit=memory_char_limit,
|
||||
user_char_limit=user_char_limit,
|
||||
)
|
||||
store.load_from_disk()
|
||||
return store
|
||||
|
||||
|
||||
def _apply_write_gate(action: str, target: str, content: Optional[str],
|
||||
old_text: Optional[str]) -> Optional[str]:
|
||||
"""Evaluate the memory write gate. Returns a JSON tool-result string when
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue