mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(hindsight): optional bank_id_template for per-agent / per-user banks
Adds an optional bank_id_template config that derives the bank name at
initialize() time from runtime context. Existing users with a static
bank_id keep the current behavior (template is empty by default).
Supported placeholders:
{profile} — active Hermes profile (agent_identity kwarg)
{workspace} — Hermes workspace (agent_workspace kwarg)
{platform} — cli, telegram, discord, etc.
{user} — platform user id (gateway sessions)
{session} — session id
Unsafe characters in placeholder values are sanitized, and empty
placeholders collapse cleanly (e.g. "hermes-{user}" with no user
becomes "hermes"). If the template renders empty, the static bank_id
is used as a fallback.
Common uses:
bank_id_template: hermes-{profile} # isolate per Hermes profile
bank_id_template: {workspace}-{profile} # workspace + profile scoping
bank_id_template: hermes-{user} # per-user banks for gateway
This commit is contained in:
parent
f9c6c5ab84
commit
edff2fbe7e
3 changed files with 223 additions and 4 deletions
|
|
@ -19,6 +19,8 @@ from plugins.memory.hindsight import (
|
|||
RETAIN_SCHEMA,
|
||||
_load_config,
|
||||
_normalize_retain_tags,
|
||||
_resolve_bank_id_template,
|
||||
_sanitize_bank_segment,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -782,7 +784,7 @@ class TestConfigSchema:
|
|||
keys = {f["key"] for f in schema}
|
||||
expected_keys = {
|
||||
"mode", "api_url", "api_key", "llm_provider", "llm_api_key",
|
||||
"llm_model", "bank_id", "bank_mission", "bank_retain_mission",
|
||||
"llm_model", "bank_id", "bank_id_template", "bank_mission", "bank_retain_mission",
|
||||
"recall_budget", "memory_mode", "recall_prefetch_method",
|
||||
"retain_tags", "retain_source",
|
||||
"retain_user_prefix", "retain_assistant_prefix",
|
||||
|
|
@ -795,6 +797,150 @@ class TestConfigSchema:
|
|||
assert expected_keys.issubset(keys), f"Missing: {expected_keys - keys}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# bank_id_template tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestBankIdTemplate:
|
||||
def test_sanitize_bank_segment_passthrough(self):
|
||||
assert _sanitize_bank_segment("hermes") == "hermes"
|
||||
assert _sanitize_bank_segment("my-agent_1") == "my-agent_1"
|
||||
|
||||
def test_sanitize_bank_segment_strips_unsafe(self):
|
||||
assert _sanitize_bank_segment("josh@example.com") == "josh-example-com"
|
||||
assert _sanitize_bank_segment("chat:#general") == "chat-general"
|
||||
assert _sanitize_bank_segment(" spaces ") == "spaces"
|
||||
|
||||
def test_sanitize_bank_segment_empty(self):
|
||||
assert _sanitize_bank_segment("") == ""
|
||||
assert _sanitize_bank_segment(None) == ""
|
||||
|
||||
def test_resolve_empty_template_uses_fallback(self):
|
||||
result = _resolve_bank_id_template(
|
||||
"", fallback="hermes", profile="coder"
|
||||
)
|
||||
assert result == "hermes"
|
||||
|
||||
def test_resolve_with_profile(self):
|
||||
result = _resolve_bank_id_template(
|
||||
"hermes-{profile}", fallback="hermes",
|
||||
profile="coder", workspace="", platform="", user="", session="",
|
||||
)
|
||||
assert result == "hermes-coder"
|
||||
|
||||
def test_resolve_with_multiple_placeholders(self):
|
||||
result = _resolve_bank_id_template(
|
||||
"{workspace}-{profile}-{platform}",
|
||||
fallback="hermes",
|
||||
profile="coder", workspace="myorg", platform="cli",
|
||||
user="", session="",
|
||||
)
|
||||
assert result == "myorg-coder-cli"
|
||||
|
||||
def test_resolve_collapses_empty_placeholders(self):
|
||||
# When user is empty, "hermes-{user}" becomes "hermes-" -> trimmed to "hermes"
|
||||
result = _resolve_bank_id_template(
|
||||
"hermes-{user}", fallback="default",
|
||||
profile="", workspace="", platform="", user="", session="",
|
||||
)
|
||||
assert result == "hermes"
|
||||
|
||||
def test_resolve_collapses_double_dashes(self):
|
||||
# Two empty placeholders with a dash between them should collapse
|
||||
result = _resolve_bank_id_template(
|
||||
"{workspace}-{profile}-{user}", fallback="fallback",
|
||||
profile="coder", workspace="", platform="", user="", session="",
|
||||
)
|
||||
assert result == "coder"
|
||||
|
||||
def test_resolve_empty_rendered_falls_back(self):
|
||||
result = _resolve_bank_id_template(
|
||||
"{user}-{profile}", fallback="fallback",
|
||||
profile="", workspace="", platform="", user="", session="",
|
||||
)
|
||||
assert result == "fallback"
|
||||
|
||||
def test_resolve_sanitizes_placeholder_values(self):
|
||||
result = _resolve_bank_id_template(
|
||||
"user-{user}", fallback="hermes",
|
||||
profile="", workspace="", platform="",
|
||||
user="josh@example.com", session="",
|
||||
)
|
||||
assert result == "user-josh-example-com"
|
||||
|
||||
def test_resolve_invalid_template_returns_fallback(self):
|
||||
# Unknown placeholder should fall back without raising
|
||||
result = _resolve_bank_id_template(
|
||||
"hermes-{unknown}", fallback="hermes",
|
||||
profile="", workspace="", platform="", user="", session="",
|
||||
)
|
||||
assert result == "hermes"
|
||||
|
||||
def test_provider_uses_bank_id_template_from_config(self, tmp_path, monkeypatch):
|
||||
config = {
|
||||
"mode": "cloud",
|
||||
"apiKey": "k",
|
||||
"api_url": "http://x",
|
||||
"bank_id": "fallback-bank",
|
||||
"bank_id_template": "hermes-{profile}",
|
||||
}
|
||||
config_path = tmp_path / "hindsight" / "config.json"
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text(json.dumps(config))
|
||||
monkeypatch.setattr("plugins.memory.hindsight.get_hermes_home", lambda: tmp_path)
|
||||
|
||||
p = HindsightMemoryProvider()
|
||||
p.initialize(
|
||||
session_id="s1",
|
||||
hermes_home=str(tmp_path),
|
||||
platform="cli",
|
||||
agent_identity="coder",
|
||||
agent_workspace="hermes",
|
||||
)
|
||||
assert p._bank_id == "hermes-coder"
|
||||
assert p._bank_id_template == "hermes-{profile}"
|
||||
|
||||
def test_provider_without_template_uses_static_bank_id(self, tmp_path, monkeypatch):
|
||||
config = {
|
||||
"mode": "cloud",
|
||||
"apiKey": "k",
|
||||
"api_url": "http://x",
|
||||
"bank_id": "my-static-bank",
|
||||
}
|
||||
config_path = tmp_path / "hindsight" / "config.json"
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text(json.dumps(config))
|
||||
monkeypatch.setattr("plugins.memory.hindsight.get_hermes_home", lambda: tmp_path)
|
||||
|
||||
p = HindsightMemoryProvider()
|
||||
p.initialize(
|
||||
session_id="s1",
|
||||
hermes_home=str(tmp_path),
|
||||
platform="cli",
|
||||
agent_identity="coder",
|
||||
)
|
||||
assert p._bank_id == "my-static-bank"
|
||||
|
||||
def test_provider_template_with_missing_profile_falls_back(self, tmp_path, monkeypatch):
|
||||
config = {
|
||||
"mode": "cloud",
|
||||
"apiKey": "k",
|
||||
"api_url": "http://x",
|
||||
"bank_id": "hermes-fallback",
|
||||
"bank_id_template": "hermes-{profile}",
|
||||
}
|
||||
config_path = tmp_path / "hindsight" / "config.json"
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text(json.dumps(config))
|
||||
monkeypatch.setattr("plugins.memory.hindsight.get_hermes_home", lambda: tmp_path)
|
||||
|
||||
p = HindsightMemoryProvider()
|
||||
# No agent_identity passed — template renders to "hermes-" which collapses to "hermes"
|
||||
p.initialize(session_id="s1", hermes_home=str(tmp_path), platform="cli")
|
||||
assert p._bank_id == "hermes"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Availability tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue