Skip to content

feat(desktop): let the agent reap background processes + neutral signal-kill rendering - #49809

Open
professorpalmer wants to merge 1 commit into
NousResearch:mainfrom
professorpalmer:feat/desktop-bg-process-reap
Open

feat(desktop): let the agent reap background processes + neutral signal-kill rendering#49809
professorpalmer wants to merge 1 commit into
NousResearch:mainfrom
professorpalmer:feat/desktop-bg-process-reap

Conversation

@professorpalmer

Copy link
Copy Markdown
Contributor

feat(desktop): let the agent reap background processes + stop mislabeling signal-kills as failures

Two gaps in the background-process status stack:

  1. REAP CAPABILITY — finished/killed background entries piled up with no way
    for the agent (or a re-poll-safe UI action) to evict them. The agent could
    kill a LIVE process but had no primitive to drop a FINISHED one from the
    registry, so the desktop "Background" group accumulated dead rows that only
    cleared on a 30-min TTL or app restart.

    • ProcessRegistry.remove(session_id): evict one finished entry; refuses a
      running process (returns status 'running' — kill it first); idempotent.
    • ProcessRegistry.clear_finished(task_id?, session_key?): evict all finished
      entries, optionally scoped.
    • Gateway: process.remove (session-scoped, 4044 if not owned, 4090 if still
      running) and process.clear_finished (caller-session-scoped), mirroring the
      existing process.kill scoping.
    • Agent process tool: new remove and clear actions so the agent can
      up/down background sessions during its own iteration.
    • Desktop: dismissBackgroundProcess now fires process.remove so the eviction
      is registry-authoritative (other windows + re-polls won't resurrect it),
      plus a "Clear finished" accessory on the Background group header that
      bulk-reaps via process.clear_finished.
  2. SIGNAL-EXIT RENDERING — a negative exit code is a signal termination
    (kill_process sets -15 = SIGTERM), i.e. a DELIBERATE teardown, not a crash.
    It was painted the same scary red "failed" with a raw "exit -15" badge as a
    genuine exit-1 failure. Now exitCode < 0 maps to a new neutral 'stopped'
    state (muted dot + muted badge) and the badge renders the signal NAME
    (SIGTERM/SIGINT/SIGKILL) instead of a raw negative number. A real non-zero
    exit still renders red "failed" with "exit N".

i18n: statusStack gains stopped and clearFinished (en/ja/zh/zh-hant +
types.ts).

Tests: process_registry remove/clear_finished (evict, refuse-running,
idempotent, session-key scoping — 80 pass); composer-status (negative exit ->
'stopped', exit 1 -> 'failed', clearFinished keeps running rows). tsc clean.

…ling signal-kills as failures

Two gaps in the background-process status stack:

1. REAP CAPABILITY — finished/killed background entries piled up with no way
   for the agent (or a re-poll-safe UI action) to evict them. The agent could
   `kill` a LIVE process but had no primitive to drop a FINISHED one from the
   registry, so the desktop "Background" group accumulated dead rows that only
   cleared on a 30-min TTL or app restart.
   - ProcessRegistry.remove(session_id): evict one finished entry; refuses a
     running process (returns status 'running' — kill it first); idempotent.
   - ProcessRegistry.clear_finished(task_id?, session_key?): evict all finished
     entries, optionally scoped.
   - Gateway: process.remove (session-scoped, 4044 if not owned, 4090 if still
     running) and process.clear_finished (caller-session-scoped), mirroring the
     existing process.kill scoping.
   - Agent `process` tool: new `remove` and `clear` actions so the agent can
     up/down background sessions during its own iteration.
   - Desktop: dismissBackgroundProcess now fires process.remove so the eviction
     is registry-authoritative (other windows + re-polls won't resurrect it),
     plus a "Clear finished" accessory on the Background group header that
     bulk-reaps via process.clear_finished.

2. SIGNAL-EXIT RENDERING — a negative exit code is a signal termination
   (kill_process sets -15 = SIGTERM), i.e. a DELIBERATE teardown, not a crash.
   It was painted the same scary red "failed" with a raw "exit -15" badge as a
   genuine exit-1 failure. Now exitCode < 0 maps to a new neutral 'stopped'
   state (muted dot + muted badge) and the badge renders the signal NAME
   (SIGTERM/SIGINT/SIGKILL) instead of a raw negative number. A real non-zero
   exit still renders red "failed" with "exit N".

i18n: statusStack gains `stopped` and `clearFinished` (en/ja/zh/zh-hant +
types.ts).

Tests: process_registry remove/clear_finished (evict, refuse-running,
idempotent, session-key scoping — 80 pass); composer-status (negative exit ->
'stopped', exit 1 -> 'failed', clearFinished keeps running rows). tsc clean.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/gateway Gateway runner, session dispatch, delivery comp/desktop Electron desktop app (apps/desktop/*) and removed comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 20, 2026

@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 addressing two real current gaps: main still has no finished-process reap operation, and tools/process_registry.py:1503-1505 deliberately assigns killed processes exit code -15 while the desktop currently renders it failed.

Problems

  • apps/desktop/src/store/composer-status.ts:138 treats every negative exit as a deliberate stop. That also catches the real backend-loss path, which sets exit_code = -1, completion_reason = "lost", and termination_source = "backend_lost" in tools/process_registry.py:1025-1029.
  • The new desktop RPC writes discard all failures at composer-status.ts:240 and :251; the local dismissal can therefore hide a row even though the registry did not reap it.

Suggested changes

  • Carry an explicit termination reason to the desktop and classify only confirmed kills as stopped; test backend loss separately.
  • Await reap RPCs and reconcile/report an error on failure. Add gateway tests for owner scoping and the running-process refusal.

Salvage will require conflict-aware reapplication because the current process RPCs and tool session scoping now live at tui_gateway/server.py:11481-11513 and tools/process_registry.py:2217-2248.

Automated hermes-sweeper review.


const state: StatusItemState = !exited ? 'running'
: exitCode === undefined || exitCode === 0 ? 'done'
: exitCode < 0 ? 'stopped'

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.

exitCode < 0 is not synonymous with a deliberate signal kill: current ProcessRegistry uses exit_code = -1, completion_reason = "lost", and termination_source = "backend_lost" when the environment disappears (tools/process_registry.py:1025-1029). Please classify from an explicit termination reason, or at minimum keep that loss path failed.

@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:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 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/desktop Electron desktop app (apps/desktop/*) comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have 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-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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants