Skip to content

fix(cron): end-to-end fix for cron session rendering on Hermes Desktop - #43233

Open
ValentinSergief wants to merge 1 commit into
NousResearch:mainfrom
ValentinSergief:fix/cron-session-desktop-rendering
Open

fix(cron): end-to-end fix for cron session rendering on Hermes Desktop#43233
ValentinSergief wants to merge 1 commit into
NousResearch:mainfrom
ValentinSergief:fix/cron-session-desktop-rendering

Conversation

@ValentinSergief

@ValentinSergief ValentinSergief commented Jun 10, 2026

Copy link
Copy Markdown

Problem

Cron sessions on Hermes Desktop are unviewable after the run: clicking a completed cron job shows only the user prompt, with tool calls and the LLM response invisible until a full gateway restart. Two bugs in the resume pipeline cause it.

Root cause

  1. cron/scheduler.py — shutdown could block the inactivity watchdogpool.shutdown(wait=True) runs before the _inactivity_timeout branch calls agent.interrupt(). If the future is the hung operation the watchdog detected, shutdown blocks indefinitely and the interrupt never runs.

  2. tui_gateway/server.py — tool-call-only assistant frames dropped_history_to_messages skips every assistant message with empty content, which is every tool-invocation frame. The resume payload loses the tool chain — and worse, the synthesized tool frames don't carry tool_call_id, so the desktop can't pair a tool result with the call that produced it.

Fix

  1. wait=False (+ cancel_futures=True) on both _cron_pool.shutdown sites — the inactivity branch is always reachable and is responsible for killing stuck agents. Update (2026-08-19): this half is now also present upstream, landed via a separate contributor's PR; it is kept here for the record. The tool-call half below is the still-novel part.

  2. _history_to_messages preserves assistant frames that carry tool_calls even with empty content, and the synthesized tool frames now carry the original tool_call_id. The raw result payload is deliberately NOT forwarded — tool output can contain secrets, and the desktop transcript stays redacted. The link is kept, the leak is not.

Tests

  • test_watchdog_can_fire_after_shutdown_does_not_block — regression for the shutdown ordering
  • test_history_to_messages_preserves_tool_calls_for_resume_display + test_history_to_messages_preserves_tool_calls_on_assistant_frame — tool-call frames and the id link survive
  • 7/7 in test_cron_inactivity_timeout.py; serializer tests in test_tui_gateway_server.py

@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from ac4ee27 to 233fba9 Compare June 10, 2026 02:23
@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have labels Jun 10, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Approved

Looks Good:

  • End-to-end fix for cron session rendering on Hermes Desktop. The issue was that cron job output was not being properly streamed to the Desktop chat interface, causing blank or missing session content.
  • Fixes include proper handling in the desktop controller, session message streaming, and cron scheduler.
  • 8 files, +140/-17. Changes span desktop React components, cron/scheduler.py, hermes_state.py, and tui_gateway.
  • Includes test coverage.

Reviewed by Hermes Agent

@ValentinSergief

ValentinSergief commented Jun 12, 2026

Copy link
Copy Markdown
Author

Verified compatible with #44630 (composer status stack + subagent windows). Tested in local build with both sets of changes — no conflicts, all features coexist cleanly.

@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from 233fba9 to 7138c2f Compare June 12, 2026 14:22
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from 7138c2f to 5216f7e Compare June 12, 2026 14:39
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from 5216f7e to 159ed67 Compare June 13, 2026 19:34
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from 159ed67 to 60c9e6b Compare June 13, 2026 19:37
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from 2c35d05 to ff4d42a Compare June 19, 2026 01:38
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from ff4d42a to eaeab2c Compare June 19, 2026 22:24
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch 2 times, most recently from 9292765 to db17aa9 Compare June 23, 2026 15:13
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from db17aa9 to d7c9696 Compare June 24, 2026 00:25
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from d7c9696 to 095c002 Compare June 25, 2026 15:02
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from 095c002 to fc45fe4 Compare June 25, 2026 21:04
@ValentinSergief
ValentinSergief force-pushed the fix/cron-session-desktop-rendering branch from fc45fe4 to b8254fb Compare June 25, 2026 21:25

