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:
Erosika 2026-03-11 17:45:35 -04:00
parent d987ff54a1
commit 3c813535a7
2 changed files with 64 additions and 39 deletions

View file

@ -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()