Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused fix. I verified the underlying bug path still exists on current main: gateway/run.py:11952-11969 calls vision_analyze_tool once and emits the kawaii fallback immediately when the result has success: false.
Problems
- The retry knob is implemented as a new user-facing env var:
gateway/run.py:14237readsHERMES_VISION_AUTO_RETRIES, andwebsite/docs/reference/environment-variables.md:507documents it. That conflicts with the repo rubric inAGENTS.md:102-106, which says non-secret behavioral settings belong inconfig.yaml, not newHERMES_*env vars. - Salvage will need conflict resolution: the PR patch targets the old
gateway/run.py:14184neighborhood, while current main has_enrich_message_with_visionaroundgateway/run.py:11918.
Suggested changes
- Move the retry budget into config.yaml, preferably under the existing auxiliary vision configuration surface, and update the tests/docs to cover that config path.
- Keep the inline bounded retry behavior; the premise is real, and the classifier/backoff shape looks like the right layer for the reported failure.
Automated hermes-sweeper review.
| back to the documented default so a typo never silently | ||
| disables the safety net. | ||
| """ | ||
| raw = os.environ.get("HERMES_VISION_AUTO_RETRIES") |
There was a problem hiding this comment.
This should not be a new user-facing HERMES_* env var. AGENTS.md:102-106 says non-secret behavioral settings like retry budgets belong in config.yaml; please route this through the existing config surface, likely under auxiliary vision settings, and update the docs/tests accordingly.
) Discord-cached image attachments occasionally come back success: false from the first vision_analyze call inside _enrich_message_with_vision even though a second call against the same local path succeeds. The agent sees the kawaii fallback string, recognises it, and reissues vision_analyze manually — costing ~30s and one wasted tool round-trip per affected image, every session. Add a bounded inline retry inside _enrich_message_with_vision: default 1 retry, exponential backoff capped at 3s, with a permanent-failure classifier that short-circuits the budget (image too large, insufficient credits, model does not support vision, SSRF block, interrupt). Exceptions still bubble out so the existing "something went wrong" branch keeps owning non-transient failures. The retry budget is a non-secret behavioral setting, so per AGENTS.md it lives in config.yaml under auxiliary.vision.auto_retries (0 = legacy single-shot), not a new HERMES_* env var.
New test_vision_auto_retry.py exercises the retry budget resolver (auxiliary.vision.auto_retries from config.yaml, including default/zero/ clamp/garbage/load-failure paths), the transient-vs-permanent classifier, the retry loop, and the public _enrich_message_with_vision entry point (the NousResearch#28972 repro, kawaii fallback preserved, per-image budget isolation). The existing test_vision_memory_leak.py fixture is extended to bind the new retry helpers so its sanitize-context coverage exercises the real path.
0de8763 to
f3918db
Compare
|
Thanks for the focused investigation and for updating the retry setting to Automated hermes-sweeper review found this behavior is already implemented on current
Closing as implemented on main. |
What does this PR do?
Discord-cached image attachments routinely come back
success: falsefrom the firstvision_analyzecall inside_enrich_message_with_vision, even though calling the tool again against the exact same local path succeeds. The reporter observed the failure on every Discord session with an image in their logs.The agent then sees the kawaii fallback string
"couldn't quite see it this time (>_<)", recognises it, and reissuesvision_analyzemanually — costing ~30 s of reasoning latency and one wasted tool call per affected image, in every session.Root cause analysis. Tracing the code path:
gateway/platforms/discord.py::_cache_discord_imagewrites the attachment viacache_image_from_bytes, which uses synchronousfilepath.write_bytes(data)— the file is fully on disk before the path is returned._handle_messagepropagates the path throughevent.media_urls, and_prepare_event_textcalls_enrich_message_with_vision(text, image_paths).vision_analyze_toolonly returnssuccess: falsewhen an exception is caught internally (timeout, empty content, transient 5xx, rate limit). The "permanent" failures (image too large, insufficient credits, model doesn't support vision) all also surface this way.So the reporter's "timing race in Discord adapter" hypothesis isn't quite right — the file IS on disk. The actual failure mode is transient API errors that resolve on a second attempt ~30 s later when the agent reissues the call manually.
Fix. Add a bounded inline retry inside
_enrich_message_with_vision(the reporter's preferred Option 1):HERMES_VISION_AUTO_RETRIES; set to0to opt out and restore the legacy single-shot behaviour)._vision_failure_is_retryable) short-circuits the retry budget so we don't waste API calls onimage too large/insufficient credits/does not supportvision / SSRF block / interrupt. Both theerrorandanalysisJSON fields participate in the match."something went wrong"branch in_enrich_message_with_visioncontinues to fire for non-transient failures like missing API keys.Cost on the happy path: zero extra API calls. Cost on a transient failure: 1 extra call instead of the current 1 (manual by the agent) + ~30 s reasoning. Cost on a permanent failure: 1 call, same as today.
Related Issue
Fixes #28972
Type of Change
Changes Made
gateway/run.py— Introduce_vision_auto_retry_count,_vision_failure_is_retryable,_vision_analyze_with_auto_retryhelpers plus_VISION_AUTO_RETRY_COUNT_DEFAULT,_VISION_AUTO_RETRY_INITIAL_BACKOFF_S,_VISION_AUTO_RETRY_MAX_BACKOFF_S,_VISION_NONRETRYABLE_HINTSclass constants._enrich_message_with_visiondelegates the tool call to the retry helper. Sub-200-line change in a single file.tests/gateway/test_vision_auto_retry.py— 35 new tests in five classes covering: env var resolution (unset/zero/explicit/negative/garbage/whitespace), permanent-vs-transient classification (parametrised), the retry loop (happy path, transient-then-success, permanent short-circuit, all-fail, env opt-out, exception propagation), the public entry point (the [Bug]: [Discord] Auto-vision returns success=false on attachments, forcing duplicate vision_analyze call per image #28972 repro, kawaii-fallback preserved, no-retry on happy path, multi-image budget isolation), and structural invariants (default ≥ 1, lowercase hints, hint table covers known permanent errors).tests/gateway/test_vision_memory_leak.py— Extend the existing_Stubfixture to bind the new helpers so the sanitize-context regression coverage continues to exercise the real code path.website/docs/reference/environment-variables.md— DocumentHERMES_VISION_AUTO_RETRIESnext toHERMES_VISION_DOWNLOAD_TIMEOUT.How to Test
Reproduce the bug on
main:On
mainyou'll see the kawaii fallback embedded in the model's first user message, followed shortly by a duplicatevision_analyzetool call.After this PR:
For operators on metered providers who prefer the legacy behaviour:
Automated coverage:
Checklist
Code
fix(gateway):,test(gateway):,docs(gateway):)Documentation & Housekeeping
website/docs/reference/environment-variables.mdwith the new env varScreenshots / Logs
Before — every Discord session with an image (per reporter)
The model's first user message ends up containing:
Followed by:
That's one wasted tool call + ~30 s of agent reasoning, per image, per session.
After
The retry layer absorbs the transient failure invisibly. The model's first user message contains the happy-path descriptor:
No follow-up
vision_analyzetool call. The retry shows up only in the gateway log: