mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(byterover): honor auto extract config
This commit is contained in:
parent
f062cf076b
commit
52a09d8faf
2 changed files with 127 additions and 1 deletions
|
|
@ -12,6 +12,11 @@ curl -fsSL https://byterover.dev/install.sh | sh).
|
|||
Config via environment variables (profile-scoped via each profile's .env):
|
||||
BRV_API_KEY — ByteRover API key (for cloud features, optional for local)
|
||||
|
||||
Config via config.yaml:
|
||||
memory:
|
||||
byterover:
|
||||
auto_extract: false # disable automatic brv curate hooks
|
||||
|
||||
Working directory: $HERMES_HOME/byterover/ (profile-scoped context tree)
|
||||
"""
|
||||
|
||||
|
|
@ -40,6 +45,49 @@ _MIN_QUERY_LEN = 10
|
|||
_MIN_OUTPUT_LEN = 20
|
||||
|
||||
|
||||
def _coerce_bool(value: Any, default: bool = False) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value is None:
|
||||
return default
|
||||
if isinstance(value, (int, float)):
|
||||
return bool(value)
|
||||
if isinstance(value, str):
|
||||
text = value.strip().lower()
|
||||
if text in {"1", "true", "yes", "on"}:
|
||||
return True
|
||||
if text in {"0", "false", "no", "off"}:
|
||||
return False
|
||||
return default
|
||||
|
||||
|
||||
def _load_plugin_config() -> Dict[str, Any]:
|
||||
"""Read ByteRover's profile-scoped memory config.
|
||||
|
||||
New memory-provider setup stores non-secret provider settings under
|
||||
``memory.<provider>``. Some users also set ``memory.provider_config`` from
|
||||
early docs/issues, so accept it as a compatibility fallback.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
config = load_config()
|
||||
memory_config = config.get("memory", {})
|
||||
if not isinstance(memory_config, dict):
|
||||
return {}
|
||||
|
||||
provider_config = memory_config.get("byterover", {})
|
||||
if isinstance(provider_config, dict) and provider_config:
|
||||
return dict(provider_config)
|
||||
|
||||
legacy_config = memory_config.get("provider_config", {})
|
||||
if isinstance(legacy_config, dict):
|
||||
return dict(legacy_config)
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# brv binary resolution (cached, thread-safe)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -172,7 +220,9 @@ STATUS_SCHEMA = {
|
|||
class ByteRoverMemoryProvider(MemoryProvider):
|
||||
"""ByteRover persistent memory via the brv CLI."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||
self._config = dict(config) if config is not None else _load_plugin_config()
|
||||
self._auto_extract = _coerce_bool(self._config.get("auto_extract"), True)
|
||||
self._cwd = ""
|
||||
self._session_id = ""
|
||||
self._turn_count = 0
|
||||
|
|
@ -195,6 +245,12 @@ class ByteRoverMemoryProvider(MemoryProvider):
|
|||
"env_var": "BRV_API_KEY",
|
||||
"url": "https://app.byterover.dev",
|
||||
},
|
||||
{
|
||||
"key": "auto_extract",
|
||||
"description": "Automatically curate completed turns and compression/memory hooks",
|
||||
"default": "true",
|
||||
"choices": ["true", "false"],
|
||||
},
|
||||
]
|
||||
|
||||
def initialize(self, session_id: str, **kwargs) -> None:
|
||||
|
|
@ -238,6 +294,9 @@ class ByteRoverMemoryProvider(MemoryProvider):
|
|||
def sync_turn(self, user_content: str, assistant_content: str, *, session_id: str = "") -> None:
|
||||
"""Curate the conversation turn in background (non-blocking)."""
|
||||
self._turn_count += 1
|
||||
if not self._auto_extract:
|
||||
logger.debug("ByteRover sync_turn skipped (auto_extract disabled)")
|
||||
return
|
||||
|
||||
# Only curate substantive turns
|
||||
if len(user_content.strip()) < _MIN_QUERY_LEN:
|
||||
|
|
@ -264,6 +323,9 @@ class ByteRoverMemoryProvider(MemoryProvider):
|
|||
|
||||
def on_memory_write(self, action: str, target: str, content: str) -> None:
|
||||
"""Mirror built-in memory writes to ByteRover."""
|
||||
if not self._auto_extract:
|
||||
logger.debug("ByteRover memory mirror skipped (auto_extract disabled)")
|
||||
return
|
||||
if action not in {"add", "replace"} or not content:
|
||||
return
|
||||
|
||||
|
|
@ -282,6 +344,9 @@ class ByteRoverMemoryProvider(MemoryProvider):
|
|||
|
||||
def on_pre_compress(self, messages: List[Dict[str, Any]]) -> str:
|
||||
"""Extract insights before context compression discards turns."""
|
||||
if not self._auto_extract:
|
||||
logger.debug("ByteRover pre-compression flush skipped (auto_extract disabled)")
|
||||
return ""
|
||||
if not messages:
|
||||
return ""
|
||||
|
||||
|
|
|
|||
61
tests/plugins/memory/test_byterover_provider.py
Normal file
61
tests/plugins/memory/test_byterover_provider.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""Tests for the ByteRover memory provider config gates."""
|
||||
|
||||
from plugins.memory.byterover import ByteRoverMemoryProvider
|
||||
|
||||
|
||||
def test_auto_extract_false_skips_sync_turn(monkeypatch):
|
||||
calls = []
|
||||
provider = ByteRoverMemoryProvider({"auto_extract": False})
|
||||
provider.initialize("session-1")
|
||||
|
||||
monkeypatch.setattr("plugins.memory.byterover._run_brv", lambda *args, **kwargs: calls.append((args, kwargs)))
|
||||
|
||||
provider.sync_turn("please remember this detail", "acknowledged")
|
||||
|
||||
assert calls == []
|
||||
assert provider._sync_thread is None
|
||||
|
||||
|
||||
def test_auto_extract_false_skips_memory_write(monkeypatch):
|
||||
calls = []
|
||||
provider = ByteRoverMemoryProvider({"auto_extract": "false"})
|
||||
provider.initialize("session-1")
|
||||
|
||||
monkeypatch.setattr("plugins.memory.byterover._run_brv", lambda *args, **kwargs: calls.append((args, kwargs)))
|
||||
|
||||
provider.on_memory_write("add", "user", "User prefers concise responses")
|
||||
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_auto_extract_false_skips_pre_compress(monkeypatch):
|
||||
calls = []
|
||||
provider = ByteRoverMemoryProvider({"auto_extract": "off"})
|
||||
provider.initialize("session-1")
|
||||
|
||||
monkeypatch.setattr("plugins.memory.byterover._run_brv", lambda *args, **kwargs: calls.append((args, kwargs)))
|
||||
|
||||
result = provider.on_pre_compress([
|
||||
{"role": "user", "content": "remember this"},
|
||||
{"role": "assistant", "content": "stored"},
|
||||
])
|
||||
|
||||
assert result == ""
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_auto_extract_false_keeps_explicit_curate_tool(monkeypatch):
|
||||
calls = []
|
||||
provider = ByteRoverMemoryProvider({"auto_extract": False})
|
||||
provider.initialize("session-1")
|
||||
|
||||
def fake_run(args, **kwargs):
|
||||
calls.append(args)
|
||||
return {"success": True, "output": "ok"}
|
||||
|
||||
monkeypatch.setattr("plugins.memory.byterover._run_brv", fake_run)
|
||||
|
||||
result = provider.handle_tool_call("brv_curate", {"content": "Important project fact"})
|
||||
|
||||
assert "Memory curated successfully" in result
|
||||
assert calls == [["curate", "--", "Important project fact"]]
|
||||
Loading…
Add table
Add a link
Reference in a new issue