fix(honcho): scope config writes to hosts.hermes, not root
Config writes from hermes honcho setup/peer now go to hosts.hermes instead of mutating root-level keys. Root is reserved for the user or honcho CLI. apiKey remains at root as a shared credential. Reads updated to check hosts.hermes first with root fallback for all fields (peerName, enabled, saveMessages, environment, sessionStrategy, sessionPeerPrefix).
This commit is contained in:
@@ -88,7 +88,12 @@ def cmd_setup(args) -> None:
|
||||
if not _ensure_sdk_installed():
|
||||
return
|
||||
|
||||
# API key
|
||||
# All writes go to hosts.hermes — root keys are managed by the user
|
||||
# or the honcho CLI only.
|
||||
hosts = cfg.setdefault("hosts", {})
|
||||
hermes_host = hosts.setdefault(HOST, {})
|
||||
|
||||
# API key — shared credential, lives at root so all hosts can read it
|
||||
current_key = cfg.get("apiKey", "")
|
||||
masked = f"...{current_key[-8:]}" if len(current_key) > 8 else ("set" if current_key else "not set")
|
||||
print(f" Current API key: {masked}")
|
||||
@@ -96,45 +101,39 @@ def cmd_setup(args) -> None:
|
||||
if new_key:
|
||||
cfg["apiKey"] = new_key
|
||||
|
||||
if not cfg.get("apiKey"):
|
||||
effective_key = cfg.get("apiKey", "")
|
||||
if not effective_key:
|
||||
print("\n No API key configured. Get your API key at https://app.honcho.dev")
|
||||
print(" Run 'hermes honcho setup' again once you have a key.\n")
|
||||
return
|
||||
|
||||
# Peer name
|
||||
current_peer = cfg.get("peerName", "")
|
||||
current_peer = hermes_host.get("peerName") or cfg.get("peerName", "")
|
||||
new_peer = _prompt("Your name (user peer)", default=current_peer or os.getenv("USER", "user"))
|
||||
if new_peer:
|
||||
cfg["peerName"] = new_peer
|
||||
|
||||
# Host block
|
||||
hosts = cfg.setdefault("hosts", {})
|
||||
hermes_host = hosts.setdefault(HOST, {})
|
||||
hermes_host["peerName"] = new_peer
|
||||
|
||||
current_workspace = hermes_host.get("workspace") or cfg.get("workspace", "hermes")
|
||||
new_workspace = _prompt("Workspace ID", default=current_workspace)
|
||||
if new_workspace:
|
||||
hermes_host["workspace"] = new_workspace
|
||||
# Also update flat workspace if it was the primary one
|
||||
if cfg.get("workspace") == current_workspace:
|
||||
cfg["workspace"] = new_workspace
|
||||
|
||||
hermes_host.setdefault("aiPeer", HOST)
|
||||
|
||||
# Memory mode
|
||||
current_mode = cfg.get("memoryMode", "hybrid")
|
||||
current_mode = hermes_host.get("memoryMode") or cfg.get("memoryMode", "hybrid")
|
||||
print(f"\n Memory mode options:")
|
||||
print(" hybrid — write to both Honcho and local MEMORY.md (default)")
|
||||
print(" honcho — Honcho only, skip MEMORY.md writes")
|
||||
print(" local — MEMORY.md only, Honcho disabled")
|
||||
new_mode = _prompt("Memory mode", default=current_mode)
|
||||
if new_mode in ("hybrid", "honcho", "local"):
|
||||
cfg["memoryMode"] = new_mode
|
||||
hermes_host["memoryMode"] = new_mode
|
||||
else:
|
||||
cfg["memoryMode"] = "hybrid"
|
||||
hermes_host["memoryMode"] = "hybrid"
|
||||
|
||||
# Write frequency
|
||||
current_wf = str(cfg.get("writeFrequency", "async"))
|
||||
current_wf = str(hermes_host.get("writeFrequency") or cfg.get("writeFrequency", "async"))
|
||||
print(f"\n Write frequency options:")
|
||||
print(" async — background thread, no token cost (recommended)")
|
||||
print(" turn — sync write after every turn")
|
||||
@@ -142,22 +141,22 @@ def cmd_setup(args) -> None:
|
||||
print(" N — write every N turns (e.g. 5)")
|
||||
new_wf = _prompt("Write frequency", default=current_wf)
|
||||
try:
|
||||
cfg["writeFrequency"] = int(new_wf)
|
||||
hermes_host["writeFrequency"] = int(new_wf)
|
||||
except (ValueError, TypeError):
|
||||
cfg["writeFrequency"] = new_wf if new_wf in ("async", "turn", "session") else "async"
|
||||
hermes_host["writeFrequency"] = new_wf if new_wf in ("async", "turn", "session") else "async"
|
||||
|
||||
# Recall mode
|
||||
current_recall = cfg.get("recallMode", "hybrid")
|
||||
current_recall = hermes_host.get("recallMode") or cfg.get("recallMode", "hybrid")
|
||||
print(f"\n Recall mode options:")
|
||||
print(" hybrid — pre-warmed context + memory tools available (default)")
|
||||
print(" context — pre-warmed context only, memory tools suppressed")
|
||||
print(" tools — no pre-loaded context, rely on tool calls only")
|
||||
new_recall = _prompt("Recall mode", default=current_recall)
|
||||
if new_recall in ("hybrid", "context", "tools"):
|
||||
cfg["recallMode"] = new_recall
|
||||
hermes_host["recallMode"] = new_recall
|
||||
|
||||
# Session strategy
|
||||
current_strat = cfg.get("sessionStrategy", "per-session")
|
||||
current_strat = hermes_host.get("sessionStrategy") or cfg.get("sessionStrategy", "per-session")
|
||||
print(f"\n Session strategy options:")
|
||||
print(" per-session — new Honcho session each run, named by Hermes session ID (default)")
|
||||
print(" per-directory — one session per working directory")
|
||||
@@ -165,10 +164,10 @@ def cmd_setup(args) -> None:
|
||||
print(" global — single session across all directories")
|
||||
new_strat = _prompt("Session strategy", default=current_strat)
|
||||
if new_strat in ("per-session", "per-repo", "per-directory", "global"):
|
||||
cfg["sessionStrategy"] = new_strat
|
||||
hermes_host["sessionStrategy"] = new_strat
|
||||
|
||||
cfg.setdefault("enabled", True)
|
||||
cfg.setdefault("saveMessages", True)
|
||||
hermes_host.setdefault("enabled", True)
|
||||
hermes_host.setdefault("saveMessages", True)
|
||||
|
||||
_write_config(cfg)
|
||||
print(f"\n Config written to {GLOBAL_CONFIG_PATH}")
|
||||
@@ -321,7 +320,7 @@ def cmd_peer(args) -> None:
|
||||
# Show current values
|
||||
hosts = cfg.get("hosts", {})
|
||||
hermes = hosts.get(HOST, {})
|
||||
user = cfg.get('peerName') or '(not set)'
|
||||
user = hermes.get('peerName') or cfg.get('peerName') or '(not set)'
|
||||
ai = hermes.get('aiPeer') or cfg.get('aiPeer') or HOST
|
||||
lvl = hermes.get("dialecticReasoningLevel") or cfg.get("dialecticReasoningLevel") or "low"
|
||||
max_chars = hermes.get("dialecticMaxChars") or cfg.get("dialecticMaxChars") or 600
|
||||
@@ -337,9 +336,9 @@ def cmd_peer(args) -> None:
|
||||
return
|
||||
|
||||
if user_name is not None:
|
||||
cfg["peerName"] = user_name.strip()
|
||||
cfg.setdefault("hosts", {}).setdefault(HOST, {})["peerName"] = user_name.strip()
|
||||
changed = True
|
||||
print(f" User peer → {cfg['peerName']}")
|
||||
print(f" User peer → {user_name.strip()}")
|
||||
|
||||
if ai_name is not None:
|
||||
cfg.setdefault("hosts", {}).setdefault(HOST, {})["aiPeer"] = ai_name.strip()
|
||||
|
||||
@@ -147,17 +147,28 @@ class HonchoClientConfig:
|
||||
)
|
||||
linked_hosts = host_block.get("linkedHosts", [])
|
||||
|
||||
api_key = raw.get("apiKey") or os.environ.get("HONCHO_API_KEY")
|
||||
api_key = (
|
||||
host_block.get("apiKey")
|
||||
or raw.get("apiKey")
|
||||
or os.environ.get("HONCHO_API_KEY")
|
||||
)
|
||||
|
||||
environment = (
|
||||
host_block.get("environment")
|
||||
or raw.get("environment", "production")
|
||||
)
|
||||
|
||||
# Auto-enable when API key is present (unless explicitly disabled)
|
||||
# This matches user expectations: setting an API key should activate the feature.
|
||||
explicit_enabled = raw.get("enabled")
|
||||
if explicit_enabled is None:
|
||||
# Not explicitly set in config -> auto-enable if API key exists
|
||||
enabled = bool(api_key)
|
||||
# Host-level enabled wins, then root-level, then auto-enable if key exists.
|
||||
host_enabled = host_block.get("enabled")
|
||||
root_enabled = raw.get("enabled")
|
||||
if host_enabled is not None:
|
||||
enabled = host_enabled
|
||||
elif root_enabled is not None:
|
||||
enabled = root_enabled
|
||||
else:
|
||||
# Respect explicit setting
|
||||
enabled = explicit_enabled
|
||||
# Not explicitly set anywhere -> auto-enable if API key exists
|
||||
enabled = bool(api_key)
|
||||
|
||||
# write_frequency: accept int or string
|
||||
raw_wf = (
|
||||
@@ -170,16 +181,31 @@ class HonchoClientConfig:
|
||||
except (TypeError, ValueError):
|
||||
write_frequency = str(raw_wf)
|
||||
|
||||
# saveMessages: host wins (None-aware since False is valid)
|
||||
host_save = host_block.get("saveMessages")
|
||||
save_messages = host_save if host_save is not None else raw.get("saveMessages", True)
|
||||
|
||||
# sessionStrategy / sessionPeerPrefix: host first, root fallback
|
||||
session_strategy = (
|
||||
host_block.get("sessionStrategy")
|
||||
or raw.get("sessionStrategy", "per-session")
|
||||
)
|
||||
host_prefix = host_block.get("sessionPeerPrefix")
|
||||
session_peer_prefix = (
|
||||
host_prefix if host_prefix is not None
|
||||
else raw.get("sessionPeerPrefix", False)
|
||||
)
|
||||
|
||||
return cls(
|
||||
host=host,
|
||||
workspace_id=workspace,
|
||||
api_key=api_key,
|
||||
environment=raw.get("environment", "production"),
|
||||
peer_name=raw.get("peerName"),
|
||||
environment=environment,
|
||||
peer_name=host_block.get("peerName") or raw.get("peerName"),
|
||||
ai_peer=ai_peer,
|
||||
linked_hosts=linked_hosts,
|
||||
enabled=enabled,
|
||||
save_messages=raw.get("saveMessages", True),
|
||||
save_messages=save_messages,
|
||||
**_resolve_memory_mode(
|
||||
raw.get("memoryMode", "hybrid"),
|
||||
host_block.get("memoryMode"),
|
||||
@@ -201,8 +227,8 @@ class HonchoClientConfig:
|
||||
or raw.get("recallMode")
|
||||
or "hybrid"
|
||||
),
|
||||
session_strategy=raw.get("sessionStrategy", "per-session"),
|
||||
session_peer_prefix=raw.get("sessionPeerPrefix", False),
|
||||
session_strategy=session_strategy,
|
||||
session_peer_prefix=session_peer_prefix,
|
||||
sessions=raw.get("sessions", {}),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user