Skip to content

Диагностика проблем

Это не список возможностей трейсинга/observability-инструментов — это observability.md. Это другое направление: у вас конкретный вопрос "почему это произошло/не произошло" и нужен самый быстрый путь к ответу, по симптомам.

Общая нить: в reactifact допустимость — решение по состоянию (§69) — никто не вызывает агента напрямую, поэтому нет стектрейса, указывающего на "строку, которая должна была его вызвать". Все инструменты ниже отвечают на один и тот же вопрос по-разному: что реально содержало состояние в момент, когда runtime решал, что запускать?

"Мой агент вообще не запустился"

Допустимость решают Consume/Trigger, сопоставленные с событиями, а не порядок кода — поэтому "не запустился" почти всегда означает либо что событие, которое должно было его триггернуть, не произошло, либо что условие Consume.by_field/by_status оказалось ложным для существующего артефакта.

  • reactifact graph <module> (например, reactifact graph examples.knowledge.agents) печатает статический blueprint — заявленные consumes/produces каждого агента. Проверьте, что агент реально подписан на нужный тип артефакта; опечатка или устаревший Consume(WrongType) — самая частая причина.
  • Частый "тихий no-op", который выглядит как пропущенный триггер, но им не является: effects.create_once(..., id=...)/upsert(...) намеренно ничего не делает, если такой id уже существует (§42, идемпотентность) — проверьте, не существовал ли артефакт, который вы ожидали увидеть свежесозданным, уже с прошлого поколения.
  • agent.matches(event, context) — тот самый булев вызов, который делает runtime; можно вызвать напрямую в REPL с реальным Event, если граф выглядит верно, а уверенности всё ещё нет.

"Мой агент запустился, но ничего не произвёл"

Ранний возврат из produce (return None) — нормальная, честная форма "пока не готов" или "нечего делать" (§59); сам по себе он не ошибка, поэтому нигде не отобразится как ошибка.

  • reactifact trace <path/to/traces.db> [run_id] рисует run как диаграмму Mermaid (trace_to_mermaid, по умолчанию последний run) — у каждого span'а агента видны reads/writes, а любое выброшенное исключение вписано прямо в узел (⚠ <сообщение>), так что это самый быстрый способ увидеть, какой агент реально упал, а какой законно отказался действовать.
  • Если ничего не упало, а span показывает отсутствие записей — значит гард внутри вашего produce() вернул None. Это случай для breakpoint/print в конкретных местах return None, а не для трейсера (трейсер фиксирует, что произошло, а не почему гард решил не действовать).
  • Runtime(isolate_errors=True, on_agent_error=...) — опциональная настройка; по умолчанию reactifact падает вслух, и исключение всплывает из arun()/astream() (§69). Если вы сознательно включили изоляцию, смотрите runtime.last_stats.errors (RunStats.errors, §58) для количества и -метки в трейсе — для того, какой именно агент.

"Мой агент запустился больше раз, чем ожидалось"

  • Отсутствие идемпотентности: effects.create(...) без стабильного id= (или с id, выведенным из счётчика) создаёт новый артефакт на каждое подходящее событие; вместо этого обычно нужен effects.create_once(..., id=...) (§42).
  • Самозапуск: агент, который одновременно consumes и produces один и тот же тип артефакта, перезапускает себя на собственном выводе, если гард (if <условие-уже-сделано>: return None) его не останавливает — см. гарды already planned (§42)/already executed в examples/plan_execute.
  • Снова reactifact trace: повторные записи в один и тот же id артефакта на диаграмме через несколько поколений — прямой симптом.

"Run остановился раньше, чем я ожидал"

  • Проверьте runtime.outcome (RunOutcome, §58) после arun()/astream() (также доступно на runtime.last_stats.outcome): budget_runs_exceeded/budget_time_exceeded/iterations_exhausted означают, что сработал лимит Budget, а не баг; completed означает, что run действительно устаканился (очередное поколение не произвело ничего, на что можно было бы отреагировать).
  • Если completed наступил раньше, чем ожидалось — обычно это реактивная модель работает так, как задумано: больше ничего не стало допустимым. Вернитесь к разделу "Мой агент вообще не запустился" именно для этого агента.

"Шедулер выбрал/отбросил не того агента" (если используете scheduler=)

Актуально только если вы передали Runtime(scheduler=...) — без него выполняются все допустимые кандидаты, в порядке заявленного priority.

  • Правила фильтра могут полностью отбросить кандидата ещё до ранжирования; ранжирование только переупорядочивает, никогда не отбрасывает; rank_limit обрезает уже ранжированный список после ранжирования. No-starvation fallback означает, что кандидат действительно исчезает только через фильтр rules или rank_limit — никогда как сюрприз "метрика оценила слишком низко, чтобы вообще выполниться", потому что само ранжирование никогда не отбрасывает.
  • См. design-notes/adaptive.md — полный контракт filter → rank → LLM-tie-break, и reactifact.scheduler.relation_balance_metric, если используете встроенную uncertainty-driven метрику (§26) — она читает связи supports/contradicts, так что неожиданный порядок часто означает, что эти связи установлены не так, как вы думаете (context.incoming(artifact_id, relation=...), чтобы проверить напрямую).

"Не понимаю, почему ответ именно такой"

Это не сбой — это ровно то, для чего существует provenance (§15, §34):

  • context.related(answer.id, "supported_by") проходит один шаг исходящих связей от артефакта (пропуская "висящие"); context.incoming(id, relation=...) проходит в обратную сторону.
  • context_to_mermaid() / trace_provenance_to_mermaid() (reactifact.viz) рисуют весь граф связей или цепочку provenance трейса как диаграмму — см. comparison.md §3 за разобранным примером.

Детерминированное воспроизведение проблемы

Когда понятно, что произошло, reactifact.testing.ScenarioLab позволяет закрепить это без зависимости от живого LLM или живого run'а: засеять точные артефакты, прогнать до конца и проверить результат (.artifacts(...)/.tools/.path/.errors); lab.fail(tool_name, error) и lab.fail_resource(...) инжектируют конкретный сбой, который вы ловите; записанный через ReplayLLM ответ модели делает вывод нестабильного провайдера детерминированным между перезапусками. Отдельного гайда пока нет — начните с докстринга модуля reactifact.testing и разобранных примеров в examples/repair/scenarios/ и examples/knowledge/scenarios/.