よくある質問とトラブルシューティング¶
問題が起きたとき、人はどのモジュールが壊れたのかを知らない —— 知っているのは自分が何を見たか、それだけだ。 だからこのページはあなたが観測した現象でグループ分けしてあり、サブシステム別ではない。
各項目の構造は同じ:症状(実際に目にするもの)→ 原因 → 対処。
そのうち5項目は既知の不具合であって、設計上そうなっているわけではない。該当する項目では bug であることを明言し、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" がエラーにならなければパッケージは無事だ。
対処:flower スクリプトの shebang は絶対パスなので、すでに PATH に入っているディレクトリへ シンボリックリンクを一本張れば十分だ。何かを source する必要はない。
macOS:install.sh の指示どおり PATH を足したのに、まだ command not found¶
既知の問題(issue #16)
このアドバイスは、まさにそれを必要とするマシンでだけ機能しない。
症状:macOS で install.sh を実行し、最後に出た指示どおり ~/.local/bin を PATH に足して ターミナルを開き直しても、flower はまだ command not found。
原因:pip フォールバックの経路に入ると、macOS の pip は実行スクリプトを ~/Library/Python/3.X/bin に置く。一方 install.sh が足せと言うのは ~/.local/bin だ。 二つのディレクトリが噛み合わないので、言われたとおりにしても効かない。
install.sh がどの順でインストール方法を選ぶか、そしてあの指示の原文
優先順位は4段であって、2段ではない(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 はそこに入れるからだ。噛み合わないのは macOS での4番、pip フォールバックだけ。 つまりこの落とし穴は、前の3つがどれも成立しなかったマシンでのみ現れる。
対処:ディレクトリを推測せず、インタプリタに訊く。
出力されたディレクトリを 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 は動くのにソースを変えても反映されない。あるいはアップグレードしても古いままだ。 あるいは同じマシンの二つのターミナルで挙動が違う。
原因:3つのインストール方法はパッケージと実行スクリプトを別々の場所に置く。PATH 上で先に ヒットしたものが走る。
3つのインストール方法がどこに落ちるか
| インストール方法 | 実行スクリプト | いつ使うか |
|---|---|---|
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 | フォールバック。ディレクトリは前項参照 |
対処:いま走っているのがどれかを先に確認し、それからどれを触るか決める。
ソースを変えるなら 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= を残すと、下流がもう埋められなくなる¶
症状:プロセス環境でトークンを export していて、.env にも ANTHROPIC_AUTH_TOKEN= の行があるのに、 それでも「認証情報がない」と言われる。
原因:空値も一回の代入だ。優先度の高い供給元にある KEY= がそのキーを占有し、優先度の低い 供給元はもう埋めに来ない。そして check_credentials()(定義は env.py:184)が見るのは「値が空でないこと」なので、 やはり欠落と報告される。「占有」と「欠落」は別の事象なのに、現象はまったく同じだ —— この種の問題で 自力で気づくのがいちばん難しいところがここにある。
対処:行ごと消すこと。空値を残さない。
消したら -v で効いている値をもう一度確認する。パース規則は config.md を参照。
サードパーティゲートウェイ:接続はできたが最初のラウンドで失敗する¶
症状:401 / 403。あるいはモデル名が存在しないと言われる。あるいは開始直後にハンドオフし、 さらに「起動フロア」と報告される。
原因:3種類の設定ミスがあり、現象がそれぞれ違う。
3種類のゲートウェイ設定ミスの見分け方
| 現象 | たいていは | どこを触るか |
|---|---|---|
| 401 / 403 | 認証情報自体は正しいがこのゲートウェイが発行したものではない。あるいは BASE_URL のパスが足りない/末尾スラッシュが余計 | config.md |
| モデル名が存在しないと言われる | ゲートウェイは自前のモデル名しか受け付けない。マッピング未設定 | config.md |
| 開始直後にハンドオフし「起動フロア」と出る | ウィンドウを小さく設定しすぎ:閾値がロールの起動フロアを下回っている(コーディネーターは実測で約 34k) | --window、handoff.md 参照 |
対処:設定を触る前に、まず -v で効いている値を出す。ウィンドウの項目はとくに突き合わせる価値がある —— 開発に使っているこのマシンのゲートウェイは claude-opus-5[1m] を設定しており、以前のように 20万で計算すると 15万ごとに世代交代することになるが、実際には 95万まで走れる。5倍の差があり、 ロングホライズンの作業はずたずたに切り刻まれる。
動くが挙動が正しくない¶
このグループの症状はいずれもエラーではなく、コマンドは完走し、終了コードは 0 なのに、やっていることが 間違っているというものだ。最初の4項目は確認済みのコードの不具合で、issue も出してある。ここに書くのは 回避策であって修正方法ではない。最後の1項目は設計上そうなっている。
flower setup が起動するのは agent だ¶
症状:flower setup を実行し、エンドポイントとトークンを訊いてくれると思っていたら、 「何をしますか」と訊きはじめ、そのまま go のフローを完走し、setup という語をタスクの記述として扱う。 走り終わっても認証情報は一文字も書かれていない。
原因:cli.py:937 の _CMDS には "go"、"run"、"once" しか並んでおらず、"setup" が漏れている。 デフォルトサブコマンドを補う処理が argv ["setup"] を ["go", "setup"] に書き換える —— setup はサブコマンドから go の第1位置引数、つまり要求そのものへと降格する。 設定ウィザードに到達できる argv は存在しない。 #11 に報告済み。
対処:Ctrl-C で止め、設定ファイルを直接書く。setup がもともとやるのも、このファイルに文字を書くことだけだ:
変数名と認証情報の探索順序の全体は config.md を参照。 書き終わったら任意のディレクトリで flower -v を一度走らせる。起動時に出力される有効なエンドポイントが 突き合わせの結果になる。
全角の ? ではオラクル問答が起動しない¶
症状:オラクル問答 の書き方どおり、入力プロンプトで ?这个目录能删吗 と打ったのに、 オラクルは起動せず、いま出ている質問への回答として扱われるか、そのまま受信箱に入る。
原因:cli.py:907 は startswith("?") の判定を続けて2回行っているが、2回とも同じ ASCII 文字を使っている。 コードの意図としては、2回目は全角の ? を判定するはずだった。中国語入力メソッドがデフォルトで出すのは全角だ —— この機能の主な利用者が、ちょうど一人残らず使えない。#12 に報告済み。
これは要求を汚染する
空振りした ? はエラーにならないし、捨てられもしない。通常の入力として処理される: 確認フェーズならいま出ている質問への回答として扱われ、それ以外のときは受信箱に入る。 こっそり一言訊きたかっただけの文が、ブリーフに書き込まれる。 打ち間違いに気づいたら、その場で .flower/notes/需求.md を直すこと。下流の基準になるのはそのファイルだ。
対処:半角に切り替えてから ? を打つか、先に半角の ? を打ってから中国語に戻して本文を書く。
once の累計コストが永遠に $0.00、経過時間が永遠に 0:00¶
症状:flower once を最初から最後まで走らせても、下部のステータス行の累計コストがずっと 累计 $0.00 のままで、タイマーもずっと 0:00 のまま。同じモデル・同じ作業なら go では数字が出る。
原因:once の render() はイベントを1つ受け取るたびに新しい Render を作るので、 アキュムレータも一緒に作り直され、毎回ゼロから始まる。累計値は繰り返しゼロクリアされているのであって、 集計していないわけではない。#14 に報告済み。
対処:数字を正確に見たいなら go を使う。そちらの経路は影響を受けない。once の単ラウンドの形が ほしくて、かつ会計も見たいなら、走り終わってから runs/manifest.json を見る —— 各ステップのコストは そこに記録されており、その記録は正しい。詳細は config.md を参照。
plugin/ に置いた skill がいつまでもロードされない¶
症状:deploy.md のとおりに skill を書き、ディレクトリ構成も正しいのに、 agent はその存在を知らないかのように振る舞う —— エラーもなく、ログも一行も出ない。
原因:plugin/ が wheel に含まれていない。インストールされたパッケージでは PLUGIN_DIR が <site-packages>/plugin を指すが、そのディレクトリは存在せず、ロード前の存在チェックが黙ってスキップする。 install.sh の3経路すべてが該当する。ロードできるのはソースをチェックアウトしたリポジトリだけだ。 #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 では効果を確認できない¶
症状:flower go に -T を付けて、付けた場合と付けない場合を比べても挙動がまったく同じで、 スイッチが壊れているように見える。
原因:これは設計上そうなっているのであって、不具合ではない。 go の経路はもともとデフォルトで トリムが有効なので、-T が表す意図はすでに満たされており、重ねて指定しても変化しない。 この経路で本当のスイッチになるのは逆向きの --no-trim —— トリムを切りたいときに明示的に渡す。 -T が意味のあるスイッチになるのは run と once だけだ。
対処:go でトリムが有効であることを確かめたいなら -v で起動時の出力を見る。-T の有無で判断しないこと。 切りたいなら --no-trim を渡す。スイッチの完全な意味は cli.md を参照。
終わったと言うが終わっていない¶
ゴールガード が存在するのはまさにこの種を止めるためだ —— ワーカーには系統的な楽観バイアスがあり、 自分が何をしたかは知っているが、何を漏らしたかは知らない。しかしガード自身も誤判定するし、その誤りの方向には 規則性がある。以下の5項目は「通すべきでないものを通した」場合と「いつまでも通さない」場合に分けてある。
「ここでは検証できない」と言って、そのまま通る¶
症状:判定結果に「現在の環境ではこの項目を検証できないため、達成とみなす」と書かれ、フローがそのまま先へ進む。
原因:ジャッジが「達成不能」と「未達成」を一つの結論に混ぜている。この二つは別の結論であり、 結論は二つではなく三つ が言っているのはまさにこのことだ: 「未達成」は差し戻して続けさせること、「達成不能」は止まって人に訊くこと —— 受け入れるのか、目標を変えるのか、 それとも判定が間違っているのか。「達成/未達成」の二つしか結論がないと、実際には達成できない目標のために コーディネーターがラウンドを空回りさせ、予算が尽きるまで走り続ける。
対処:「ここでは検証できない」を達成として扱ってはならない。 ジャッジへの instructions に、 あなたのシナリオで何を達成不能とみなすかを名指しで書き、「達成不能」を出すべきときに出させること。 止まって訊かれるのが本当に嫌なら --timeout 0 を渡す:達成不能なら即座に停止し、理由はディスクに残る。 ごまかして通すのではなく。
ソースを読んだだけで完了と言う¶
症状:判定理由に「コードでは X が実装されている」「関数シグネチャは要件を満たしている」と書かれているが、 ビルド成果物もコマンド出力も、起動したサービスも、何一つ触られていない。
原因:ジャッジがソースへ誘導されている。判定するのは成果物であってソースではない —— ソースが正しそうに見えるかどうかと、引き渡したものが使えるかどうかは別の話だ。前者はワーカーがすでに確信していることであり、 もう一度確信し直しても新しい情報は生まれない。
対処:判定項目は成果物に対する断言として書くこと。「エクスポート機能を実装した」では不十分で、 「./app export out.csv を走らせると out.csv に3列のヘッダがある」なら成立する。目標を書く段階でこう書くべきで、 そうでなければジャッジは曖昧な項目を自分で補完するしかない。
ジャッジがコマンドを実行できず、Makefile を読んで通してしまう¶
症状:目標は「Linux で動くバイナリをビルドする」で、判定は通った。自分で file をかけてみると、 成果物は Mach-O で、ELF ではまったくない。
原因:ジャッジはデフォルトで judge(can_run=False) であり、手元には Read / Glob / Grep しかない。 この3つはファイルを読めるが、file は実行できないし、./app --version も実行できない。 そこで次善策として Makefile を読み、Darwin のブランチにクロスコンパイルと書いてあるのを見て条件を満たしたと判断する。 嘘をついたわけではない。能力の範囲内でもっとも証拠らしいものを見つけただけだ。
can_run を必ず有効にすべきなのはどんなときか
判断基準は単純だ:目標に「ビルドされたもの」の類の語が出てきたら有効にする。
- 成果物系:バイナリ、イメージ、パッケージ、生成データ —— 有効に
- 挙動系:サービスが起動する、コマンドが 0 を返す、出力があるパターンに一致する —— 有効に
- 純テキスト系:ドキュメントを書いたか、あるフィールドが schema に追加されたか —— 不要
コマンドラインでは --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 |
代償として、ジャッジは実際にコマンドを実行するので、1ラウンドの判定が遅く高くなる。引き換えに検証するのは 現場であって、現場の説明書ではない。ジャッジはコマンドを実行できるか 参照。
対処:目標が成果物に関わるなら --judge-can-run を有効にする。有効にしないときは、 「読めれば判定できる」を判定項目の書き方の厳格な制約とすること —— そう書けない項目は、そもそも コマンド実行が必要な項目だということだ。
判定チェックリストが十数項目あって、いつまでも通らない¶
症状:毎ラウンド差し戻され、足りない点が長々と列挙され、直すほど増え、作業が終わらない。
原因:チェックリストが「どれだけ厳密にしたいか」で書かれており、「この作業には何種類の失敗の仕方があるか」で 書かれていない。チェックリストの長さは失敗の仕方が何種類あるかで決まる: git clone && make && ./app のようなタスクなら 3〜5項目で十分 —— ビルドが通る、起動する、使える。 HT002 の実際の事故では、 「リポジトリを入れて動かす」タスクが 15項目として書かれた:動くかどうかを検証しているのは 5項目だけで、 6項目は手順の遵守を検証しており、さらに 4項目は原理的に検証不可能だった。
対処:.flower/notes/目标.md を直すこと。判定の根拠になるのはそのファイルだ。1項目ずつ 「これはどの失敗に対応するのか」と問い、答えられないものは削る。目標を設定するステップは検証不能な項目に対して もともと警告を出すので、警告されたものを無理に残さないこと。
境界が判定項目として書かれている¶
症状:チェックリストに「brew install を実行していない」「プロジェクトディレクトリ外のファイルを変更していない」の類の 項目が現れ、ジャッジが潔白を証明するために ~/.zshrc の mtime を調べたり、.flower/ ディレクトリが触られていないかを 調べたりする。
原因:境界と判定項目が制約するものは違う。混ぜたのが HT002 のあの事故の主因だ (根本原因その1)。
| 何を制約するか | どう守るか | |
|---|---|---|
| 境界 | どう作業するか(「プロジェクトディレクトリ内にだけ入れる」「業務コードに触らない」) | 越えないことで守る。事後の自己証明ではない |
| 判定項目 | 引き渡したもの(「動いたか」「結果は正しいか」) | その場での検証で守る |
境界はまさに確認フェーズで書き込むよう促される部分だ。それを1項目ずつチェックリストへ移すのは、 境界を1つ足すたびにチェックを1つ増やすのと同じであり、この種のチェックはたいてい検証できない —— 検証できない項目は、判定ラウンド全体を道連れに失敗させる。
対処:境界はブリーフの「境界」の節に残し、越えないことで守る。判定チェックリストには入れない。 どうしても説明が要るなら一文で済ませ、6項目に分解しないこと。
コンテキストとコスト¶
ロングホライズンなランではコンテキストとお金は同じ問題だ:コンテキストが上限まで伸びれば、ハンドオフするか そのステップが落ちるかのどちらか。そして毎ラウンド繰り返す言葉には、後続の全ラウンドで再び金を払うことになる。
途中で勝手に新しいセッションを始めて「ハンドオフ」と言う¶
症状 イベントストリームに handoff が現れ、payload["phase"] がまず near、次に done となり、 その間にハンドオフ文書を書く1ラウンド分の時間が余計にかかる。その後は通常どおり作業が続く。
原因 コンテキストが閾値に近づいた。flower は compact しない —— 現在のセッションの状態を5節構成の ハンドオフ文書として書き出し、新しいセッションを起こしてそれを読ませ、続きをやらせる。 compact は「行き止まりだった道」のような最も高価な情報ごと消してしまうが、 ハンドオフ文書は明示的で、ディスク上にあり、いつでも編集できる:引き継ぐセッションが読むのはそのファイルだ。
対処 これは正常な経路なので放っておいてよい。ハンドオフはリトライに数えない —— attempts は増えない (数えるのは失敗だ)。使い切った session_id は StepResult.retired に記録され、外向きの session_id は 常に生きている後継のほうだ (../guide/handoff.md#换代不算重试账怎么记 参照)。 どうしても SDK の auto-compact に戻したいなら --no-handoff。
閾値はどう決まるのか、そしてなぜデフォルトがこれほど積極的なのか
at = window - headroom。window はデフォルト 100万で、モデル名から判定する:名前に haiku を含むものは 20万、それ以外は 100万。headroom はデフォルト 50k —— auto-compact は −33k で発火するので、ハンドオフは その前に間に合わせる必要があり、しかも「ハンドオフを書く」こと自体にもう1ラウンドかかる。50k はこの二つを同時に満たす。
大きく見積もっても致命的ではない:実際のウィンドウがもっと小さければ閾値には永遠に届かず、リクエストは API から「prompt が長すぎる」として返される。flower はこのシグナルを認識し (handoff.is_overflow())、 その場で機械的に組み立てた縮退版でハンドオフするので、このステップは失敗しない (../guide/handoff.md#is_overflow把硬错变成当场换代 参照)。
実測として触れておく価値があること:開発に使っているこのマシンのゲートウェイは claude-opus-5[1m] を設定している。 以前のように 20万で計算すると 15万ごとに世代交代することになるが、実際には 95万まで走れる —— 5倍の差があり、ロングホライズンな作業はずたずたに切り刻まれる。
開始直後にハンドオフし、しかも止まらない¶
症状 エラーに「起動フロア」が出る、あるいは同じステップで繰り返しハンドオフし、max_generations=8 に当たるまで続く。
原因 window が小さすぎ、閾値がそのロールの起動フロアを下回っている —— コーディネーターは実測で約 34k で、 システムプロンプトとワークベンチインデックスだけでそれを食う。新しいセッションが口を開いた瞬間に 線を越えるので、ハンドオフを書き、世代交代し、また越え、永遠に止まらない(ハンドオフはリトライ枠を消費しない。それは意図的だ)。
対処 --window をモデルの実際のウィンドウに合わせる。-v が有効なエンドポイントとモデルマッピングを出力する。 通常の長時間ランで 8世代に達することはない。本当に当たったならほぼ確実にこれが原因で、エラーメッセージもそう明言している (../guide/handoff.md#一道防跑飞的闸 参照)。 関連するもう一つの症状は「ハンドオフがいつも縮退する」ことで、理由は runs/manifest.json の errors に書かれている。
途中で止まり、予算上限に達したと言う¶
症状 ステップが終わらないうちに停止し、理由はコスト超過。
原因 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 1つの起動フロア(償却不能) | ~4.3k tokens |
| コーディネーターの起動フロア | ~34k tokens |
tests/smoke.py 単一 agent のフルチェーン | ~$0.21 |
tests/flow_demo.py workflow の3つの繋ぎ方 | ~$0.39 |
tests/delegation.py 分担 + コンテキスト分布の計測 | ~$0.71 |
tests/isolation.py 3つの issue に3つの worktree | ~$0.9 |
コンテキストが作業の進みより速く伸びる¶
症状 毎ラウンドのタスクブリーフで同じ規律(「先にファイルを読んでから直す」「業務コードに触らない」 「直したらテストを走らせる」)を繰り返しており、ワーカーはもともとそのとおりにやっている。
原因 コーディネーターが言うことはすべて自分の transcript に入り、transcript は増える一方だ。規律を一度言い直せば、 このラウンドで金を払い、後続の全ラウンドでもその分をもう一度払い続ける。相手が既に知っていることを言い直しても 利得はゼロで、コストは恒久的だ。
対処 規律は仕組みに入れ、毎ラウンドの発話に入れない:allowed_tools、ブリーフの「境界」の節、 ワークベンチのインデックスで表現できることはタスクブリーフに書かない。タスクブリーフにはこのラウンドで変わったことだけを書く。 分担そのものが最も節約できる層だ (../guide/context.md#第一层分工省得最多 参照)。 resume 時には古い大きなツール結果を -T でファイルポインタに置き換えられる。
中断と継続¶
プロセスが kill された、マシンが再起動した¶
症状 途中で消えてしまい、ターミナルを開き直してもどう拾い直せばいいか分からない。
原因 拾い直すものは何もない。リネージ (runs/lineage.json) が記録するのは ステップ名 → session_id で、各ステップが終わるたびにディスクへ書き出される。書くときはまず .tmp に書いて アトミックに置換する —— 途中で kill されても半端なファイルは残らない。
対処 同じディレクトリに戻って flower をもう一度走らせるだけで、各ステップは前回のセッションの続きになる: 要求をもう一度問い直されないし、目標を設定し直すこともないし、コーディネーターがどの行き止まりを試したかも覚えている。 何も言いたくなければそのまま Enter を押す (../guide/continuity.md#进程被杀和机器重启 参照)。 ジャッジは例外だ —— あれは Step ではなく gate から直接送り出されるので、リネージの経路をそもそも通らない。 だから毎ラウンドまっさらな別の目になる。
毎回ゼロから始まり、まったく継続していない¶
症状 同じディレクトリでもう一度走らせると、また要求を問い直される。
原因 3種類の「噛み合わない」があり、flower はいずれも黙って最初からやり直し、エラーを出さない —— 継続は上乗せの機能であって、それが効かないからといって人の作業を止めるべきではない:
runs/lineage.jsonがない、あるいはその中のworkspaceが現在のパスと一致しない(ディレクトリをコピーして移すとこうなる)- session がもう
runs/sessions.dbにない(DB を消した) - リネージファイルが壊れている
対処 まず runs/lineage.json の有無と workspace が正しいかを見る (3つのファイルの役割は ../guide/continuity.md#落在磁盘上的三个文件)。 ディレクトリを移したあと継続しないのは意図的だ:project_key はワークスペースのパスから導出されるので、 古い session は新しい場所では見つからない。
やり直したいが、履歴は捨てたくない¶
症状 要求の方向がすでに変わっており、前回の話の続きをされたくない。
原因 デフォルトの挙動が続きを話すことだ。使用済みのディレクトリで flower "顺便支持代码块高亮" と打つのは 新しいタスクではなく、もう一言足しただけだ。
対処 --new。これはアーカイブであって削除ではない。古いものは notes/archive/ に残る。 ウェイク時にまず現在のコンテキスト規模が1行報告されるので、大きすぎると感じたときもこの経路を使う。
ネットワークが切れると、エラーも出さず動きもしない¶
症状 画面に新しいイベントが出ず、プロセスは生きていて、固まったように見える。
原因 切断は失敗ではなく「少し待つ」として扱われる。flower はぶら下がったまま待つ:まず DNS を探り、 次に TCP を探り、通ったら続行する (../guide/continuity.md#韧性断网时挂着等而且错误不进接续后的上下文 参照)。 HT001 ではこれが実際の障害で検証された (../cases/ht001.md#六断网续跑第一次被真实故障验证 参照)。
対処 待つこと。-v で探査が走っているのが見える。待機中に溜まったエラーは継続後のコンテキストには入らない —— それらは runs/manifest.json にだけ残り、引き継いだセッションが見るのはきれいな現場だ。タイムアウトの連続に引きずられない。
Ctrl-C を2回連打すると、後始末が最後まで走らない¶
症状 kill(SIGTERM)で落とした場合と、Ctrl-C を2回連打した場合とで、残る現場が違う。
原因 既知の欠落。Ctrl-C の2回連打が投げるのは KeyboardInterrupt で、cli.py の _drive の finally は rt.close() を呼ぶが、rt.rescue() は呼ばない —— rescue() を呼ぶのは SIGHUP/SIGTERM のハンドラだけだ。
対処 リネージはどちらの経路でも保たれる(各ステップの終了時にアトミックに書き出される)ので、もう一度走らせれば 同じように継続でき、この欠落で進捗を失うことはない。後始末を最後まで走らせたいなら、Ctrl-C を連打せず kill <pid> を使う。
並列と隔離¶
not in a git repository¶
症状 隔離を有効にすると起動せず、not in a git repository と出る。
原因 worker(..., isolate=True) は git worktree で各 agent にプライベートなコピーを渡す。 workspace が git リポジトリでなければ作れない。
対処 これは黙って縮退しない —— 本当にリポジトリの中で走らせるか、isolate を切るかのどちらかだ。 隔離が保証するのは「複数の agent が同時に編集しても互いのワークツリーが見えない」ことであって、 マージが衝突しないことを保証するものではない。
隔離された agent がワークベンチに書き込めない¶
症状 subagent が「書き込み権限を拒否された」と報告し、スクリプトも成果物もディスクに落ちない。 あるいは成果物がどれか一つの worktree に落ちて、他の agent から見えない。
原因 worktree は各 agent のプライベートなコピーで、ワークベンチは agent をまたぐ共有層だ。 共有すべきものをプライベートな柵の中に置けば、当然ほかの者は取れない。
対処 隔離を有効にするときはワークベンチをリポジトリの外に置く:
ワークスペースの外に置くと 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/ ができるが、Python で Runtime(workbench=True) を呼んでもできない。
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 がこれを固定している:5番目のアサーションがブリーフの prompt_block() への出現を検証する。 またインデックスが注入されるのはコーディネーターの system prompt だけで、subagent には継承されない (実測 $0.2461、tests/prelude_live.py)—— パスはコーディネーターが下流へ伝える必要があり、各 subagent が 自動的に知るわけではない (../guide/workflow.md#工作台要挂在-workflow-上 参照)。