From 2d70740e769b74de94bf94c8b7fc10977c194577 Mon Sep 17 00:00:00 2001 From: Simbiot Harness Date: Wed, 30 Sep 2026 04:24:58 +0000 Subject: [PATCH] =?UTF-8?q?fix(worker):=20=D1=83=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20limits.turns=20=E2=80=94=20=D1=81=D1=82=D0=BE=D0=BF=20?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=B0,=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=B9=D0=B4=D0=B6=D0=B5=D1=81=D1=82=D1=8B=20=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D1=82=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Репродукция: мин-тик с реальным контекстом → модель (auto/low effort) каждый ход делала tool-вызов, никогда не выдавая чистый текст; limits={'turns': 15} обрывал цикл на границе хода, result.message оставался toolResult-сообщением → str(result)='' → DigestWritten len 0. limit_turns — нативный стоп SDK, но он не кладёт в историю итоговый ассистентский текст. Наш end_turn (AfterToolsEvent) кладёт: дайджест не пустеет. Защита от таймаутов остаётся двойной: cancel_tool на вызовы сверх лимита + end_turn после партии. E2E-подтверждение: тот же промпт, stop_reason=end_turn, текст 465 символов ('Тик чистый. Проверил: ...'). --- README.md | 8 +++++--- harness/_llm_worker.py | 16 ++++++++-------- tests/test_tool_budget.py | 4 ---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ea10491..16df94f 100644 --- a/README.md +++ b/README.md @@ -123,9 +123,11 @@ sqlite, rag.db ~12.5k чанков по контексту/навыкам, herme Защита в `_llm_worker`: `BeforeToolCallEvent.cancel_tool` отменяет вызова сверх лимита, `AfterToolsEvent.end_turn` останавливает цикл агента ПОСЛЕ партии инструментов (без него модель шлёт шаги снова — -e2e: 20 отмен подряд), `limits={"turns": N}` — нативный кап ходов SDK. -Лимиты: `SIMBIOT_MAX_TOOL_CALLS` (по умолчанию 30, счёт по реальным -исполнениям), `SIMBIOT_MAX_TURNS` (по умолчанию 15). +e2e: 20 отмен подряд), кладя в историю ассистентский текст-итог. +Нативный `limits={"turns": N}` НЕ используется: стоп на границе хода +оставляет `result.message` = toolResult → пустой дайджест (04:18+). +Лимит: `SIMBIOT_MAX_TOOL_CALLS` (по умолчанию 30, счёт по реальным +исполнениям). ## Дашборд задач diff --git a/harness/_llm_worker.py b/harness/_llm_worker.py index d4eea92..73d0b58 100644 --- a/harness/_llm_worker.py +++ b/harness/_llm_worker.py @@ -238,16 +238,16 @@ def main(): ) agent = _wire_trace_hooks(agent, es, trace_id) agent = _wire_budget_hooks(agent, level, trace_id) - # хард-кап шагов (e-260930031800): двойная защита. - # 1) limits={"turns": N} — нативный механизм SDK: цикл агента - # останавливается на границе хода (stop_reason="limit_turns"), - # модель не может уйти в бесконечную цепочку tool-вызовов. - # 2) хук cancel_tool — страховка на случай параллельного блока - # (20 вызовов одним блоком: turns их не режет, хук отменяет). - max_turns = int(os.environ.get("SIMBIOT_MAX_TURNS") or 15) + # хард-кап шагов (e-260930031800): cancel_tool + end_turn. + # ВАЖНО: НЕ используем limits={"turns": N} — нативный стоп SDK + # обрывает цикл на границе хода, и result.message остаётся + # toolResult-сообщением → str(result)='' → пустой дайджест + # (репродукция: stop_reason=limit_turns, text='', тики 04:18+). + # Наш end_turn (AfterToolsEvent) кладёт в историю ассистентский + # текст с итогом — дайджест не пустеет. agent = _wire_tool_budget( agent, int(os.environ.get("SIMBIOT_MAX_TOOL_CALLS") or 30)) - result = agent(payload.get("prompt", ""), limits={"turns": max_turns}) + result = agent(payload.get("prompt", "")) usage = _extract_usage(agent) _emit(payload, {"ok": True, "text": str(result), "usage": usage, "trace_id": trace_id}) except Exception as e: diff --git a/tests/test_tool_budget.py b/tests/test_tool_budget.py index 28f4aa3..8133f8f 100644 --- a/tests/test_tool_budget.py +++ b/tests/test_tool_budget.py @@ -104,10 +104,6 @@ os.environ["SIMBIOT_MAX_TOOL_CALLS"] = "7" check("budget: env SIMBIOT_MAX_TOOL_CALLS читается", int(os.environ.get("SIMBIOT_MAX_TOOL_CALLS") or 30) == 7) del os.environ["SIMBIOT_MAX_TOOL_CALLS"] -os.environ["SIMBIOT_MAX_TURNS"] = "15" -check("budget: env SIMBIOT_MAX_TURNS читается", - int(os.environ.get("SIMBIOT_MAX_TURNS") or 15) == 15) -del os.environ["SIMBIOT_MAX_TURNS"] # ── 5. реальные strands-хуки: create_harness + add_hook не падают ── try: