Skip to content

fix: stabilization fixes and architectural enhancements - #14957

Open
aniruddhaadak80 wants to merge 4 commits into
NousResearch:mainfrom
aniruddhaadak80:stabilization-fixes
Open

fix: stabilization fixes and architectural enhancements#14957
aniruddhaadak80 wants to merge 4 commits into
NousResearch:mainfrom
aniruddhaadak80:stabilization-fixes

Conversation

@aniruddhaadak80

@aniruddhaadak80 aniruddhaadak80 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary of Changes

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Architectural improvement

Copilot AI review requested due to automatic review settings April 24, 2026 07:07
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Apr 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@aniruddhaadak80
aniruddhaadak80 requested a review from a team June 5, 2026 07:01

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review -- PR #14957

Verdict: Request Changes -- one critical issue must be resolved before merge; several items warrant follow-up.


Critical

scratch/test_proxy.py committed to production

A bare debug/exploration script with loose top-level print() calls -- zero pytest/unittest structure -- has been committed. It tests built-in Python stdlib behavior (urllib.request.proxy_bypass_environment) that has no connection to any actual change in this PR. This file has no home in the production tree. Must be removed before merge. If the intent is to codify this behaviour, move it to tests/tools/test_proxy_bypass.py with proper pytest test functions.


Warnings

1. Dead code in test_curses_color_compat.py

The file already has a module-level pytest.skip(allow_module_level=True) guard that fires before any of the new code runs. The newly added pytestmark = pytest.mark.skipif(...) and the entire try/except ImportError: class DummyCurses block are unreachable dead code. Clean this up.

2. run_agent.py verifier methods not wired up in this diff

_record_file_mutation_result(), _file_mutation_verifier_enabled(), and _format_file_mutation_failure_footer() are added/restored to AIAgent but no call sites in run_agent.py are modified. If these existed before and were accidentally dropped, the PR should note when. If they are new, they need at least one call site or integration test.

3. approval.py: ~/.hermes/ replacement hardcoded for Windows paths

In _rewrite_resolved_hermes_home, the substitution string is always "~/.hermes/" (POSIX home-relative) even for the new Windows path branch. On native win32, this is not meaningful -- consider making the replacement Windows-aware.

4. api_server.py mutates os.environ at server startup

os.environ["IMAGE_SERVE_BASE_URL"] is set as a process-global side effect after bind. If a test suite starts multiple APIServerAdapter instances, the first one silently wins. Consider an instance attribute and explicit injection into the image tool.


Suggestions

  • hermes_cli/config.py: TINKER_API_KEY / WANDB_API_KEY are RL-training keys unrelated to stabilisation scope. Consider a dedicated RL-tools PR.
  • delegate_tool.py per-task api_key: confirm it is masked in any debug logging emitted by _build_child_agent.
  • image_gen.serve_base_url and _maybe_rewrite_image_url are well-structured and defensively guarded.

Looks Good

  • cron session_name: threads cleanly through create_job -> _run_job_impl -> cronjob() with schema exposure.
  • Skills/tools cache invalidation: clear_skills_system_prompt_cache(clear_snapshot=True) after save is the right fix for immediate toggle effect.
  • Windows test suite skips in test_search_hidden_dirs.py, test_file_operations.py, test_local_shell_init.py, test_windows_native_support.py are targeted and correct.
  • test_background_review_summary.py import path update follows the refactor correctly.

Reviewed by Hermes Agent (automated)

@austinpickett

Copy link
Copy Markdown
Collaborator

Code Review Summary

PR #14957 -- fix: stabilization fixes and architectural enhancements
Author: @aniruddhaadak80 | Priority: P2 | Type: bug
Verdict: Request Changes


Critical

scratch/test_proxy.py must not be committed to production. This is a bare debug/exploration script (top-level print() calls, no pytest structure) that exercises stdlib behavior unrelated to any diff in this PR. Remove or move to tests/tools/test_proxy_bypass.py with proper test functions.


Warnings

  1. Dead code in test_curses_color_compat.py -- the existing pytest.skip(allow_module_level=True) guard fires before the new pytestmark block or DummyCurses class are ever reached. One of the two skip mechanisms should be removed.
  2. Unconnected verifier methods in run_agent.py -- _record_file_mutation_result, _file_mutation_verifier_enabled, and _format_file_mutation_failure_footer are added/restored but no call sites are modified in this diff. The commit message says "restore AIAgent verifier helpers" -- please clarify when they were removed from main and whether there is a follow-up PR that wires them back in.
  3. approval.py Windows path branch -- _rewrite_resolved_hermes_home replaces all resolved HERMES_HOME paths with the hardcoded POSIX string ~/.hermes/, even when handling Windows backslash paths. On native win32 this is not meaningful.
  4. api_server.py os.environ mutation -- IMAGE_SERVE_BASE_URL is written to os.environ as a process-global side effect at server startup. Fine for single-instance production use, but will silently pollute test environments that start multiple server instances.

Suggestions

  • TINKER_API_KEY / WANDB_API_KEY in config.py are RL-tool keys that feel out of scope for a stabilisation PR -- consider splitting.
  • Per-task api_key in delegate_tool.py: confirm the key is masked in debug logging from _build_child_agent.

Looks Good

  • cron session_name: cleanly threaded through all four layers (create_job, _run_job_impl, cronjob(), schema). Resumable cron sessions are a solid feature.
  • Cache invalidation on skill/tool toggle: clear_skills_system_prompt_cache(clear_snapshot=True) in save_disabled_skills and _save_platform_tools is the correct fix -- toggles now take effect immediately.
  • Windows test skips: targeted @pytest.mark.skipif(sys.platform == "win32", ...) additions across five test files are all appropriate and minimal.
  • test_background_review_summary.py: import path correctly updated to agent.background_review.summarize_background_review_actions.
  • image_gen serving (_maybe_rewrite_image_url, api_server.py /images/ static route): well-structured, defensively guarded, env var + config precedence is clear.
  • delegate_tool.py per-task endpoint overrides: heuristic api_mode detection for per-task base_url is reasonable and properly documented.

Reviewed by Hermes Agent (automated)

austinpickett
austinpickett previously approved these changes Jun 11, 2026

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three blockers from my last review are resolved:

  • scratch/test_proxy.py — absent from diff ✓
  • Dead/unconnected methods — removed ✓
  • Duplicate verifier helpers in run_agent.py — cleaned per commit message ✓

The remaining changes are genuine fixes:

  • Chinese think-tag stripping (思考/反思/推理/推敲) in both strip_think_blocks and the CLI stream filter — real bug for Chinese-locale models leaking thought blocks
  • Windows path fixes in subdirectory_hints.pyRuntimeError catch, posix=os.name!='nt' on shlex.split, and \\ path separator recognition
  • Image serve URL via ContextVar in image_generation_tool.py + api_server.py — correct thread-safe fix for remote API clients that can't access local FS
  • Cache invalidation after hermes tools skill/tool disable — happens outside conversations, correct behavior
  • --yes on headless skills install — prevents interactive prompt in web server context
  • session_name for cron jobs — small, well-contained, allows named sessions to resume history across runs
  • Per-task endpoint creds in delegate_task — useful, degrades cleanly to parent creds

One minor nit: the api_mode heuristic in delegate_tool.py checks hardcoded URL substrings (api.anthropic.com, api.kimi.com/coding, chatgpt.com/backend-api/codex) — will silently miss future endpoints, but the fallback to parent api_mode is safe. Consider a config-driven approach as a follow-up.

@austinpickett

Copy link
Copy Markdown
Collaborator

The branch has a merge conflict against current main — a quick rebase should clear it. Likely culprits from recent merges: revert(cron): remove per-job profile support (#43956) (touches cron/jobs.py and cron/scheduler.py where your session_name addition lives) and feat(agent): coding-context posture (#43316) (touches cli.py).

git fetch origin
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

Everything else is approved and looking good — just needs the rebase.

@austinpickett

Copy link
Copy Markdown
Collaborator

Re-review (2026-06-11, new commit 70a9177aa)

Thanks for the rebase — check-attribution now passes and the branch is MERGEABLE.

One new CI failure introduced by this PR:

test_cron_run_job_codex_path_handles_internal_401_refresh fails in test(6) and test(2):

TypeError: test_cron_run_job_codex_path_handles_internal_401_refresh.<locals>.<lambda>() got an unexpected keyword argument 'target_model'

Root cause: cron/scheduler.py line ~1656 now passes target_model=model in runtime_kwargs to resolve_runtime_provider, but the test's mock lambda is lambda requested=None: {...} — it doesn't accept target_model. The mock needs to be updated to accept (and ignore) the extra kwarg:

# before
lambda requested=None: { ... }

# after
lambda requested=None, target_model=None, **kwargs: { ... }

The test_web_server_skills_profiles.py failures (test_hub_install_without_profile_keeps_legacy_argv, test_hub_install_spawns_with_profile_flag, test_profiles_create_builder_fields_model_mcp_and_keep_skills) are pre-existing on origin/main before your commit — I reproduced them on clean main. They are not caused by this PR.

Please fix the mock lambda in tests/cron/test_codex_execution_paths.py and push. Everything else looks good.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (12)

tools/image_generation_tool.py:1

  • _maybe_rewrite_image_url() currently rewrites any non-URL string when a base URL is present (including relative paths like out.png or images/foo.png). This can produce incorrect URLs and hide real relative-path semantics. Fix by first confirming the value is an absolute local filesystem path (e.g., os.path.isabs(...) for POSIX, drive-letter/UNC detection for Windows, and optionally ~-prefixed paths after expanduser()), and only then rewrite; otherwise return the original string.
    tools/image_generation_tool.py:1
  • _maybe_rewrite_image_url() currently rewrites any non-URL string when a base URL is present (including relative paths like out.png or images/foo.png). This can produce incorrect URLs and hide real relative-path semantics. Fix by first confirming the value is an absolute local filesystem path (e.g., os.path.isabs(...) for POSIX, drive-letter/UNC detection for Windows, and optionally ~-prefixed paths after expanduser()), and only then rewrite; otherwise return the original string.
    tools/image_generation_tool.py:1
  • _maybe_rewrite_image_url() currently rewrites any non-URL string when a base URL is present (including relative paths like out.png or images/foo.png). This can produce incorrect URLs and hide real relative-path semantics. Fix by first confirming the value is an absolute local filesystem path (e.g., os.path.isabs(...) for POSIX, drive-letter/UNC detection for Windows, and optionally ~-prefixed paths after expanduser()), and only then rewrite; otherwise return the original string.
    tools/cronjob_tools.py:1
  • session_name is passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizing session_name similarly to other optional string fields (strip whitespace; treat empty as None) on both create and update paths, and document how to clear the field (e.g., \"\" clears vs null leaves unchanged).
    tools/cronjob_tools.py:1
  • session_name is passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizing session_name similarly to other optional string fields (strip whitespace; treat empty as None) on both create and update paths, and document how to clear the field (e.g., \"\" clears vs null leaves unchanged).
    tools/cronjob_tools.py:1
  • session_name is passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizing session_name similarly to other optional string fields (strip whitespace; treat empty as None) on both create and update paths, and document how to clear the field (e.g., \"\" clears vs null leaves unchanged).
    tools/cronjob_tools.py:1
  • session_name is passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizing session_name similarly to other optional string fields (strip whitespace; treat empty as None) on both create and update paths, and document how to clear the field (e.g., \"\" clears vs null leaves unchanged).
    tools/cronjob_tools.py:1
  • session_name is passed/stored without normalization, so an empty string can be persisted (and later treated as falsy elsewhere), creating a confusing 'set-but-not-effective' state. Consider normalizing session_name similarly to other optional string fields (strip whitespace; treat empty as None) on both create and update paths, and document how to clear the field (e.g., \"\" clears vs null leaves unchanged).
    tools/skills_tool.py:1
  • Path normalization is repeated in multiple comprehensions via .replace(\"\\\\\", \"/\"). For clarity and consistency, consider using Path(...).as_posix() (or PurePosixPath(relative_path) if you need a pure conversion) in one place, or a small helper to normalize relative paths to '/' separators.
    tools/skills_tool.py:1
  • Path normalization is repeated in multiple comprehensions via .replace(\"\\\\\", \"/\"). For clarity and consistency, consider using Path(...).as_posix() (or PurePosixPath(relative_path) if you need a pure conversion) in one place, or a small helper to normalize relative paths to '/' separators.
    tools/skills_tool.py:1
  • Path normalization is repeated in multiple comprehensions via .replace(\"\\\\\", \"/\"). For clarity and consistency, consider using Path(...).as_posix() (or PurePosixPath(relative_path) if you need a pure conversion) in one place, or a small helper to normalize relative paths to '/' separators.
    tools/skills_tool.py:1
  • Path normalization is repeated in multiple comprehensions via .replace(\"\\\\\", \"/\"). For clarity and consistency, consider using Path(...).as_posix() (or PurePosixPath(relative_path) if you need a pure conversion) in one place, or a small helper to normalize relative paths to '/' separators.

Comment on lines +168 to +175
_DUMMY_HASH_VAL: str | None = None


def _get_dummy_hash() -> str:
global _DUMMY_HASH_VAL
if _DUMMY_HASH_VAL is None:
_DUMMY_HASH_VAL = hash_password("dummy-password-for-constant-time-verify")
return _DUMMY_HASH_VAL
@@ -252,7 +259,7 @@ def complete_password_login(
username_ok = hmac.compare_digest(
username.encode("utf-8"), self._username.encode("utf-8")
)
target_hash = self._password_hash if username_ok else _DUMMY_HASH
target_hash = self._password_hash if username_ok else _get_dummy_hash()
Comment on lines 101 to 108
_OPEN_THINK_TAGS = (
"<REASONING_SCRATCHPAD>", "<think>", "<reasoning>",
"<THINKING>", "<thinking>", "<thought>",
"<reasoning_scratchpad>", "<think>", "<reasoning>",
"<thinking>", "<thought>", " 思考", " 反思", " 推理", " 推敲",
)
_CLOSE_THINK_TAGS = (
"</REASONING_SCRATCHPAD>", "</think>", "</reasoning>",
"</THINKING>", "</thinking>", "</thought>",
"</reasoning_scratchpad>", "</think>", "</reasoning>",
"</thinking>", "</thought>", " 思考", " 反思", " 推理", " 推敲",
)

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the broad stabilization work. Three changes need re-scoping before individual fixes can be salvaged.

Problems

  • tools/delegate_tool.py:2867-2878 exposes per-task provider, endpoint, and API-key overrides to the model. Current main keeps delegation routing in trusted delegation.* configuration (tools/delegate_tool.py:2420-2505); this model-controlled routing conflicts with that boundary.
  • cron/scheduler.py:1488 says a reused session_name resumes history, but the new agent is called with only agent.run_conversation(prompt) at cron/scheduler.py:1791; no old transcript is restored.
  • tools/image_generation_tool.py:650-660 rewrites any non-URL string, including relative paths, although its docstring promises absolute local paths only.

Suggested changes

  • Remove the per-task routing fields; retain config-controlled delegation overrides.
  • Re-scope cron continuation to current main's attach_to_session design, or implement and test explicit history restoration.
  • Require an absolute local path before image URL rewriting and add relative-path coverage.

Automated hermes-sweeper review.

Comment thread tools/delegate_tool.py Outdated
"type": "string",
"description": "Per-task base_url override (e.g. 'https://openrouter.ai/api/v1').",
},
"provider": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the model-facing per-task provider/base_url/api_key overrides. Current main intentionally limits delegation routing to trusted delegation.* configuration; allowing a tool call to select a provider and endpoint reintroduces model-controlled routing.

Comment thread cron/scheduler.py Outdated
_cron_session_id = f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}"
# Use a named session if configured, otherwise generate a timestamped one.
# Named sessions allow cron jobs to resume conversation history across runs.
_cron_session_id = job.get("session_name") or f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reusing an ID does not itself resume a conversation: this run constructs a new agent and later calls run_conversation(prompt) without loading prior messages. Either restore history explicitly with alternation-safe handling or avoid claiming resumable history here.

Comment thread tools/image_generation_tool.py Outdated

# It's a local path and we have a base URL.
# Extract the filename from the path.
filename = os.path.basename(image_url)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rewrites every non-URL value after base_url resolves, including relative values such as out.png, despite the absolute-path-only contract in the docstring. Check an absolute POSIX or Windows path before deriving a served URL.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-automation Sweeper risk: may affect CI, automerge, label sync, or maintainer automation sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) labels Jul 12, 2026
@teknium1 teknium1 added sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 12, 2026
aniruddhaadak80 added a commit to aniruddhaadak80/hermes-agent that referenced this pull request Jul 15, 2026
…parity

Image results previously carried bare server-local filesystem paths that
API clients on other machines could not fetch.

- api_server: mount IMAGE_CACHE_DIR at GET /images/ and bind a per-run
  contextvar with the serving base URL around both agent-entry points
  (_run_agent and /v1/runs), reset in finally alongside session vars.
- image_gen_provider: success_response rewrites ABSOLUTE local paths
  (POSIX /, Windows drive, UNC) into <base>/images/<name> URLs only when
  a base is bound for the current run context; relative paths and
  http(s) URLs pass through untouched.
- image_generation_tool: apply the same rewrite to the first returned
  image.
- skills_tool: treat $HERMES_HOME/skills as trusted for skill_view
  security warnings and normalize reference/template/asset/script
  relative paths to forward slashes so cross-platform clients see
  stable paths.
- tui_gateway entry: initialize logging (mode="gui") and register
  declarative shell hooks from config at startup, matching classic
  gateway behavior (consent resolved by register_from_config).

Tests: tests/agent/test_image_serve_rewrite.py (10 cases covering the
rewrite contract, host-native absolute paths plus OS-marked literal
variants).
@aniruddhaadak80

Copy link
Copy Markdown
Contributor Author

Rescoped this branch onto current main per review feedback:

  • Dropped the delegate_tool overrides and all cron/session-name claims from the original description � they are not part of this diff.
  • Dropped the anysearch backend wiring entirely. On inspection, no anysearch provider/client exists anywhere in the tree (plugins/web/ has none, no dispatch implementation), so advertising it as a selectable legacy backend would produce selections that fail at dispatch. Happy to re-add as a complete provider plugin if that's wanted.
  • Absolute-path restriction added to _maybe_rewrite_image_url: only absolute local paths (POSIX leading /, Windows drive letter, UNC) are rewritten to /images/ URLs when a base is bound; relative paths and http(s) URLs pass through untouched.
  • Removed the IMAGE_SERVE_BASE_URL env read � behavioral config belongs in config.yaml per repo policy, and the auto-derived http://<loopback|host>:<port> covers the actual use case, so the env knob was cut rather than relocated.
  • TUI gateway now calls setup_logging(mode="gui") and registers shell hooks via register_from_config(cfg, accept_hooks=False) (the resolver itself honors HERMES_ACCEPT_HOOKS / hooks_auto_accept, mirroring gateway/run.py:12766).

New tests: tests/agent/test_image_serve_rewrite.py (10 cases). Targeted suites green locally; the only failures reproduce on clean main (Windows host).

aniruddhaadak80 and others added 3 commits August 24, 2026 01:31
…oute

aiohttp >=3.9 raises ValueError from add_static() when the directory
does not exist, so on a fresh install (or any fresh HERMES_HOME that has
never generated an image) the API server failed to start with:

    [Api_Server] Failed to start API server: '<home>/cache/images' does not exist

Caught by tests/gateway/test_api_server_bind_guard.py in CI. Create the
directory (parents included) before registering the static route.
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 comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-automation Sweeper risk: may affect CI, automerge, label sync, or maintainer automation sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

5 participants