fix(config): merge duplicate kanban block so auto_subscribe_on_create default survives

DEFAULT_CONFIG declared "kanban" twice. Python keeps only the last
literal for a duplicate key, so the first kanban block was silently
dropped and its "auto_subscribe_on_create": True default never made it
into DEFAULT_CONFIG. The consumer in tools/kanban_tools.py masks the
miss with cfg_get(..., default=True), but the documented default was
absent from DEFAULT_CONFIG (so config templates / 'hermes config show'
omit it), and the duplicate key is a standing hazard: any future key
added to the first block would also vanish.

Merge auto_subscribe_on_create into the single canonical kanban block.

Adds a regression test asserting both default sets survive and a guard
against any duplicate top-level DEFAULT_CONFIG key.
This commit is contained in:
MaxFreedomPollard 2026-07-25 00:12:58 -04:00 committed by Teknium
parent 2365fed985
commit aa636c6fca
3 changed files with 41 additions and 15 deletions

View file

@ -0,0 +1 @@
MaxFreedomPollard

View file

@ -1562,21 +1562,6 @@ DEFAULT_CONFIG = {
# Example: 1800 = compact after 30 min idle.
},
# Kanban subsystem (orchestrator workers + dispatcher-driven child tasks).
# See tools/kanban_tools.py and hermes_cli/kanban_db.py for the actual
# implementations. Per-platform notification opt-out is handled by the
# kanban dashboard (see ``hermes dashboard`` -> Notifications).
"kanban": {
# Auto-subscribe the originating gateway/TUI session to task
# completion + block events when ``kanban_create`` is called from
# inside a session that has a persistent delivery channel. The
# agent that dispatched the task will get notified automatically
# instead of having to poll. Disable to mirror pre-feature
# behaviour — e.g. for a profile that prefers explicit
# ``kanban_notify-subscribe`` calls per task.
"auto_subscribe_on_create": True,
},
# Anthropic prompt caching (Claude via OpenRouter or native Anthropic API).
# cache_ttl must be "5m" or "1h" (Anthropic-supported tiers); other values are ignored.
"prompt_caching": {
@ -2920,6 +2905,14 @@ DEFAULT_CONFIG = {
# each claimable ready task. One dispatcher per profile is sufficient;
# running more than one on the same kanban.db will race for claims.
"kanban": {
# Auto-subscribe the originating gateway/TUI session to task
# completion + block events when ``kanban_create`` is called from
# inside a session that has a persistent delivery channel. The
# agent that dispatched the task will get notified automatically
# instead of having to poll. Disable to mirror pre-feature
# behaviour — e.g. for a profile that prefers explicit
# ``kanban_notify-subscribe`` calls per task.
"auto_subscribe_on_create": True,
# Run the dispatcher inside the gateway process. On by default —
# the cost is ~300µs every `dispatch_interval_seconds` when idle,
# and gateway is the supervisor users already have. Set to false

View file

@ -2385,3 +2385,35 @@ class TestProviderEnabledRuntimeGate:
assert "disabled" not in str(e).lower()
except Exception:
pass # any non-ValueError is fine; we only gate the disabled path
# ---------------------------------------------------------------------------
# DEFAULT_CONFIG must not carry a duplicate "kanban" key
# ---------------------------------------------------------------------------
def test_default_config_kanban_block_not_dropped_by_duplicate_key():
"""DEFAULT_CONFIG previously declared ``"kanban"`` twice, so Python kept
only the second literal and silently dropped the first losing the
``auto_subscribe_on_create`` default. Both sets of defaults must survive.
"""
kanban = DEFAULT_CONFIG["kanban"]
# From the first (dropped) block:
assert kanban.get("auto_subscribe_on_create") is True
# From the second block:
assert "dispatch_in_gateway" in kanban
assert "auto_decompose" in kanban
def test_default_config_has_no_duplicate_top_level_keys():
"""Guard against any duplicate key silently shadowing a default."""
import ast
import hermes_cli.config as cfg_mod
src = open(cfg_mod.__file__, encoding="utf-8").read()
tree = ast.parse(src)
for node in ast.walk(tree):
if isinstance(node, ast.Dict):
keys = [k.value for k in node.keys if isinstance(k, ast.Constant)]
if "model" in keys and "kanban" in keys: # the DEFAULT_CONFIG literal
dupes = {k for k in keys if keys.count(k) > 1}
assert not dupes, f"duplicate DEFAULT_CONFIG keys: {sorted(dupes)}"