Skip to content

feat(agent): opt-in recovery hook for wedged local inference backends - #37176

Closed
OmarB97 wants to merge 1 commit into
NousResearch:mainfrom
OmarB97:codex/pr35642-local-backend-recovery
Closed

feat(agent): opt-in recovery hook for wedged local inference backends#37176
OmarB97 wants to merge 1 commit into
NousResearch:mainfrom
OmarB97:codex/pr35642-local-backend-recovery

Conversation

@OmarB97

@OmarB97 OmarB97 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What

When a local backend trips the first-chunk or non-stream stale watchdog (see #37168, which this PR stacks on), Hermes can now invoke an operator-configured recovery command — useful for self-hosted servers that wedge in ways only the operator knows how to repair (slot cancellation, child-process kill, service restart).

How

  • New agent/local_backend_recovery.py: maybe_recover_local_backend() runs HERMES_LOCAL_BACKEND_RECOVERY_COMMAND (opt-in, default off) with sanitized metadata passed via HERMES_RECOVERY_* env vars, rate-limited by HERMES_LOCAL_BACKEND_RECOVERY_COOLDOWN (default 60s) so retries cannot flap a shared GPU service.
  • Hook points in the non-stream stale-kill and first-chunk timeout paths; failures in the hook are logged and never affect the turn.
  • scripts/recover_local_llama_server.py: a reference recovery script for llama-server-style backends.
  • New transport default: HERMES_LOCAL_DEFAULT_MAX_TOKENS caps local completions (default 8192) so a wedged generation cannot run unbounded.

Tests

tests/agent/test_local_backend_recovery.py (env contract, cooldown, opt-in gating), tests/test_recover_local_llama_server.py, transport tests for the max-tokens default. 300 tests pass locally on the stack.

Stacked on #37168 — until that merges, this diff shows the union of both; the recovery-specific changes are agent/local_backend_recovery.py, the two hook sites, scripts/recover_local_llama_server.py, and their tests. Part 5 of the former #35642 split.

@OmarB97
OmarB97 marked this pull request as ready for review June 2, 2026 03:26
@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have labels Jun 2, 2026
@OmarB97

OmarB97 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

From: Reviewer (ko-mac.codex#handoff-pr35642-codex-20260602) — on ko-mac

Needs changeshermes-pr35642-split-snowball-20260602-pr37176, attempt 1 · by ko-mac.codex#handoff-pr35642-codex-20260602

Blocking: the bundled recovery script appends /slots directly to HERMES_RECOVERY_BASE_URL. Hermes passes OpenAI-compatible base URLs such as http://127.0.0.1:9090/v1 (the new hook test asserts that exact env value), so this probes http://127.0.0.1:9090/v1/slots instead of llama.cpp's management endpoint at /slots. That means the default shipped recovery command misses slot cancellation and falls through to no-op/kill behavior. Please normalize a trailing /v1 before building the /slots and /slots/{id}?action=cancel URLs, and add a script-level regression test for a /v1 base URL.

Merge gate: Evidence-backed blocking review feedback must be resolved in a new attempt.

@OmarB97

OmarB97 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

From: Reviewer (ko-mac.codex#handoff-pr35642-codex-20260602) — on ko-mac

Needs changeshermes-pr35642-split-snowball-20260602-pr37176, attempt 1 · by ko-mac.codex#handoff-pr35642-codex-20260602

Request changes: recover_local_llama_server.py must normalize OpenAI-compatible /v1 base URLs before probing/canceling llama.cpp /slots.

Merge gate: Evidence-backed blocking review feedback must be resolved in a new attempt.

@OmarB97

OmarB97 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review blocker in 2a3903558.

  • scripts/recover_local_llama_server.py now normalizes OpenAI-compatible .../v1 base URLs before building llama.cpp management URLs, so recovery probes/cancels hit /slots and /slots/{id}?action=cancel instead of /v1/slots.
  • Added script-level regression coverage for both the /v1 active-slot probe and slot-cancel URLs.

Verification:

  • scripts/run_tests.sh tests/test_recover_local_llama_server.py tests/agent/test_local_backend_recovery.py tests/agent/transports/test_chat_completions.py tests/agent/test_local_stream_timeout.py tests/hermes_cli/test_timeouts.py -> 148 passed
  • python -m py_compile scripts/recover_local_llama_server.py tests/test_recover_local_llama_server.py
  • git diff --cached --check

@OmarB97
OmarB97 force-pushed the codex/pr35642-local-backend-recovery branch from 2a39035 to 98799a6 Compare June 10, 2026 03:11
@OmarB97 OmarB97 changed the title [Hermes Agent][hermes-pr35642-split-snowball][5/n] Split local backend recovery hook feat(agent): opt-in recovery hook for wedged local inference backends Jun 10, 2026
@OmarB97
OmarB97 force-pushed the codex/pr35642-local-backend-recovery branch from 98799a6 to 209253e Compare July 5, 2026 19:15
Reapply the opt-in recovery hook for wedged local inference backends
(agent/local_backend_recovery.py, scripts/recover_local_llama_server.py,
the resolve_stream_stale_timeout local-provider stream/first-chunk/
non-stream timeout resolvers) plus its three stacked fix-up commits
(local non-stream timeout must beat the reasoning floor + restore
first_chunk_seen; telegram /new "parallel work" de-flake; MoA virtual
endpoint output-cap exclusion) onto current upstream/main.

Conflict resolution:
- agent/transports/chat_completions.py (import block): upstream removed
  the now-unused `import copy`; kept this branch's `import os` (used by
  the local-default-max-tokens env lookups added to this file) and
  dropped `copy` (confirmed no remaining use in the file).
- agent/chat_completion_helpers.py (3 spots): upstream independently
  grew its own cross-turn stale-call circuit breaker (NousResearch#58962:
  _stale_streak/_bump_stale_streak/_reset_stale_streak/_check_stale_giveup)
  at the same insertion points as this branch's local-provider timeout
  resolvers and local-backend-recovery hook. Both features are additive
  with no logic overlap — kept both side by side: the circuit-breaker
  block ahead of the local timeout helpers; _bump_stale_streak(agent)
  alongside the local-backend-recovery call in the non-streaming
  stale-kill path; _check_stale_giveup(agent) folded into the same
  result-dict block that carries local_first_chunk_timeout.
- tests/gateway/test_telegram_topic_mode.py: upstream already carries an
  equivalent fix for the same "parallel work" tip-collision flake in the
  same test (different patch target/style, identical intent) — took
  upstream's version; this branch's now-redundant duplicate nets to zero
  diff vs upstream in this file.

Ran scripts/run_tests.sh across all 8 touched-module test files
(test_error_classifier, test_local_backend_recovery, test_local_stream_timeout,
transports/test_chat_completions, hermes_cli/test_timeouts,
run_agent/test_run_agent, test_recover_local_llama_server,
gateway/test_telegram_topic_mode): 821 passed, 0 failed. py_compile clean
on all touched files.

Refresh of PR NousResearch#37176; original head 025569e9573a72d00a6fb04937afbf85dd326d24.
@OmarB97
OmarB97 force-pushed the codex/pr35642-local-backend-recovery branch from 025569e to 3b930f3 Compare July 11, 2026 00:51
@OmarB97

OmarB97 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Refreshed onto upstream/main (rebase, live merge-base d33becd877, 349 commits behind). Two real conflicts:

  1. agent/transports/chat_completion s.py import block — upstream dropped the now-unused import copy; kept this branch's import os (needed by the local-default-max-tokens env lookups this PR adds to the file), dropped copy.
  2. agent/chat_completion_helpers.py (3 spots) — same add/add class as [codex] Add progress outcome canary #35702: upstream independently grew its own cross-turn stale-call circuit breaker (_stale_streak/_check_stale_giveup, Sessions permanently stuck in Stream stale loop - never recovers (OpenAI-compatible provider) #58962) at the same insertion points as this PR's local-provider timeout resolvers/local-backend-recovery hook. No logic overlap — kept both side by side (circuit-breaker block ahead of the timeout helpers; _bump_stale_streak alongside the recovery call in the non-streaming stale-kill path; _check_stale_giveup folded into the same result-dict block that carries local_first_chunk_timeout).

A third apparent conflict (tests/gateway/test_telegram_topic_mode.py) resolved to zero diff: upstream already carries an equivalent fix for the same "parallel work" tip-collision flake in the same test, so this PR's now-redundant duplicate was dropped in favor of upstream's.

Original head 025569e957 -> refreshed 3b930f3ce1. Content unchanged vs original diff (same 15-file set as the original merge-base..head diff). Local verification: scripts/run_tests.sh across all 8 touched-module test files — 821 passed, 0 failed. Checks re-running.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the careful local-backend recovery work, including the follow-up that normalizes OpenAI-compatible /v1 URLs before llama.cpp slot operations.

Automated hermes-sweeper review found that this PR’s user-facing interface is new non-secret HERMES_* configuration:

  • agent/local_backend_recovery.py:39-58 configures the command, cooldown, and command timeout through HERMES_LOCAL_BACKEND_RECOVERY_* variables.
  • agent/chat_completion_helpers.py and agent/transports/chat_completions.py add further local timeout and output-cap environment knobs.
  • Hermes policy requires behavioral settings such as timeouts, thresholds, feature flags, and commands to be configured in config.yaml, not introduced as new .env variables. Existing provider stale-timeout configuration already resolves from providers.<id>.stale_timeout_seconds and model overrides (hermes_cli/timeouts.py:43-69).

Please re-scope any follow-up around a profile-safe config.yaml mechanism (with setup/docs integration where appropriate) rather than new user-facing environment variables.

This is an automated hermes-sweeper review.


Closed as not-planned per standing maintainer policy (env-var-for-config). This is a design-direction decision, not a code-quality judgment — see the Contribution Rubric in AGENTS.md for what the project is looking for. If you believe this policy was misapplied to your change, comment here and a maintainer will take a look.

@teknium1 teknium1 closed this Jul 13, 2026
@teknium1 teknium1 added sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants