mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
Keep reserved workers able to persist during drain, join actual in-process writers before releasing a profile, and reap children created after cancelled process acquisition. Reopening a worker outbox reconciles its pending receipt; managed results retain per-turn usage and bounded live tool details.
This commit contains the physical-worker foundation only. Mutation-task and Bot mailbox lifetime tracking are separate follow-ups. The existing zero-second restart drain default is unchanged.
Validation: scripts/run_tests.sh -j 4 across 10 worker/runtime files: 26 passed, 1 POSIX-only skip on native Windows. Includes a real managed child completing its durable receipt during a configured restart drain. The unchanged upstream one-second retirement test missed its deadline once under parallel load and passed the runner retry; an isolated rerun passed both cases without retries. Logs: split-W-tests.log, split-W-retirement-rerun.log.
Review-source: helix4u, https://github.com/NousResearch/hermes-agent/pull/106742#issuecomment-6024687448
Review-source: greptile-apps, https://github.com/NousResearch/hermes-agent/pull/106742#discussion_r4211047305
Review-source: greptile-apps, https://github.com/NousResearch/hermes-agent/pull/106742#discussion_r4211047387
Review-source: greptile-apps, https://github.com/NousResearch/hermes-agent/pull/106742#discussion_r4213427126
Review-source: dokterdok, https://github.com/NousResearch/hermes-agent/pull/106742#discussion_r4211144039
Source-base: NousResearch/hermes-agent#106742 at 0b815190b2
(cherry picked from commit 6156b21c5e3fa5e9295a13d5e02caa9b959ada82)
37 lines
1.2 KiB
Python
37 lines
1.2 KiB
Python
"""Cancellation-safe handoff of a managed subprocess acquired off the owner loop."""
|
|
import asyncio
|
|
import threading
|
|
|
|
|
|
async def acquire_process(spawn, *args, **kwargs):
|
|
from gateway.session_managed_worker import ManagedWorker
|
|
lock = threading.Lock()
|
|
state = {'cancelled': False, 'process': None}
|
|
|
|
def create():
|
|
process = spawn(*args, **kwargs)
|
|
with lock:
|
|
cancelled = state['cancelled']
|
|
if not cancelled:
|
|
state['process'] = process
|
|
if cancelled:
|
|
ManagedWorker(process).close()
|
|
return process
|
|
|
|
pending = asyncio.create_task(asyncio.to_thread(create))
|
|
try:
|
|
process = await asyncio.shield(pending)
|
|
with lock:
|
|
state['process'] = None
|
|
return process
|
|
except asyncio.CancelledError:
|
|
with lock:
|
|
state['cancelled'] = True
|
|
process = state['process']
|
|
state['process'] = None
|
|
if process is not None:
|
|
await asyncio.to_thread(ManagedWorker(process).close)
|
|
# If creation still runs, its thread owns cleanup; consume an eventual spawn exception.
|
|
pending.add_done_callback(lambda task: task.exception() if not task.cancelled() else None)
|
|
raise
|