Частые проблемы и разбор ошибок¶
Когда что-то ломается, человек не знает, какой модуль сломался, — он знает только то, что видит. Поэтому эта страница сгруппирована по наблюдаемым симптомам, а не по подсистемам.
Структура каждого пункта одинакова: симптом (то, что вы реально видите) → причина → что делать.
Пять пунктов — это известные дефекты, а не задуманное поведение. В таких пунктах прямо сказано, что это баг, дана ссылка на issue и обходной путь — мы не выдаём их за осознанное решение.
Не устанавливается / не запускается¶
Полная процедура установки — в install.md. В этом разделе только случаи «установилось, но команда не запускается».
Версия Python ниже 3.10¶
Симптом: во время установки вылетает синтаксическая ошибка, либо pip прямо говорит, что не нашёл подходящей версии.
Причина: flower требует Python ≥ 3.10. Единственная зависимость времени выполнения — claude-agent-sdk, нативный бинарник лежит внутри его wheel, — так что если не ставится, дело почти всегда в версии интерпретатора, а не в сети.
Что делать: сначала выясните, в какой интерпретатор вы ставите.
Ниже 3.10 — берите другой и ставьте заново. Системный python3 часто оказывается не тем, на который указывает python в вашем терминале; сверить версию до установки дешевле, чем разбираться после (см. install.md).
Установилось, но flower: command not found¶
Симптом:
Причина: пакет установился, но каталог со сгенерированным исполняемым скриптом не попал в PATH. Это не то же самое, что «не установилось»: если python3 -c "import flower" не падает, значит пакет в порядке.
Что делать: в shebang скрипта flower прописан абсолютный путь, поэтому достаточно сделать симлинк в каталог, который уже есть в PATH, — ничего сорсить не нужно.
macOS: добавил PATH по подсказке install.sh, всё равно command not found¶
Известная проблема (issue #16)
Этот совет перестаёт работать ровно на той машине, где он нужен.
Симптом: на macOS отработал install.sh, по его финальной подсказке добавил ~/.local/bin в PATH, перезапустил терминал — flower по-прежнему command not found.
Причина: когда дело доходит до запасного пути через pip, pip на macOS кладёт исполняемые скрипты в ~/Library/Python/3.X/bin, а install.sh предлагает добавить ~/.local/bin. Каталоги не совпадают, и выполнение подсказки ничего не даёт.
В каком порядке install.sh выбирает способ установки и что именно говорит подсказка
Приоритетов четыре, а не два (install.sh:35-56):
1. есть uv → uv tool install --force
2. иначе есть pipx → pipx install --force
3. иначе → curl astral.sh/uv/install.sh, поднять uv; если удалось — ставить через uv
4. и это не вышло → "$PY" -m pip install --user --upgrade ← ломается именно этот
Текст финальной подсказки про PATH (install.sh:62-68, печатается только если command -v flower ничего не нашёл):
BINDIR захардкожен как $HOME/.local/bin (install.sh:63). Для путей 1 и 3 это верно — uv ставит именно туда; не совпадает только запасной путь 4 через pip на macOS. Поэтому яма появляется лишь на машинах, где первые три варианта не сработали.
Что делать: не гадайте про каталог — спросите у интерпретатора.
Добавьте напечатанный каталог в PATH либо сделайте из него симлинк в ~/.local/bin:
ln -sf "$(python3 -c "import sysconfig; print(sysconfig.get_path('scripts', scheme='posix_user'))")/flower" ~/.local/bin/flower
uv / pipx / pip поставили разные flower¶
Симптом: flower запускается, но правки в исходниках не действуют; или после обновления остаётся старая версия; или два терминала на одной машине ведут себя по-разному.
Причина: три способа установки кладут пакет и исполняемый скрипт в разные места, а запускается тот, который первым нашёлся в PATH.
Куда попадает каждый способ установки
| Способ | Исполняемый скрипт | Когда использовать |
|---|---|---|
python3 -m venv .venv + pip install -e . | .venv/bin/flower | Нужно править исходники. Правки действуют сразу |
uv tool install / pipx install | ~/.local/bin/flower | Только использовать, не править, нужна изолированная среда |
pip install --user | Linux ~/.local/bin, macOS ~/Library/Python/3.X/bin | Запасной вариант. Про каталог см. предыдущий пункт |
Что делать: сначала выясните, что именно сейчас запускается, потом решайте, что править.
which -a flower # перечислить все одноимённые в PATH
head -1 "$(which flower)" # на какой интерпретатор указывает shebang — в той среде и лежит пакет
Если правите исходники — берите venv + -e . и не держите его рядом с установкой через uv / pipx: разбираться при сосуществовании дороже, чем один раз переустановить (см. install.md).
Учётные данные и шлюзы¶
缺少凭证:需要 ANTHROPIC_API_KEY 或 ANTHROPIC_AUTH_TOKEN¶
Симптом:
Причина: flower изолируется от конфигурации хоста через setting_sources=[], поэтому учётные данные нужно приносить с собой. Полный порядок поиска — в config.md.
Что делать: пропишите их в .env в корне репозитория либо в окружение процесса.
.env уже в gitignore. Как это делать в контейнере — см. deploy.md.
Оно пишет «flower не читает ~/.claude/settings.json» — это неправда¶
Симптом: когда учётные данные не настроены, env.py:192 печатает строку:
Причина: эта фраза не соответствует коду. env.py:56-75 действительно читает ~/.claude/settings.json, берёт оттуда только поля с учётными данными и использует их как последний запасной источник — именно это и рекламирует install.sh. Фраза печатается только после того, как запасной источник уже оказался пустым, поэтому она ничего не ломает; но она подводит человека к выводу «flower не умеет пользоваться моим токеном от Claude Code», а это неверно. Записано в issue #13.
Что делать: если на машине уже стоял Claude Code, новые учётные данные заводить не нужно — запасной источник сам их подхватит (см. install.md). Если вы всё-таки увидели эту строку — значит и в том файле нет пригодных полей с учётными данными; пишите .env по предыдущему пункту.
Непонятно, какие учётные данные и эндпойнт реально действуют¶
Симптом: .env вроде бы изменён, а запросы всё равно уходят на старый шлюз; или непонятно, какая модель сейчас используется.
Причина: у учётных данных и эндпойнта несколько источников (окружение процесса, .env, запасной источник), и кто победил — видно не по конфигу, а по рантайму.
Что делать: запустите один раз с -v. На старте печатается describe(): действующий BASE_URL и маппинг моделей, токен замаскирован.
Полная таблица флагов — в cli.md, полная таблица переменных — в config.md.
В .env осталась строка KEY=, и нижестоящие источники больше ничего не подставят¶
Симптом: в окружении процесса токен экспортирован, в .env есть строка ANTHROPIC_AUTH_TOKEN=, а ошибка «нет учётных данных» всё равно.
Причина: пустое значение — это тоже присваивание. KEY= из источника с высоким приоритетом занимает ключ, и источники с низким приоритетом уже ничего в него не положат; а check_credentials() (определена в env.py:184) проверяет «значение непустое» — и рапортует о нехватке. «Занято» и «отсутствует» — разные вещи, а симптом один в один: именно поэтому такие случаи труднее всего разглядеть самому.
Что делать: удалите строку целиком, не оставляйте пустое значение.
После удаления ещё раз проверьте действующие значения через -v. Правила разбора — в config.md.
Сторонний шлюз: подключение есть, но первый же раунд падает¶
Симптом: 401 / 403; либо сообщение о несуществующем имени модели; либо смена поколения прямо на старте с жалобой на «стартовый пол».
Причина: три класса неверной конфигурации, симптомы у каждого свои.
Три класса ошибок конфигурации шлюза и как их различить
| Симптом | Скорее всего | Куда лезть |
|---|---|---|
| 401 / 403 | Учётные данные валидны, но выданы не этим шлюзом; либо в BASE_URL не хватает пути или лишний хвостовой слэш | config.md |
| Имя модели не существует | Шлюз понимает только свои имена моделей, маппинг не настроен | config.md |
| Смена поколения сразу на старте, жалоба на «стартовый пол» | Окно задано слишком маленьким: порог ниже стартового пола роли (у координатора замерено около 34k) | --window, см. handoff.md |
Что делать: сначала выведите действующие значения через -v, потом правьте конфиг. Особенно стоит сверить окно: на машине, где велась разработка, шлюз настроен на claude-opus-5[1m]; если считать по 200 тысячам, как раньше, поколение менялось бы каждые 150 тысяч, тогда как на деле оно тянет 950 тысяч — разница в 5 раз, и работа длинного горизонта будет изрублена в кашу.
Запускается, но ведёт себя не так¶
В этой группе симптом — не ошибка, а команда отработала, код возврата 0, но сделано не то. Первые четыре пункта — подтверждённые дефекты кода, issue заведены; здесь даны обходные пути, а не исправления. Последний пункт — задуманное поведение.
flower setup запускает агента¶
Симптом: выполняете flower setup, ожидая вопросов про эндпойнт и токен, а оно начинает спрашивать «что нужно сделать» и дальше идёт полный сценарий go, приняв слово setup за описание задачи. По завершении в учётные данные не записано ни символа.
Причина: в _CMDS (cli.py:937) перечислены только "go", "run", "once", а "setup" пропущен. Шаг подстановки подкоманды по умолчанию переписывает argv ["setup"] в ["go", "setup"] — setup из подкоманды понижается до первого позиционного аргумента go, то есть до самого запроса. Ни один argv не доведёт до мастера настройки. Заведено как #11.
Что делать: прервите по Ctrl-C и напишите конфиг напрямую. setup и так только писал бы в этот файл:
Полные имена переменных и порядок поиска учётных данных — в config.md. После записи запустите flower -v в любом каталоге: напечатанный на старте действующий эндпойнт и есть результат сверки.
Полноширинный ? не запускает боковой опрос¶
Симптом: по правилам из бокового опроса вы ввели в приглашении ?这个目录能删吗, но oracle не поднялся — фраза была принята за ответ на текущий вопрос либо как есть ушла во входящие.
Причина: в cli.py:907 подряд идут две проверки startswith("?"), обе с одним и тем же ASCII-символом. По замыслу вторая должна проверять полноширинный ?. Китайская раскладка по умолчанию выдаёт именно полноширинный — то есть основная аудитория этой функции не может ею воспользоваться вообще. Заведено как #12.
Этот баг загрязняет требования
Не сработавший ? не даёт ошибки и не отбрасывается. Он обрабатывается как обычный ввод: на этапе уточнения — как ответ на текущий вопрос, в остальное время — уходит во входящие. Фраза, которую вы хотели спросить в сторону, попадёт в бриф. Заметили опечатку — тут же правьте .flower/notes/需求.md, ориентиром для всего нижестоящего служит именно этот файл.
Что делать: переключитесь на半角 и наберите ?, либо сначала наберите ASCII ?, а потом вернитесь в китайскую раскладку для текста.
У once накопленная стоимость всегда $0.00, а время всегда 0:00¶
Симптом: flower once отрабатывает от начала до конца, а в нижней строке статуса накопленная стоимость всё время показывает 累计 $0.00, таймер стоит на 0:00, хотя та же модель и та же работа в go считаются нормально.
Причина: render() у once создаёт новый Render на каждое полученное событие, вместе с ним пересоздаются аккумуляторы, и всё начинается с нуля. Накопленные величины постоянно обнуляются, а не остаются неучтёнными. Заведено как #14.
Что делать: если нужны точные цифры — идите через go, там этой проблемы нет. Если нужна однораундовая форма once, но хочется видеть счёт, после прогона смотрите runs/manifest.json — стоимость каждого шага записана там, и эта запись верна. Подробнее — в config.md.
Skill, положенный в plugin/, никогда не загружается¶
Симптом: skill написан по deploy.md, структура каталогов верная, но агент ведёт себя так, будто вообще не знает о нём — ни ошибки, ни строчки в логе.
Причина: plugin/ не попадает в wheel. В установленном пакете PLUGIN_DIR указывает на <site-packages>/plugin, такого каталога нет, и проверка существования перед загрузкой молча пропускает шаг. Все три пути install.sh подвержены этому, загрузка работает только в репозитории, выкаченном из исходников. Заведено как #15.
Что делать: сначала проверьте, во что вообще разрешается путь.
Напечаталось False — это как раз этот случай. Чтобы пользоваться skill'ами, сейчас есть ровно один способ: запускать из исходников.
git clone https://github.com/ChenyuHeee/flower
cd flower
python3 -m venv .venv && .venv/bin/pip install -e .
ln -sf "$PWD/.venv/bin/flower" ~/.local/bin/flower
Пакет, установленный с -e, указывает обратно в каталог выкачки, PLUGIN_DIR попадает на настоящий plugin/, и та же проверка напечатает True.
-T не даёт видимого эффекта в go¶
Симптом: добавили -T к flower go, сравнили с запуском без него — поведение полностью одинаковое, как будто флаг сломан.
Причина: это задумано, а не дефект. Путь go и так по умолчанию включает усечение, намерение, выражаемое -T, уже выполнено, поэтому повторное указание ничего не меняет. Реальный флаг на этом пути — обратный, --no-trim: его нужно указывать явно, чтобы усечение выключить. -T имеет смысл только в run и once.
Что делать: чтобы убедиться в go, что усечение включено, смотрите стартовый вывод с -v, а не наличие -T; чтобы выключить — передайте --no-trim. Полная семантика флагов — в cli.md.
Оно говорит, что сделало, а на деле не сделало¶
Страж цели существует именно для того, чтобы ловить такие случаи: у исполнителя есть систематическое смещение в оптимизм — он знает, что сделал, и не знает, что пропустил. Но и сам страж ошибается, причём ошибается закономерно. Ниже пять пунктов, разделённых на «пропустило то, что пропускать не следовало» и «никак не пропускает».
Оно пишет «здесь проверить нельзя» и пропускает¶
Симптом: в вердикте написано «в текущем окружении этот пункт проверить нельзя, считаем достигнутым», и workflow идёт дальше.
Причина: судья склеил «недостижимо» и «не достигнуто» в один вывод. Это разные выводы, об этом и говорит Выводов три, а не два: «не достигнуто» — вернуть и доделать, «недостижимо» — остановиться и спросить человека: принять, поменять цель или признать, что судья ошибся. Когда выводов только два — «достигнуто/не достигнуто», — реально невыполнимая цель заставит координатора крутиться вхолостую раунд за раундом, пока не кончится лимит.
Что делать: «здесь не проверить» никогда нельзя считать достижением. В instructions для судьи прямо опишите, что в вашем сценарии считается недостижимым, чтобы он выдавал «недостижимо» там, где это уместно. Если вы не хотите, чтобы вас останавливали вопросом, передайте --timeout 0: при недостижимости прогон просто остановится, а причина останется на диске — вместо того чтобы проскочить обманом.
Оно прочитало исходники и объявило работу сделанной¶
Симптом: в обосновании вердикта написано «в коде уже реализовано X», «сигнатура функции соответствует требованию», а к артефактам сборки, выводу команд и поднятому сервису никто не притрагивался.
Причина: судью увели на исходники. Судят артефакт, а не исходники — то, насколько правильно выглядит код, и то, работает ли сданное, это разные вещи. В первом исполнитель уже уверен, и повторная уверенность новой информации не даёт.
Что делать: пункты проверки должны быть утверждениями об артефакте. «Реализована выгрузка» не годится, годится «запустить ./app export out.csv, в out.csv три колонки заголовка». Так их и надо писать на шаге постановки цели, иначе судье останется самому додумывать расплывчатые формулировки.
Судья не может запускать команды и поэтому пропустил по Makefile¶
Симптом: цель — «собрать бинарник, который запускается на Linux», вердикт положительный. Вы сами делаете file — артефакт оказывается Mach-O, никакой не ELF.
Причина: по умолчанию судья — это judge(can_run=False), и у него в руках только Read / Glob / Grep. Эти три инструмента читают файлы, но не запускают file и не запускают ./app --version. Поэтому он идёт по пути наименьшего сопротивления, читает Makefile, видит в ветке Darwin кросс-компиляцию и решает, что условие выполнено. Он не соврал — он нашёл самое похожее на доказательство в пределах своих возможностей.
Когда can_run обязательно включать
Критерий простой: если в цели встречаются слова про «собранную вещь» — включать.
- Артефакты: бинарник, образ, пакет, сгенерированные данные — включать
- Поведение: сервис поднимается, команда возвращает 0, вывод совпадает с шаблоном — включать
- Чистый текст: написана ли документация, добавлено ли поле в схему — не нужно
В командной строке это --judge-can-run. При ручной сборке разные точки входа выглядят по-разному, но всё в итоге сводится к тому же параметру judge():
| Точка входа | Как передать | Где смотреть |
|---|---|---|
judge() | can_run= — обычный параметр | roles.py:361 |
with_goal() | can_run= — параметр, пробрасывается в judge() | goal.py:155 → :170 |
goal_step() | параметра can_run нет, но он попадёт в **spec_kw, а там как раз judge(..., **spec_kw) — дойдёт | goal.py:97 → :105 |
starter_flow() | judge_can_run=, превращается в with_goal(can_run=…); --judge-can-run идёт именно этим путём | starter.py:105 → :196 |
Цена в том, что судья действительно выполняет команды: раунд вердикта становится медленнее и дороже; взамен он проверяет живой объект, а не инструкцию к нему. См. Может ли судья запускать команды.
Что делать: если цель про артефакты — включайте --judge-can-run. Если не включаете, возьмите за жёсткое ограничение при формулировке пунктов «проверяемо одним чтением»: пункт, который так не формулируется, и означает, что здесь изначально нужен запуск команд.
В списке проверки полтора десятка пунктов, и он никак не проходит¶
Симптом: каждый раунд возвращают, список расхождений длинный, чем больше чините — тем больше становится, работа не заканчивается.
Причина: список написан по принципу «насколько я хочу быть строгим», а не «сколькими способами эта работа может провалиться». Длина списка определяется количеством способов провалиться: для задачи вроде git clone && make && ./app хватает трёх–пяти пунктов — собралось, запускается, работает. В реальном провале из HT002 задачу «поставить репозиторий и запустить» расписали на 15 пунктов: только 5 проверяли работоспособность, 6 проверяли соблюдение процесса, ещё 4 в принципе непроверяемы.
Что делать: правьте .flower/notes/目标.md — именно этот файл служит основанием вердикта. По каждому пункту спрашивайте «какому способу провалиться он соответствует»; если ответа нет — удаляйте. Шаг постановки цели и так ругается на непроверяемые пункты — не оставляйте их насильно.
Границы записаны как пункты проверки¶
Симптом: в списке появляются пункты вроде «не запускал brew install», «не менял файлы вне каталога проекта», и судья, чтобы доказать невиновность, лезет смотреть mtime у ~/.zshrc и проверять, не трогали ли каталог .flower/.
Причина: границы и пункты проверки ограничивают разные вещи, и их смешение стало основной причиной провала HT002 (корневая причина №1).
| Что ограничивает | Как соблюдается | |
|---|---|---|
| Границы | Как вы работаете («ставить только внутри каталога проекта», «бизнес-код не трогать») | Тем, что не выходите за них, а не пост-фактум доказательствами |
| Пункты проверки | Сданный результат («запустилось или нет», «результат верный или нет») | Проверкой на месте |
Границы — это как раз тот раздел, который на этапе уточнения предлагается расписывать подробно. Перенести их по одному в список проверки — значит на каждую границу добавить ещё одну проверку, а такие проверки в большинстве своём невыполнимы; невыполнимый пункт тащит за собой провал всего раунда вердикта.
Что делать: границы остаются в разделе «границы» брифа и соблюдаются тем, что за них не выходят; в список проверки они не идут. Если действительно нужно как-то отчитаться — одной фразой, а не шестью пунктами.
Контекст и стоимость¶
В прогонах на длинном горизонте контекст и деньги — одна и та же проблема: контекст упирается в потолок, и дальше либо смена поколения, либо этот шаг взрывается; а за каждую фразу, повторённую в очередном раунде, придётся платить заново в каждом последующем.
На полпути оно само открыло новую сессию и сказало «смена поколения»¶
Симптом В потоке событий появляется handoff, payload["phase"] сначала near, потом done, между ними уходит лишний раунд на написание документа передачи, после чего работа идёт как ни в чём не бывало.
Причина Контекст подошёл к порогу. flower не делает compact — он записывает состояние текущей сессии в документ передачи из пяти разделов и поднимает новую сессию, которая читает его и продолжает. Сжатие заодно стирает самую дорогую информацию — вроде «этот путь не работает», — а документ передачи явный, лежит на диске и в любой момент правится: принимающая сессия читает именно этот файл.
Что делать Это нормальный путь, вмешиваться не нужно. Смена поколения не считается повтором — attempts не растёт (он считает провалы), сгоревший session_id записывается в StepResult.retired, а наружу отдаётся session_id живого преемника (см. ../guide/handoff.md#换代不算重试账怎么记). Если действительно хочется откатиться к auto-compact из SDK — используйте --no-handoff.
Откуда берётся порог и почему значение по умолчанию такое агрессивное
at = window - headroom. window по умолчанию 1 миллион, определяется по имени модели: если в имени есть haiku — считается 200 тысяч, иначе 1 миллион. headroom по умолчанию 50k — auto-compact срабатывает на −33k, смена поколения должна успеть раньше, а «написать передачу» — это ещё один раунд; 50k покрывает оба условия.
Переоценка не является жёсткой ошибкой: если реальное окно меньше, порог просто никогда не будет достигнут, запрос вернётся от API с «prompt слишком длинный», flower распознаёт этот сигнал (handoff.is_overflow()) и тут же меняет поколение механически собранным деградированным документом — шаг не падает (см. ../guide/handoff.md#is_overflow把硬错变成当场换代).
Замер, который стоит упомянуть: шлюз на машине разработки настроен на claude-opus-5[1m]. Если считать по 200 тысячам, как раньше, поколение менялось бы каждые 150 тысяч, тогда как реально оно тянет 950 тысяч — разница в 5 раз, и работа длинного горизонта была бы изрублена в кашу.
Смена поколения прямо на старте, и она не прекращается¶
Симптом В ошибке упоминается «стартовый пол», либо один и тот же шаг меняет поколение раз за разом, пока не упрётся в max_generations=8.
Причина window задан слишком маленьким, порог оказался ниже стартового пола этой роли — у координатора замерено около 34k, одни только системный промпт и индекс верстака съедают столько. Новая сессия переходит черту с первой же реплики, дальше пишется передача, меняется поколение, снова переход черты — и так бесконечно (смена поколения не расходует лимит повторов, это сделано намеренно).
Что делать Выставьте --window в реальное окно модели; -v печатает действующий эндпойнт и маппинг моделей. При нормальном долгом прогоне до 8 поколений дело не доходит, и если дошло — почти наверняка причина именно эта, об этом прямо говорится и в тексте ошибки (см. ../guide/handoff.md#一道防跑飞的闸). Смежный симптом — «передача всегда деградированная»: причина записана в errors в runs/manifest.json.
Остановилось на полпути, говорит, что упёрлось в бюджет¶
Симптом Шаг не доделан, но остановился с причиной «превышена стоимость».
Причина AgentSpec(max_budget_usd=...) — это жёсткий потолок, а не мягкое напоминание; Runtime.total_cost() — сумма по текущему прогону.
Что делать Прежде чем поднимать потолок, убедитесь, что оно не крутится вхолостую. Если раунд за раундом возвращают, а прогресса нет, обычно судья выдаёт «не достигнуто» там, где должен выдать «недостижимо», — реально невыполнимая цель будет жечь бюджет до дна (см. ../guide/goal.md#三个结论不是两个). Убедились, что работа идёт, — тогда поднимайте потолок.
Почему этот прогон такой дорогой¶
Симптом Стоимость сильно выше ожидаемой, но по выводу непонятно, на что ушли деньги.
Причина Счёт не лежит в контексте модели. session_id каждого шага, стоимость, число повторов и причина провала записываются только в runs/manifest.json, дописываются между процессами. История повторов и оригинальные тексты ошибок тоже только там — модель их не видит, и это намеренно: если отклонённые вызовы копятся в контексте, координатор выучивает, что «Bash всё равно заблокируют», и перестаёт пробовать даже git status (Runtime(keep_denials=1) по умолчанию уже чистит, увеличивать не надо).
Что делать Откройте runs/manifest.json и разложите стоимость по шагам (раскладка на диске — в config.md#磁盘布局). Несколько замеренных ориентиров:
| Стоимость | |
|---|---|
| Стартовый пол одного subagent (не амортизируется) | ~4.3k tokens |
| Стартовый пол координатора | ~34k tokens |
tests/smoke.py, один агент по всей цепочке | ~$0.21 |
tests/flow_demo.py, три способа связать workflow | ~$0.39 |
tests/delegation.py, разделение труда + замер распределения контекста | ~$0.71 |
tests/isolation.py, три issue, три worktree | ~$0.9 |
Контекст растёт быстрее, чем делается работа¶
Симптом В каждом раунде задание повторяет одни и те же правила («сначала прочитай файл, потом правь», «бизнес-код не трогать», «после правок прогнать тесты»), а исполнитель и так им следует.
Причина Каждая фраза координатора попадает в его же transcript, а transcript только растёт. Повторили правила — заплатили за этот раунд, и в каждом последующем раунде будете платить за них снова. Если собеседник уже это знает, выгода от повторения нулевая, а издержки вечные.
Что делать Дисциплина закладывается в механизм, а не в реплики каждого раунда: если что-то выражается через allowed_tools, раздел «границы» в брифе или индекс верстака — не пишите это в задание; в задании только то, что изменилось в этом раунде. Само разделение труда экономит больше всего (см. ../guide/context.md#第一层分工省得最多). При resume старые крупные результаты инструментов можно заменить указателями на файлы через -T.
Прерывания и преемственность¶
Процесс убили, машину перезагрузили¶
Симптом Прогон оборвался на середине, открываете терминал заново и не знаете, как всё подхватить.
Причина Подхватывать особо нечего. Родословная (runs/lineage.json) хранит соответствие имя шага → session_id, сбрасывается на диск после каждого шага, при записи сначала пишется .tmp, потом атомарная замена — обрыв посередине не оставит половинчатого файла.
Что делать Вернитесь в тот же каталог и запустите flower ещё раз: каждый шаг продолжит свою прошлую сессию — требования заново не выспрашивают, цель заново не ставят, и даже тупики, которые пробовал координатор, помнятся. Если сказать нечего — просто Enter (см. ../guide/continuity.md#进程被杀和机器重启). Судья — исключение: это не Step, его отправляют прямо из gate, он никогда не идёт по пути родословной, поэтому в каждом раунде это свежая пара глаз.
Каждый раз начинает с нуля, ничего не подхватывается¶
Симптом Запускаете в том же каталоге, а оно снова выспрашивает требования.
Причина Три вида «не сходится», и во всех flower молча откатывается к старту с нуля, без ошибки — преемственность это приятное дополнение, её отказ не должен мешать человеку работать:
runs/lineage.jsonотсутствует, либоworkspaceвнутри него не совпадает с текущим путём (так бывает, если каталог скопировали)- сессии больше нет в
runs/sessions.db(базу удаляли) - файл родословной повреждён
Что делать Сначала проверьте, есть ли runs/lineage.json и верен ли workspace (за что отвечают три файла — см. ../guide/continuity.md#落在磁盘上的三个文件). Отсутствие преемственности после смены каталога сделано намеренно: project_key выводится из пути рабочей области, и старую сессию в новом месте не найти.
Хочу начать заново, но не хочу терять историю¶
Симптом Требования ушли в другую сторону, и не хочется, чтобы оно продолжало прошлый разговор.
Причина Поведение по умолчанию — продолжать. В уже использованном каталоге flower "顺便支持代码块高亮" — это не новая задача, а ещё одна реплика.
Что делать --new. Это архивация, а не удаление, старое остаётся в notes/archive/. При пробуждении сначала печатается строка с текущим размером контекста; если он великоват — тот же путь.
Пропала сеть, оно не ругается и не двигается¶
Симптом В интерфейсе новых событий нет, процесс жив, выглядит как зависание.
Причина Обрыв сети трактуется как «подождать», а не как провал. flower висит и ждёт: сначала пробует DNS, потом TCP, и продолжает только когда прошло (см. ../guide/continuity.md#韧性断网时挂着等而且错误不进接续后的上下文). В HT001 это было проверено настоящим сбоем (см. ../cases/ht001.md#六断网续跑第一次被真实故障验证).
Что делать Ждите, с -v видно, что зондирование идёт. Ошибки, накопившиеся за время ожидания, не попадают в контекст после возобновления — они остаются только в runs/manifest.json, а принимающая сессия видит чистую картину и не сбивается на череду таймаутов.
Двойной Ctrl-C — завершение отработало не полностью¶
Симптом После kill (SIGTERM) и после двойного Ctrl-C остаётся разное состояние.
Причина Известная дыра. Двойной Ctrl-C бросает KeyboardInterrupt: в cli.py блок finally внутри _drive вызывает rt.close(), но не вызывает rt.rescue() — rescue() вызывают только обработчики SIGHUP/SIGTERM.
Что делать Родословная сохраняется в обоих случаях (после каждого шага атомарный сброс на диск), поэтому следующий запуск всё равно подхватится, и эта дыра не отнимет у вас прогресс. Чтобы завершение отработало полностью, используйте kill <pid>, а не долбёжку по Ctrl-C.
Параллельность и изоляция¶
not in a git repository¶
Симптом С включённой изоляцией не стартует, ошибка not in a git repository.
Причина worker(..., isolate=True) выдаёт каждому агенту приватную копию через git worktree; если workspace не git-репозиторий, создать её невозможно.
Что делать Здесь нет молчаливой деградации — либо действительно запускайтесь внутри репозитория, либо выключайте isolate. Изоляция гарантирует, что несколько агентов правят одновременно и не видят чужих рабочих деревьев; она не гарантирует за вас отсутствие конфликтов при слиянии.
Изолированный агент не может писать в верстак¶
Симптом Subagent сообщает «отказано в праве на запись», скрипты и результаты не попадают на диск; либо результат оказался в одном из worktree и остальные агенты его не видят.
Причина Worktree — приватная копия каждого агента, а верстак — общий слой между агентами. Если положить общее внутрь приватной ограды, остальные, естественно, до него не доберутся.
Что делать При включённой изоляции указывайте верстак вне репозитория:
Когда он лежит вне рабочей области, Runtime автоматически выдаёт доступ через add_dirs; Runtime(workbench=True) это уже делает, а для самостоятельно созданного Workbench права надо выдавать самому.
У верстака два значения по умолчанию, и они разные
Workbench(workspace) — этим путём идут CLI и starter_flow() — кладёт верстак в <workspace>/.flower; а Runtime(workbench=True) (то есть -W) кладёт его в <run_dir>/workbench, то есть в runs/workbench. Поэтому после запуска flower вы получите .flower/, а вызов Runtime(workbench=True) из Python — нет.
При запуске flower есть .flower/, а в своём скрипте его нет¶
Симптом Бриф явно записан в .flower/notes/需求.md, а координатор ведёт себя так, будто не читал его; без ошибки.
Причина У вас на руках два объекта верстака. Бриф пишется в каталог A, а индекс, внедряемый в system prompt, сканирует каталог B, и обещание «с самого начала знать, где лежит файл требований», молча ломается. Оба варианта ошибки не дают ошибок: если собрать brief_path самому относительно cwd процесса, это будет не тот каталог, что созданный через -W <run_dir>/workbench; и достать его обратно из Runtime тоже нельзя — cli.py сначала вызывает main() и создаёт Workflow, и только потом строит Runtime, а к этому моменту brief_path уже зафиксирован.
Что делать Создайте Workbench сами и повесьте один и тот же объект одновременно на Workflow и Runtime — место зафиксируется:
wb = Workbench(Path.cwd()).ensure()
wf = Workflow(channel=ch, workbench=wb, steps=[...])
rt = Runtime(workspace=".", workbench=wb)
tests/trial_offline.py фиксирует это: пятое утверждение проверяет, что бриф присутствует в prompt_block(). Кроме того, индекс внедряется только в system prompt координатора, subagent'ы его не наследуют (замер $0.2461, tests/prelude_live.py) — путь должен передать дальше координатор, каждый subagent сам его не узнает (см. ../guide/workflow.md#工作台要挂在-workflow-上).