mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(honcho): wizard cadence default 2, surface reasoning level, backwards-compat fallback
Setup wizard now always writes dialecticCadence=2 on new configs and
surfaces the reasoning level as an explicit step with all five options
(minimal / low / medium / high / max), always writing
dialecticReasoningLevel.
Code keeps a backwards-compat fallback of 1 when dialecticCadence is
unset so existing honcho.json configs that predate the setting keep
firing every turn on upgrade. New setups via the wizard get 2
explicitly; docs show 2 as the default.
Also scrubs editorial lines from code and docs ("max is reserved for
explicit tool-path selection", "Unset → every turn; wizard pre-fills 2",
and similar process-exposing phrasing) and adds an inline link to
app.honcho.dev where the server-side observation sync is mentioned in
honcho.md. Recommended cadence range updated to 1-5 across docs and
wizard copy.
This commit is contained in:
parent
5b6792f04d
commit
21d5ef2f17
7 changed files with 40 additions and 18 deletions
|
|
@ -145,7 +145,7 @@ Controls **how often** dialectic and context calls happen.
|
||||||
| Key | Default | Description |
|
| Key | Default | Description |
|
||||||
|-----|---------|-------------|
|
|-----|---------|-------------|
|
||||||
| `contextCadence` | `1` | Min turns between context API calls |
|
| `contextCadence` | `1` | Min turns between context API calls |
|
||||||
| `dialecticCadence` | `1` (wizard: `2`) | Min turns between dialectic API calls. Unset → every turn; wizard pre-fills `2` |
|
| `dialecticCadence` | `2` | Min turns between dialectic API calls. Recommended 1–5 |
|
||||||
| `injectionFrequency` | `every-turn` | `every-turn` or `first-turn` for base context injection |
|
| `injectionFrequency` | `every-turn` | `every-turn` or `first-turn` for base context injection |
|
||||||
|
|
||||||
Higher cadence values fire the dialectic LLM less often. `dialecticCadence: 2` means the engine fires every other turn. Setting it to `1` fires every turn.
|
Higher cadence values fire the dialectic LLM less often. `dialecticCadence: 2` means the engine fires every other turn. Setting it to `1` fires every turn.
|
||||||
|
|
@ -370,7 +370,7 @@ Config file: `$HERMES_HOME/honcho.json` (profile-local) or `~/.honcho/config.jso
|
||||||
| `contextTokens` | uncapped | Max tokens for the combined base context injection (summary + representation + card). Opt-in cap — omit to leave uncapped, set to an integer to bound injection size. |
|
| `contextTokens` | uncapped | Max tokens for the combined base context injection (summary + representation + card). Opt-in cap — omit to leave uncapped, set to an integer to bound injection size. |
|
||||||
| `injectionFrequency` | `every-turn` | `every-turn` or `first-turn` |
|
| `injectionFrequency` | `every-turn` | `every-turn` or `first-turn` |
|
||||||
| `contextCadence` | `1` | Min turns between context API calls |
|
| `contextCadence` | `1` | Min turns between context API calls |
|
||||||
| `dialecticCadence` | `1` (wizard: `2`) | Min turns between dialectic LLM calls |
|
| `dialecticCadence` | `2` | Min turns between dialectic LLM calls (recommended 1–5) |
|
||||||
|
|
||||||
The `contextTokens` budget is enforced at injection time. If the session summary + representation + card exceed the budget, Honcho trims the summary first, then the representation, preserving the card. This prevents context blowup in long sessions.
|
The `contextTokens` budget is enforced at injection time. If the session summary + representation + card exceed the budget, Honcho trims the summary first, then the representation, preserving the card. This prevents context blowup in long sessions.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ class HonchoMemoryProvider(MemoryProvider):
|
||||||
self._turn_count = 0
|
self._turn_count = 0
|
||||||
self._injection_frequency = "every-turn" # or "first-turn"
|
self._injection_frequency = "every-turn" # or "first-turn"
|
||||||
self._context_cadence = 1 # minimum turns between context API calls
|
self._context_cadence = 1 # minimum turns between context API calls
|
||||||
self._dialectic_cadence = 1 # minimum turns between dialectic API calls
|
self._dialectic_cadence = 1 # backwards-compat fallback; wizard writes 2 on new configs
|
||||||
self._dialectic_depth = 1 # how many .chat() calls per dialectic cycle (1-3)
|
self._dialectic_depth = 1 # how many .chat() calls per dialectic cycle (1-3)
|
||||||
self._dialectic_depth_levels: list[str] | None = None # per-pass reasoning levels
|
self._dialectic_depth_levels: list[str] | None = None # per-pass reasoning levels
|
||||||
self._reasoning_heuristic: bool = True # scale base level by query length
|
self._reasoning_heuristic: bool = True # scale base level by query length
|
||||||
|
|
@ -304,6 +304,10 @@ class HonchoMemoryProvider(MemoryProvider):
|
||||||
raw = cfg.raw or {}
|
raw = cfg.raw or {}
|
||||||
self._injection_frequency = raw.get("injectionFrequency", "every-turn")
|
self._injection_frequency = raw.get("injectionFrequency", "every-turn")
|
||||||
self._context_cadence = int(raw.get("contextCadence", 1))
|
self._context_cadence = int(raw.get("contextCadence", 1))
|
||||||
|
# Backwards-compat: unset dialecticCadence falls back to 1
|
||||||
|
# (every turn) so existing honcho.json configs without the key
|
||||||
|
# behave as they did before. New setups via `hermes honcho setup`
|
||||||
|
# get dialecticCadence=2 written explicitly by the wizard.
|
||||||
self._dialectic_cadence = int(raw.get("dialecticCadence", 1))
|
self._dialectic_cadence = int(raw.get("dialecticCadence", 1))
|
||||||
self._dialectic_depth = max(1, min(cfg.dialectic_depth, 3))
|
self._dialectic_depth = max(1, min(cfg.dialectic_depth, 3))
|
||||||
self._dialectic_depth_levels = cfg.dialectic_depth_levels
|
self._dialectic_depth_levels = cfg.dialectic_depth_levels
|
||||||
|
|
@ -844,9 +848,7 @@ class HonchoMemoryProvider(MemoryProvider):
|
||||||
def _apply_reasoning_heuristic(self, base: str, query: str) -> str:
|
def _apply_reasoning_heuristic(self, base: str, query: str) -> str:
|
||||||
"""Scale `base` up by query length, clamped at reasoning_level_cap.
|
"""Scale `base` up by query length, clamped at reasoning_level_cap.
|
||||||
|
|
||||||
Char-count heuristic: +1 at >=120 chars, +2 at >=400. Ceiling is
|
Char-count heuristic: +1 at >=120 chars, +2 at >=400.
|
||||||
reasoning_level_cap (default 'high' — 'max' is reserved for
|
|
||||||
explicit tool-path selection).
|
|
||||||
"""
|
"""
|
||||||
if not self._reasoning_heuristic or not query:
|
if not self._reasoning_heuristic or not query:
|
||||||
return base
|
return base
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,8 @@ def cmd_setup(args) -> None:
|
||||||
current_dialectic = str(hermes_host.get("dialecticCadence") or cfg.get("dialecticCadence") or "2")
|
current_dialectic = str(hermes_host.get("dialecticCadence") or cfg.get("dialecticCadence") or "2")
|
||||||
print("\n Dialectic cadence:")
|
print("\n Dialectic cadence:")
|
||||||
print(" How often Honcho rebuilds its user model (LLM call on Honcho backend).")
|
print(" How often Honcho rebuilds its user model (LLM call on Honcho backend).")
|
||||||
print(" 1 = every turn, 2 = every other turn (wizard default), 3+ = sparse.")
|
print(" 1 = every turn, 2 = every other turn, 3+ = sparser.")
|
||||||
|
print(" Recommended: 1-5.")
|
||||||
new_dialectic = _prompt("Dialectic cadence", default=current_dialectic)
|
new_dialectic = _prompt("Dialectic cadence", default=current_dialectic)
|
||||||
try:
|
try:
|
||||||
val = int(new_dialectic)
|
val = int(new_dialectic)
|
||||||
|
|
@ -472,6 +473,25 @@ def cmd_setup(args) -> None:
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
hermes_host["dialecticCadence"] = 2
|
hermes_host["dialecticCadence"] = 2
|
||||||
|
|
||||||
|
# --- 7c. Dialectic reasoning level ---
|
||||||
|
current_reasoning = (
|
||||||
|
hermes_host.get("dialecticReasoningLevel")
|
||||||
|
or cfg.get("dialecticReasoningLevel")
|
||||||
|
or "low"
|
||||||
|
)
|
||||||
|
print("\n Dialectic reasoning level:")
|
||||||
|
print(" Depth Honcho uses when synthesizing user context on auto-injected calls.")
|
||||||
|
print(" minimal -- quick factual lookups")
|
||||||
|
print(" low -- straightforward questions (default)")
|
||||||
|
print(" medium -- multi-aspect synthesis")
|
||||||
|
print(" high -- complex behavioral patterns")
|
||||||
|
print(" max -- thorough audit-level analysis")
|
||||||
|
new_reasoning = _prompt("Reasoning level", default=current_reasoning)
|
||||||
|
if new_reasoning in ("minimal", "low", "medium", "high", "max"):
|
||||||
|
hermes_host["dialecticReasoningLevel"] = new_reasoning
|
||||||
|
else:
|
||||||
|
hermes_host["dialecticReasoningLevel"] = "low"
|
||||||
|
|
||||||
# --- 8. Session strategy ---
|
# --- 8. Session strategy ---
|
||||||
current_strat = hermes_host.get("sessionStrategy") or cfg.get("sessionStrategy", "per-session")
|
current_strat = hermes_host.get("sessionStrategy") or cfg.get("sessionStrategy", "per-session")
|
||||||
print("\n Session strategy:")
|
print("\n Session strategy:")
|
||||||
|
|
|
||||||
|
|
@ -254,8 +254,7 @@ class HonchoClientConfig:
|
||||||
# When true, the auto-injected dialectic scales reasoning level up on
|
# When true, the auto-injected dialectic scales reasoning level up on
|
||||||
# longer queries. See HonchoMemoryProvider for thresholds.
|
# longer queries. See HonchoMemoryProvider for thresholds.
|
||||||
reasoning_heuristic: bool = True
|
reasoning_heuristic: bool = True
|
||||||
# Ceiling for the heuristic-selected reasoning level. "max" is reserved
|
# Ceiling for the heuristic-selected reasoning level.
|
||||||
# for explicit tool-path selection.
|
|
||||||
reasoning_level_cap: str = "high"
|
reasoning_level_cap: str = "high"
|
||||||
# Honcho API limits — configurable for self-hosted instances
|
# Honcho API limits — configurable for self-hosted instances
|
||||||
# Max chars per message sent via add_messages() (Honcho cloud: 25000)
|
# Max chars per message sent via add_messages() (Honcho cloud: 25000)
|
||||||
|
|
|
||||||
|
|
@ -865,8 +865,10 @@ class TestDialecticCadenceDefaults:
|
||||||
_settle_prewarm(provider)
|
_settle_prewarm(provider)
|
||||||
return provider
|
return provider
|
||||||
|
|
||||||
def test_default_is_1(self):
|
def test_unset_falls_back_to_1(self):
|
||||||
"""Default dialectic_cadence is 1 — fires every turn unless overridden."""
|
"""Unset dialecticCadence falls back to 1 (every turn) for backwards
|
||||||
|
compatibility with existing configs that predate the setting. The
|
||||||
|
setup wizard writes 2 explicitly on new configs."""
|
||||||
provider = self._make_provider()
|
provider = self._make_provider()
|
||||||
assert provider._dialectic_cadence == 1
|
assert provider._dialectic_cadence == 1
|
||||||
|
|
||||||
|
|
@ -1569,8 +1571,7 @@ class TestDialecticLifecycleSmoke:
|
||||||
|
|
||||||
class TestReasoningHeuristic:
|
class TestReasoningHeuristic:
|
||||||
"""Char-count heuristic that scales the auto-injected reasoning level by
|
"""Char-count heuristic that scales the auto-injected reasoning level by
|
||||||
query length, clamped at reasoning_level_cap. 'max' is reserved for
|
query length, clamped at reasoning_level_cap."""
|
||||||
explicit tool-path selection."""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _make_provider(cfg_extra=None):
|
def _make_provider(cfg_extra=None):
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ Cost and depth are controlled by three independent knobs:
|
||||||
| Knob | Controls | Default |
|
| Knob | Controls | Default |
|
||||||
|------|----------|---------|
|
|------|----------|---------|
|
||||||
| `contextCadence` | Turns between `context()` API calls (base layer refresh) | `1` |
|
| `contextCadence` | Turns between `context()` API calls (base layer refresh) | `1` |
|
||||||
| `dialecticCadence` | Turns between `peer.chat()` LLM calls (dialectic layer refresh) | `1` (code default) / `2` (setup wizard default) |
|
| `dialecticCadence` | Turns between `peer.chat()` LLM calls (dialectic layer refresh) | `2` (recommended 1–5) |
|
||||||
| `dialecticDepth` | Number of `.chat()` passes per dialectic invocation (1–3) | `1` |
|
| `dialecticDepth` | Number of `.chat()` passes per dialectic invocation (1–3) | `1` |
|
||||||
|
|
||||||
These are orthogonal — you can have frequent context refreshes with infrequent dialectic, or deep multi-pass dialectic at low frequency. Example: `contextCadence: 1, dialecticCadence: 5, dialecticDepth: 2` refreshes base context every turn, runs dialectic every 5 turns, and each dialectic run makes 2 passes.
|
These are orthogonal — you can have frequent context refreshes with infrequent dialectic, or deep multi-pass dialectic at low frequency. Example: `contextCadence: 1, dialecticCadence: 5, dialecticDepth: 2` refreshes base context every turn, runs dialectic every 5 turns, and each dialectic run makes 2 passes.
|
||||||
|
|
@ -100,7 +100,7 @@ On session init, Honcho fires a dialectic call in the background at the full con
|
||||||
|
|
||||||
### Query-Adaptive Reasoning Level
|
### Query-Adaptive Reasoning Level
|
||||||
|
|
||||||
The auto-injected dialectic scales `dialecticReasoningLevel` by query length: +1 level at ≥120 chars, +2 at ≥400, clamped at `reasoningLevelCap` (default `"high"`). Disable with `reasoningHeuristic: false` to pin every auto call to `dialecticReasoningLevel`. `"max"` is reserved for explicit tool-path selection via `honcho_reasoning`.
|
The auto-injected dialectic scales `dialecticReasoningLevel` by query length: +1 level at ≥120 chars, +2 at ≥400, clamped at `reasoningLevelCap` (default `"high"`). Disable with `reasoningHeuristic: false` to pin every auto call to `dialecticReasoningLevel`. Available levels: `minimal`, `low`, `medium`, `high`, `max`.
|
||||||
|
|
||||||
## Configuration Options
|
## Configuration Options
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ Honcho is configured in `~/.honcho/config.json` (global) or `$HERMES_HOME/honcho
|
||||||
|-----|---------|-------------|
|
|-----|---------|-------------|
|
||||||
| `contextTokens` | `null` (uncapped) | Token budget for auto-injected context per turn. Set to an integer (e.g. 1200) to cap. Truncates at word boundaries |
|
| `contextTokens` | `null` (uncapped) | Token budget for auto-injected context per turn. Set to an integer (e.g. 1200) to cap. Truncates at word boundaries |
|
||||||
| `contextCadence` | `1` | Minimum turns between `context()` API calls (base layer refresh) |
|
| `contextCadence` | `1` | Minimum turns between `context()` API calls (base layer refresh) |
|
||||||
| `dialecticCadence` | `1` (wizard sets `2`) | Minimum turns between `peer.chat()` LLM calls (dialectic layer). Code default fires every turn when the key is unset; the setup wizard pre-fills `2`. In `tools` mode, irrelevant — model calls explicitly |
|
| `dialecticCadence` | `2` | Minimum turns between `peer.chat()` LLM calls (dialectic layer). Recommended 1–5. In `tools` mode, irrelevant — model calls explicitly |
|
||||||
| `dialecticDepth` | `1` | Number of `.chat()` passes per dialectic invocation. Clamped to 1–3 |
|
| `dialecticDepth` | `1` | Number of `.chat()` passes per dialectic invocation. Clamped to 1–3 |
|
||||||
| `dialecticDepthLevels` | `null` | Optional array of reasoning levels per pass, e.g. `["minimal", "low", "medium"]`. Overrides proportional defaults |
|
| `dialecticDepthLevels` | `null` | Optional array of reasoning levels per pass, e.g. `["minimal", "low", "medium"]`. Overrides proportional defaults |
|
||||||
| `dialecticReasoningLevel` | `'low'` | Base reasoning level: `minimal`, `low`, `medium`, `high`, `max` |
|
| `dialecticReasoningLevel` | `'low'` | Base reasoning level: `minimal`, `low`, `medium`, `high`, `max` |
|
||||||
|
|
@ -183,7 +183,7 @@ Common patterns:
|
||||||
| AI shouldn't re-model the user from its own replies | `"ai": {"observeMe": true, "observeOthers": false}` |
|
| AI shouldn't re-model the user from its own replies | `"ai": {"observeMe": true, "observeOthers": false}` |
|
||||||
| Strong persona the AI peer shouldn't update from self-observation | `"ai": {"observeMe": false, "observeOthers": true}` |
|
| Strong persona the AI peer shouldn't update from self-observation | `"ai": {"observeMe": false, "observeOthers": true}` |
|
||||||
|
|
||||||
Server-side toggles set via the Honcho dashboard win over local defaults — Hermes syncs them back at session init.
|
Server-side toggles set via the [Honcho dashboard](https://app.honcho.dev) win over local defaults — Hermes syncs them back at session init.
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ hermes memory setup # select "honcho"
|
||||||
| `workspace` | host key | Shared workspace ID |
|
| `workspace` | host key | Shared workspace ID |
|
||||||
| `contextTokens` | `null` (uncapped) | Token budget for auto-injected context per turn. Truncates at word boundaries |
|
| `contextTokens` | `null` (uncapped) | Token budget for auto-injected context per turn. Truncates at word boundaries |
|
||||||
| `contextCadence` | `1` | Minimum turns between `context()` API calls (base layer refresh) |
|
| `contextCadence` | `1` | Minimum turns between `context()` API calls (base layer refresh) |
|
||||||
| `dialecticCadence` | `1` (wizard sets `2`) | Minimum turns between `peer.chat()` LLM calls. Unset → every turn; wizard pre-fills `2`. Only applies to `hybrid`/`context` modes |
|
| `dialecticCadence` | `2` | Minimum turns between `peer.chat()` LLM calls. Recommended 1–5. Only applies to `hybrid`/`context` modes |
|
||||||
| `dialecticDepth` | `1` | Number of `.chat()` passes per dialectic invocation. Clamped 1–3. Pass 0: cold/warm prompt, pass 1: self-audit, pass 2: reconciliation |
|
| `dialecticDepth` | `1` | Number of `.chat()` passes per dialectic invocation. Clamped 1–3. Pass 0: cold/warm prompt, pass 1: self-audit, pass 2: reconciliation |
|
||||||
| `dialecticDepthLevels` | `null` | Optional array of reasoning levels per pass, e.g. `["minimal", "low", "medium"]`. Overrides proportional defaults |
|
| `dialecticDepthLevels` | `null` | Optional array of reasoning levels per pass, e.g. `["minimal", "low", "medium"]`. Overrides proportional defaults |
|
||||||
| `dialecticReasoningLevel` | `'low'` | Base reasoning level: `minimal`, `low`, `medium`, `high`, `max` |
|
| `dialecticReasoningLevel` | `'low'` | Base reasoning level: `minimal`, `low`, `medium`, `high`, `max` |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue