From 3cf5e8225d887b0044f2cd5af278e25f0eab8c85 Mon Sep 17 00:00:00 2001 From: erosika Date: Thu, 21 May 2026 22:20:47 +0000 Subject: [PATCH] refactor(honcho): accept pinUserPeer as backwards-compatible alias for pinPeerName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original key 'pinPeerName' from #14984 is ambiguous: a fresh reader can't tell whether it pins the user peer or the AI peer from the name alone. The resolver only ever pins the user-side (_resolve_user_peer_id short-circuits when pin_peer_name is true; the AI peer is already pinned by construction via aiPeer). Add 'pinUserPeer' as the canonical alias. Both keys land on the same internal pin_peer_name field; precedence is host pinUserPeer → host pinPeerName → root pinUserPeer → root pinPeerName → default. Host-level always beats root-level regardless of alias, so a host block can still explicitly disable a root-level pin even via the new key. Make _resolve_bool variadic so it can express the four-value precedence chain. All existing callers pass two positional args + default keyword, which the new signature accepts unchanged. Internal var name (pin_peer_name) stays the same to keep the cherry-picked #27371 commits clean and avoid a noisy rename diff. --- plugins/memory/honcho/client.py | 25 +++++++-- tests/honcho_plugin/test_pin_peer_name.py | 67 +++++++++++++++++++++++ 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/plugins/memory/honcho/client.py b/plugins/memory/honcho/client.py index 2a7b07ca1b3..3d31bd7a1fb 100644 --- a/plugins/memory/honcho/client.py +++ b/plugins/memory/honcho/client.py @@ -91,12 +91,17 @@ def _normalize_recall_mode(val: str) -> str: return val if val in _VALID_RECALL_MODES else "hybrid" -def _resolve_bool(host_val, root_val, *, default: bool) -> bool: - """Resolve a bool config field: host wins, then root, then default.""" - if host_val is not None: - return bool(host_val) - if root_val is not None: - return bool(root_val) +def _resolve_bool(*vals, default: bool) -> bool: + """Resolve a bool config field: first non-None wins, else default. + + Variadic to support aliased keys (e.g. ``pinUserPeer`` shadowing + ``pinPeerName`` for backwards compatibility). Pass values in + precedence order: caller's preferred alias first, then fallback + aliases, in (host, root) interleaving as needed. + """ + for val in vals: + if val is not None: + return bool(val) return default @@ -488,7 +493,15 @@ class HonchoClientConfig: peer_name=host_block.get("peerName") or raw.get("peerName"), ai_peer=ai_peer, pin_peer_name=_resolve_bool( + # ``pinUserPeer`` is the clearer name (the resolver pins + # the user-side peer to ``peerName``, ignoring runtime + # identity). ``pinPeerName`` is the original key from + # #14984 and stays accepted for backward compatibility. + # Host-level keys win over root-level; among same-level + # keys, ``pinUserPeer`` wins over ``pinPeerName``. + host_block.get("pinUserPeer"), host_block.get("pinPeerName"), + raw.get("pinUserPeer"), raw.get("pinPeerName"), default=False, ), diff --git a/tests/honcho_plugin/test_pin_peer_name.py b/tests/honcho_plugin/test_pin_peer_name.py index 2cfdfc6cdf6..e1ef5fda082 100644 --- a/tests/honcho_plugin/test_pin_peer_name.py +++ b/tests/honcho_plugin/test_pin_peer_name.py @@ -614,3 +614,70 @@ class TestCrossPlatformMemoryUnification: "multi-user default MUST keep users separate — a regression " "here would silently merge unrelated users' memory" ) + + +class TestPinUserPeerAlias: + """``pinUserPeer`` is the canonical name; ``pinPeerName`` is the + backwards-compatible alias. + + Both keys land on the same internal ``pin_peer_name`` field. When + both appear, the precedence is: host pinUserPeer → host pinPeerName + → root pinUserPeer → root pinPeerName → default. This matches the + rule for every other host/root override in the plugin and lets a + host block explicitly disable a root-level pin even via the legacy + key. + """ + + def test_root_pinUserPeer_true_pins(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "pinUserPeer": True, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is True + + def test_host_pinUserPeer_wins_over_root_pinPeerName(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "pinPeerName": False, + "hosts": {"hermes": {"pinUserPeer": True}}, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is True + + def test_host_pinUserPeer_false_disables_root_pinPeerName(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "pinPeerName": True, + "hosts": {"hermes": {"pinUserPeer": False}}, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is False, ( + "Host-level pinUserPeer=false must override the legacy " + "root-level pinPeerName=true, otherwise a host can never " + "unpin a globally-pinned profile via the new alias." + ) + + def test_pinPeerName_still_works_unchanged(self, tmp_path): + from plugins.memory.honcho.client import HonchoClientConfig + import json + config_file = tmp_path / "honcho.json" + config_file.write_text(json.dumps({ + "apiKey": "***", + "peerName": "eri", + "hosts": {"hermes": {"pinPeerName": True}}, + })) + config = HonchoClientConfig.from_global_config(config_path=config_file) + assert config.pin_peer_name is True