Files
hermes-agent/gateway/session_tool_events.py
David Dudok de Wit e32a886e1d fix(worker): retain persistence and ownership through physical shutdown
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)
2026-10-08 09:10:26 -07:00

31 lines
1.2 KiB
Python

"""Viewer payload limits; the full tool input/output stays in canonical history."""
import json
MAX_TOOL_EVENT_CHARS = 64 * 1024
def bounded_args(args):
encoded = json.dumps(args, ensure_ascii=True, default=str)
if len(encoded) <= MAX_TOOL_EVENT_CHARS:
return args
# Retain ordinary scalar paths/commands for editor locations even when a content field is large.
result = {}
remaining = MAX_TOOL_EVENT_CHARS // 2
for key, value in args.items():
if remaining <= 0 or len(result) >= 64:
break
name = str(key)[:256]
text = value if isinstance(value, str) else json.dumps(value, ensure_ascii=True, default=str)
text = text[:min(4096, remaining)]
result[name] = text
remaining -= len(json.dumps([name, text], ensure_ascii=True))
return result
def bounded_result(result, is_error):
text = result if isinstance(result, str) else json.dumps(result, ensure_ascii=True, default=str)
if len(text) <= MAX_TOOL_EVENT_CHARS:
return text
# ACP reads errors from the result as well as the event flag; keep valid JSON after truncation.
return json.dumps({'error' if is_error else 'output': text[:MAX_TOOL_EVENT_CHARS // 2], 'truncated': True})