Commit Graph
12 Commits
Author SHA1 Message Date
zccyman 2e181602a1 fix(agent): isolate credential pool on provider fallback
Closes #33163.

When _try_activate_fallback() switches from one provider to another (e.g.
openai-codex → openrouter), the credential pool still belongs to the
primary provider. This causes two compounding bugs:

1. The pool retains the primary's base_url. Downstream pool recovery
   (rate_limit / billing / auth) calls _swap_credential() with a primary
   entry which overwrites the agent's base_url back to the primary's
   endpoint. Every fallback request then 404s against the wrong host.

2. Pool recovery acting on errors from the FALLBACK provider mutates the
   PRIMARY's pool state (#33088 reported a related corruption pattern),
   exhausting/rotating entries that have nothing to do with the failure.

Two layered fixes:

a) try_activate_fallback (agent/chat_completion_helpers.py): on fallback
   activation, clear agent._credential_pool when the fallback provider
   doesn't match the pool's provider. Pool is preserved when the fallback
   shares the pool's provider (e.g. multiple openrouter entries).

b) recover_with_credential_pool (agent/agent_runtime_helpers.py):
   defensive guard rejects any pool mutation when agent.provider doesn't
   match pool.provider. Defense-in-depth — should never fire after (a)
   is in place, but covers any future path that attaches a stale pool.

Salvaged from @zccyman's PR #33217. The original PR was written against
the pre-refactor monolithic run_agent.py; both target functions have
since been extracted to module-level helpers. Behavior is identical —
the guards live in the canonical extracted locations.

Tests
- New tests/run_agent/test_fallback_credential_isolation.py (7 tests
  covering: fallback clears mismatched pool, fallback preserves matching
  pool, recovery rejects mismatched pool, recovery accepts matching
  pool, 429-from-z.ai-doesn't-exhaust-codex-pool, _client_kwargs
  base_url survives pool clear, _swap_credential doesn't restore
  primary URL after fallback).
- Cross-verified: 77/77 passing across fallback isolation tests +
  agent/test_credential_pool.py — no regression.

Co-authored-by: zccyman <16263913+zccyman@users.noreply.github.com>
2026-05-27 10:45:26 -07:00
zccyman 973bb124a4 fix(credential-pool): rotate immediately when credential already exhausted
Closes #26145.

When the user interrupts the retry loop between two 429s (Ctrl-C in
interactive mode, /new, gateway disconnect), the local has_retried_429
flag dies with the recovery function. On the next user prompt the agent
restarts with has_retried_429=False, hits 429 on the exhausted credential,
sets the flag, returns 'retry once'. Repeat forever — the second 429 that
would trigger rotation is never reached, and healthy entries (priority>0
free/paid accounts) are never tried.

Fix: in recover_with_credential_pool's rate_limit branch, pre-check
pool.current().last_status before running the retry-once dance. If the
current entry is already STATUS_EXHAUSTED, rotate immediately. Uses
getattr() for the attribute read so existing tests with SimpleNamespace
mocks (which only set 'label') keep working.

Co-authored-by: zccyman <16263913+zccyman@users.noreply.github.com>
2026-05-25 06:21:28 -07:00
zccyman 58591d9e34 feat: show names of user-modified skills in bundled skill sync summary
When 'hermes update' syncs bundled skills, the summary line only shows
the count of user-modified skills that were kept (e.g. '3 user-modified
(kept)'), but not *which* skills. Once the update finishes, the user
has no way to know which skills need triage.

Append the skill names to the summary line, truncated to 5 with a
'+N more' suffix for long lists:

  Done: 12 new, 3 updated, 7 unchanged, 3 user-modified (kept):
  hermes-agent, debugging-hermes-tui-commands, system-health.
  25 total bundled.

Closes #28121
2026-05-19 03:02:53 -07:00
zccyman fe5e0bf5a3 feat(kanban): add board-level default workdir (#25430) 2026-05-18 20:24:04 -07:00
zccyman 2e09d2567c feat(kanban): add auto_promote_children config toggle
When the kanban auto-decomposer fans a triage task into child tasks,
recompute_ready() immediately promotes parent-free children to 'ready'
so the dispatcher picks them up. Some users want a manual workflow
where children stay in 'todo' for review before dispatch.

Add 'kanban.auto_promote_children' config key (default: true):
- false: children stay in 'todo' after decomposition
- true: existing behavior (auto-promote to 'ready')

Changes:
- kanban_db.py: decompose_triage_task() gains auto_promote param
- kanban_decompose.py: reads auto_promote_children from config
- kanban dashboard API: exposes the new setting in GET/PUT /orchestration

Closes #28016
2026-05-18 20:04:32 -07:00
zccyman 5987b24314 fix(gateway): exit code 75 on service restart so launchd relaunches
When the gateway receives SIGUSR1 (graceful restart via launchd_restart),
the SIGUSR1 handler calls request_restart(via_service=True) and the
gateway shuts down cleanly with exit code 0.

However, the generated launchd plist uses KeepAlive → SuccessfulExit →
false, meaning launchd only relaunches on *non-zero* exit codes.  A
clean exit(0) is treated as "successful, don't restart", so the
gateway stays down after /restart, /update, or SIGUSR1.

The systemd unit template already uses RestartForceExitStatus=75 for the
same scenario.  Mirror that convention: when _restart_via_service is
True, raise SystemExit(75) so launchd's SuccessfulExit=false policy
triggers a relaunch.

Closes #28135
2026-05-18 20:03:19 -07:00
zccyman af78449acd feat(bg-review): add bundled/pinned skill protection rules to review prompts (#27644)
The background review prompts (_SKILL_REVIEW_PROMPT and
_COMBINED_REVIEW_PROMPT) now include explicit protection rules
for bundled, hub-installed, and pinned skills — aligning with
the curator's existing policy at curator.py L345/350.

Before this change, bg-review could freely rewrite bundled skills
like 'hermes-agent' or pinned skills, while the 7-day curator
explicitly skips them.

The review agent now sees:
  • Bundled skills (shipped with Hermes)
  • Hub-installed skills (installed via hermes skills install)
  • Pinned skills (marked via hermes curator pin)
If only protected skills need updating, the review says
'Nothing to save.' and stops.

Fixes #27644
2026-05-18 20:02:22 -07:00
zccyman 4e9df52d60 fix: elevate plugin discovery failures from debug to warning
Plugin discovery exceptions in gateway startup (gateway/run.py) and
CLI startup (hermes_cli/main.py) are caught and logged at DEBUG
level, making them invisible at the default INFO log level.

If any plugin import fails — syntax error, missing dependency, import
cycle — operators get zero indication unless they bump the log level
to DEBUG. This makes broken plugins appear enabled but silently
non-functional.

Change both locations to logger.warning() so failures are visible at
production log levels.

Closes #28137
2026-05-18 19:35:41 -07:00
zccyman 7923f844fa fix: include hermes_plugins in gateway.log component filter
gateway.log uses a _ComponentFilter that only passes records from
loggers starting with ('gateway',). Plugin modules are loaded under
the hermes_plugins.* namespace, so all plugin log output is silently
dropped from gateway.log.

This makes plugin registration — which directly affects gateway hooks
(pre_gateway_dispatch, transform_llm_output, etc.) — invisible in
the gateway-specific log. Operators debugging gateway behavior check
gateway.log and see no plugin activity, even when plugins are working
correctly.

Add 'hermes_plugins' to the gateway component prefixes tuple so
plugin log messages appear in gateway.log.

Closes #28138
2026-05-18 19:33:30 -07:00
zccyman a574246837 feat(auxiliary): add configurable fallback chains + main-agent safety net
Layered fallback for auxiliary tasks (compression, vision, tts, web_extract,
session_search, etc.):

  1. Primary aux provider (existing)
  2. User-configured auxiliary.<task>.fallback_chain (new)
  3. Main agent provider + model (new — last-resort safety net)
  4. Warn user + re-raise original error (new)

For users on 'auto' (no explicit aux provider), the existing
_try_payment_fallback auto-detection chain runs instead — its Step 1
already IS the main agent model, so they get the same behaviour without
configuration.

The configured fallback_chain config schema comes from #26882 / @zccyman;
the main-agent safety net + exhaustion warning were added on top.

Closes #26882. Builds on the capacity-error gate fix in the previous
commit (#26803 / @Bartok9).
2026-05-17 17:15:31 -07:00
zccyman f26098e22f fix(gateway): enable text-intercept for multi-choice clarify fallback (#25567) 2026-05-14 07:59:12 -07:00
zccyman 88ede807c4 fix(pricing): add deepseek-v4-pro to official docs pricing table
deepseek-v4-pro has been routable since v0.12 but was missing from
the _OFFICIAL_DOCS_PRICING table. Sessions using this model showed
as "unknown cost" in hermes insights instead of a dollar estimate.

Add pricing entry using published list prices:
- input: \$1.74/M tokens
- output: \$3.48/M tokens
- cache_read: \$0.0145/M tokens

Uses standard list rates (not the 75% promo) so estimates remain
accurate after promo expires 2026-05-31.

Closes #24218
2026-05-12 16:32:57 -07:00