Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3555,13 +3555,14 @@ def cmd_profile(args):
else:
print(f"Cloned config, .env, SOUL.md from {source_label}.")

# Auto-clone Honcho config for the new profile
try:
from plugins.memory.honcho.cli import clone_honcho_for_profile
if clone_honcho_for_profile(name):
print(f"Honcho config cloned (host: hermes.{name})")
except Exception:
pass # Honcho plugin not installed or not configured
# Auto-clone Honcho config for the new profile (only with --clone/--clone-all)
if clone or clone_all:
try:
from plugins.memory.honcho.cli import clone_honcho_for_profile
if clone_honcho_for_profile(name):
print(f"Honcho config cloned (peer: {name})")
except Exception:
pass # Honcho not installed or not configured

# Seed bundled skills (skip if --clone-all already copied them)
if not clone_all:
Expand Down
11 changes: 7 additions & 4 deletions plugins/memory/honcho/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def clone_honcho_for_profile(profile_name: str) -> bool:

# AI peer is profile-specific; workspace is shared so all profiles
# see the same user context, sessions, and project history.
new_block["aiPeer"] = new_host
# Use the bare profile name as the peer identity (not the host key).
new_block["aiPeer"] = profile_name
new_block["workspace"] = default_block.get("workspace") or cfg.get("workspace") or HOST
new_block["enabled"] = default_block.get("enabled", True)

Expand Down Expand Up @@ -112,7 +113,9 @@ def cmd_enable(args) -> None:
peer_name = default_block.get("peerName") or cfg.get("peerName")
if peer_name and "peerName" not in block:
block["peerName"] = peer_name
block.setdefault("aiPeer", host)
# Use bare profile name as AI peer, not the host key
ai_peer = host.split(".", 1)[1] if "." in host else host
block.setdefault("aiPeer", ai_peer)
block.setdefault("workspace", default_block.get("workspace") or cfg.get("workspace") or HOST)

_write_config(cfg)
Expand Down Expand Up @@ -517,7 +520,7 @@ def cmd_status(args) -> None:

try:
from plugins.memory.honcho.client import HonchoClientConfig, get_honcho_client
hcfg = HonchoClientConfig.from_global_config()
hcfg = HonchoClientConfig.from_global_config(host=_host_key())
except Exception as e:
print(f" Config error: {e}\n")
return
Expand Down Expand Up @@ -836,7 +839,7 @@ def cmd_identity(args) -> None:
try:
from plugins.memory.honcho.client import HonchoClientConfig, get_honcho_client
from plugins.memory.honcho.session import HonchoSessionManager
hcfg = HonchoClientConfig.from_global_config()
hcfg = HonchoClientConfig.from_global_config(host=_host_key())
client = get_honcho_client(hcfg)
mgr = HonchoSessionManager(honcho=client, config=hcfg)
session_key = hcfg.resolve_session_name()
Expand Down
15 changes: 12 additions & 3 deletions plugins/memory/honcho/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,22 @@ def resolve_active_host() -> str:
def resolve_config_path() -> Path:
"""Return the active Honcho config path.

Checks $HERMES_HOME/honcho.json first (instance-local), then falls back
to ~/.honcho/config.json (global). Returns the global path if neither
exists (for first-time setup writes).
Resolution order:
1. $HERMES_HOME/honcho.json (profile-local, if it exists)
2. ~/.hermes/honcho.json (default profile — shared host blocks live here)
3. ~/.honcho/config.json (global, cross-app interop)

Returns the global path if none exist (for first-time setup writes).
"""
local_path = get_hermes_home() / "honcho.json"
if local_path.exists():
return local_path

# Default profile's config — host blocks accumulate here via setup/clone
default_path = Path.home() / ".hermes" / "honcho.json"
if default_path != local_path and default_path.exists():
return default_path

return GLOBAL_CONFIG_PATH


Expand Down
16 changes: 11 additions & 5 deletions plugins/memory/honcho/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,17 @@ def _get_or_create_honcho_session(
# Configure peer observation settings.
# observe_me=True for AI peer so Honcho watches what the agent says
# and builds its representation over time — enabling identity formation.
from honcho.session import SessionPeerConfig
user_config = SessionPeerConfig(observe_me=True, observe_others=True)
ai_config = SessionPeerConfig(observe_me=True, observe_others=True)
try:
from honcho.session import SessionPeerConfig
user_config = SessionPeerConfig(observe_me=True, observe_others=True)
ai_config = SessionPeerConfig(observe_me=True, observe_others=True)

session.add_peers([(user_peer, user_config), (assistant_peer, ai_config)])
session.add_peers([(user_peer, user_config), (assistant_peer, ai_config)])
except Exception as e:
logger.warning(
"Honcho session '%s' add_peers failed (non-fatal): %s",
session_id, e,
)

# Load existing messages via context() - single call for messages + metadata
existing_messages = []
Expand Down Expand Up @@ -231,7 +237,7 @@ def get_or_create(self, key: str) -> HonchoSession:
chat_id = parts[1] if len(parts) > 1 else key
user_peer_id = self._sanitize_id(f"user-{channel}-{chat_id}")

assistant_peer_id = (
assistant_peer_id = self._sanitize_id(
self._config.ai_peer if self._config else "hermes-assistant"
)

Expand Down
15 changes: 9 additions & 6 deletions tests/honcho_plugin/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,18 @@ def test_prefers_hermes_home_when_exists(self, tmp_path):
def test_falls_back_to_global_when_no_local(self, tmp_path):
hermes_home = tmp_path / "hermes"
hermes_home.mkdir()
# No honcho.json in HERMES_HOME

with patch.dict(os.environ, {"HERMES_HOME": str(hermes_home)}):
# No honcho.json in HERMES_HOME — and no ~/.hermes/honcho.json either
with patch.dict(os.environ, {"HERMES_HOME": str(hermes_home)}), \
patch.object(Path, "home", staticmethod(lambda: tmp_path)):
result = resolve_config_path()
assert result == GLOBAL_CONFIG_PATH

def test_falls_back_to_global_without_hermes_home_env(self):
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("HERMES_HOME", None)
def test_falls_back_to_global_without_hermes_home_env(self, tmp_path):
# Point HERMES_HOME to a temp dir that has NO honcho.json
empty_home = tmp_path / ".hermes"
empty_home.mkdir()
with patch.dict(os.environ, {"HERMES_HOME": str(empty_home)}, clear=False), \
patch.object(Path, "home", staticmethod(lambda: tmp_path)):
result = resolve_config_path()
assert result == GLOBAL_CONFIG_PATH

Expand Down
Loading