fix(worker): null_callback_handler — BrokenPipe убивал тики с 04:11

Симптом: DigestWritten min с пустым text при живом агенте (15 tool-вызовов,
BudgetSpent писались). Корень: PrintingCallbackHandler стримит в stdout
воркера; scheduler читает capture_output=True; длинный вывод (reasoning +
Tool #N на каждый вызов) переполнял pipe → BrokenPipeError внутри
stream_async → ok=False, текст тика потерян. Воркер перезапускался каждый
тик — падения шли серией, мин-дайджесты пустые.

Результат тика живёт в JSON-файле (result_path) — печать не нужна вовсе:
create_harness(callback_handler=null_callback_handler).

E2E: воркер с 2 shell-вызовами, stdout 0 байт, ok=True, текст на месте.
This commit is contained in:
Simbiot Harness 2026-09-30 04:19:04 +00:00
parent 205d558ffa
commit 1b8c27b292

View file

@ -220,12 +220,18 @@ def main():
tools.append(_rag_tool_for(level, trace_id)) tools.append(_rag_tool_for(level, trace_id))
except Exception: except Exception:
pass # RAG — best effort: вендор не должен валить тик pass # RAG — best effort: вендор не должен валить тик
# stdout воркера не читается (subprocess.run с capture_output и
# длинным выводом переполняет pipe → BrokenPipeError в печати,
# результат тика терялся, дайджесты 04:11-04:17 ушли пустыми).
# Результат живёт в JSON-файле (result_path), печать не нужна.
from strands.handlers.callback_handler import null_callback_handler
agent = create_harness( agent = create_harness(
model=build_level_model_from_payload(payload), model=build_level_model_from_payload(payload),
effort="auto", # effort уже в параметрах модели effort="auto", # effort уже в параметрах модели
instructions=payload.get("instructions", ""), instructions=payload.get("instructions", ""),
session=None, session=None,
tools=tools, tools=tools,
callback_handler=null_callback_handler,
builtin_tools=("read", "write", "edit", "shell") if payload.get("can_shell") else [], builtin_tools=("read", "write", "edit", "shell") if payload.get("can_shell") else [],
memory=False, memory=False,
skills=False, skills=False,