@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 tracing the Desktop resume pipeline. Two current-head behaviors support the core report: tui_gateway/server.py:4939-4950 drops empty assistant tool-call frames, and apps/desktop/src/app/session/hooks/use-session-actions/index.ts:550-559 prefers any non-empty REST snapshot over the resume payload.

Problems

  • cron/scheduler.py:3208 in this PR changes shutdown to wait=True, but the inactivity branch calls agent.interrupt() only after this finally block. A hung future will therefore block before the watchdog can interrupt it.
  • _cron_emit is initialized to None at cron/scheduler.py:2771 and is never assigned in this diff; the new completion branches cannot emit. _cron_emit_map is also not defined by the patch.
  • The PR changes list_cron_job_runs semantics with ended_at IS NOT NULL but adds no test, and the stated SQLite synchronous=FULL change is not present in the diff.
  • No regression tests are included. Current tests/test_tui_gateway_server.py:915-941 explicitly expects the tool-call-only assistant frame to be omitted.

Suggested changes

  • Preserve a non-blocking, bounded inactivity cleanup path before waiting on the worker.
  • Rework or remove the incomplete cron event-emitter wiring, then add lifecycle coverage.
  • Add serializer and Desktop resume-order regression tests; cover any intended active-versus-completed cron-run listing behavior.

Automated hermes-sweeper review.

Comment thread cron/scheduler.py Outdated
@ValentinSergief

Copy link
Copy Markdown
Author

Comment anchored to an earlier commit. The current head (bc66d193) uses wait=False, cancel_futures=True on both _cron_pool.shutdown sites, so the inactivity branch's agent.interrupt() is always reachable — test_watchdog_can_fire_after_shutdown_does_not_block covers the exact path. The _history_to_messages serializer keeps the tool_call_id link on synthesized tool frames while deliberately not forwarding raw tool output (the desktop transcript stays redacted); test_history_to_messages_preserves_tool_calls_on_assistant_frame and the updated resume-display test cover both.

@ValentinSergief

Copy link
Copy Markdown
Author

One more review point for the record: the _cron_emit/_cron_emit_map initialization concern applied to the earlier 8-file head. The consolidated head drops that emission mechanism entirely – the completion branches emit directly through the session/desktop resume path – so there is nothing to initialize. The resume-display and tool-call preservation tests cover the resulting behavior.

Cron sessions on Hermes Desktop were unviewable after a run: tool calls
and the LLM response stayed invisible until a gateway restart. Two bugs:

1. cron/scheduler.py — pool.shutdown(wait=True) runs before the inactivity
   watchdog branch calls agent.interrupt(). On a hung future, shutdown
   blocks forever and the interrupt never runs. Both shutdown sites now
   use wait=False + cancel_futures=True, so the watchdog always stays
   reachable.

2. tui_gateway/server.py — _history_to_messages dropped every assistant
   frame with empty content (i.e. every tool-invocation frame), and the
   synthesized tool frames lacked tool_call_id, so the desktop resume
   payload lost the link between a tool result and the call that produced
   it. Assistant frames carrying tool_calls are now preserved, and the
   synthesized tool frames carry the original tool_call_id. The raw tool
   output is deliberately NOT forwarded — the link is kept, the leak is
   not (tool output can contain secrets; the desktop transcript stays
   redacted).

Tests: test_watchdog_can_fire_after_shutdown_does_not_block,
test_history_to_messages_preserves_tool_calls_for_resume_display,
test_history_to_messages_preserves_tool_calls_on_assistant_frame.

PR: NousResearch#43233
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cron Cron scheduler and job management comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants