Перейти к содержанию

Частые проблемы и разбор ошибок

Когда что-то ломается, человек не знает, какой модуль сломался, — он знает только то, что видит. Поэтому эта страница сгруппирована по наблюдаемым симптомам, а не по подсистемам.

Структура каждого пункта одинакова: симптом (то, что вы реально видите) → причиначто делать.

Пять пунктов — это известные дефекты, а не задуманное поведение. В таких пунктах прямо сказано, что это баг, дана ссылка на issue и обходной путь — мы не выдаём их за осознанное решение.

Не устанавливается / не запускается

Полная процедура установки — в install.md. В этом разделе только случаи «установилось, но команда не запускается».

Версия Python ниже 3.10

Симптом: во время установки вылетает синтаксическая ошибка, либо pip прямо говорит, что не нашёл подходящей версии.

Причина: flower требует Python ≥ 3.10. Единственная зависимость времени выполнения — claude-agent-sdk, нативный бинарник лежит внутри его wheel, — так что если не ставится, дело почти всегда в версии интерпретатора, а не в сети.

Что делать: сначала выясните, в какой интерпретатор вы ставите.

python3 --version

Ниже 3.10 — берите другой и ставьте заново. Системный python3 часто оказывается не тем, на который указывает python в вашем терминале; сверить версию до установки дешевле, чем разбираться после (см. install.md).

Установилось, но flower: command not found

Симптом:

zsh: command not found: flower

Причина: пакет установился, но каталог со сгенерированным исполняемым скриптом не попал в PATH. Это не то же самое, что «не установилось»: если python3 -c "import flower" не падает, значит пакет в порядке.

Что делать: в shebang скрипта flower прописан абсолютный путь, поэтому достаточно сделать симлинк в каталог, который уже есть в PATH, — ничего сорсить не нужно.

ln -sf "$PWD/.venv/bin/flower" ~/.local/bin/flower

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 ничего не нашёл):

! 但 flower 不在 PATH 上。
  把这一行加进你的 ~/.zshrc 或 ~/.bashrc:
    export PATH="$HOME/.local/bin:$PATH"

BINDIR захардкожен как $HOME/.local/bin (install.sh:63). Для путей 1 и 3 это верно — uv ставит именно туда; не совпадает только запасной путь 4 через pip на macOS. Поэтому яма появляется лишь на машинах, где первые три варианта не сработали.

Что делать: не гадайте про каталог — спросите у интерпретатора.

python3 -c "import sysconfig; print(sysconfig.get_path('scripts', scheme='posix_user'))"

Добавьте напечатанный каталог в 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

Симптом:

缺少凭证:需要 ANTHROPIC_API_KEY 或 ANTHROPIC_AUTH_TOKEN

Причина: flower изолируется от конфигурации хоста через setting_sources=[], поэтому учётные данные нужно приносить с собой. Полный порядок поиска — в config.md.

Что делать: пропишите их в .env в корне репозитория либо в окружение процесса.

cp .env.example .env        # заполнить ANTHROPIC_AUTH_TOKEN или ANTHROPIC_API_KEY

.env уже в gitignore. Как это делать в контейнере — см. deploy.md.

Оно пишет «flower не читает ~/.claude/settings.json» — это неправда

Симптом: когда учётные данные не настроены, env.py:192 печатает строку:

flower 不读 ~/.claude/settings.json —— 那是可移植性的代价

Причина: эта фраза не соответствует коду. 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 и маппинг моделей, токен замаскирован.

flower -v

Полная таблица флагов — в cli.md, полная таблица переменных — в config.md.

В .env осталась строка KEY=, и нижестоящие источники больше ничего не подставят

Симптом: в окружении процесса токен экспортирован, в .env есть строка ANTHROPIC_AUTH_TOKEN=, а ошибка «нет учётных данных» всё равно.

Причина: пустое значение — это тоже присваивание. KEY= из источника с высоким приоритетом занимает ключ, и источники с низким приоритетом уже ничего в него не положат; а check_credentials() (определена в env.py:184) проверяет «значение непустое» — и рапортует о нехватке. «Занято» и «отсутствует» — разные вещи, а симптом один в один: именно поэтому такие случаи труднее всего разглядеть самому.

Что делать: удалите строку целиком, не оставляйте пустое значение.

grep -n '^[A-Za-z_][A-Za-z0-9_]*=$' .env     # перечислить все строки с пустым значением

После удаления ещё раз проверьте действующие значения через -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 и так только писал бы в этот файл:

mkdir -p ~/.config/flower
cat > ~/.config/flower/.env <<'EOF'
ANTHROPIC_AUTH_TOKEN=sk-...
EOF

Полные имена переменных и порядок поиска учётных данных — в 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.

Что делать: сначала проверьте, во что вообще разрешается путь.

python3 -c "from flower.core.agent import PLUGIN_DIR; print(PLUGIN_DIR, PLUGIN_DIR.is_dir())"

Напечаталось 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 — приватная копия каждого агента, а верстак — общий слой между агентами. Если положить общее внутрь приватной ограды, остальные, естественно, до него не доберутся.

Что делать При включённой изоляции указывайте верстак вне репозитория:

wb = Workbench(Path.cwd(), home=Path.cwd().parent / ".flower-proj").ensure()

Когда он лежит вне рабочей области, 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-上).