Диагностика проблем¶
Это не список возможностей трейсинга/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вPlanExecute. - Снова
reactifact trace: повторные записи в один и тот же id артефакта на диаграмме через несколько поколений — прямой симптом.
"Run остановился раньше, чем я ожидал"¶
- Проверьте
runtime.outcome(RunOutcome, §58) послеarun()/astream()(также доступно наruntime.last_stats.outcome):budget_runs_exceeded/budget_time_exceeded/budget_tokens_exceeded/budget_cost_exceeded/iterations_exhaustedозначают, что сработал лимитBudget, а не баг;completedозначает, что run действительно устаканился (очередное поколение не произвело ничего, на что можно было бы отреагировать).budget_tokens_exceeded/budget_cost_exceededостанавливают следующее поколение, так что последний вызов LLM мог чуть перебрать лимит;RunStats.total_tokens/.costпоказывают, сколько реально потрачено (costостаётся 0 без инжектированногоpricer). - Если
completedнаступил раньше, чем ожидалось — обычно это реактивная модель работает так, как задумано: больше ничего не стало допустимым. Вернитесь к разделу "Мой агент вообще не запустился" именно для этого агента.
"Сессия/контекст всё растёт"¶
Долгоживущая сессия хранит каждый коммит и версию артефакта — так задумано, именно это делает её аудируемой и воспроизводимой. Ограничьте её между ходами:
context.compact(keep_commits=50, keep_versions=2)сворачивает старую историю коммитов в baseline-снапшот и обрезает историю версий артефактов (Устойчивость и resume);version/head_id/context_hashсохраняются, ноcheckout/diffниже baseline после этого отказывают.- Старые артефакты (например, сообщения за окном) — отдельная забота: рецепты
памяти (
WindowPruner,RollingDigestSummarizer, Recipes).
"Шедулер выбрал/отбросил не того агента" (если используете 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(Type).linked(...)/.relations), тулы, путь,
ошибки. lab.fail(tool_name, error)/lab.stub_tool(...) и
lab.fail_resource(...)/lab.stub_resource(...) инжектируют конкретный сбой
или заданный ответ, который вы ловите; result.explain() выводит состояние
прогона; записанный через ReplayLLM ответ модели (или golden-снапшот) делает
вывод нестабильного провайдера детерминированным между перезапусками.
См. Testing и разобранные примеры в
examples/repair/scenarios/ и examples/knowledge/scenarios/.