looop 0.12.0

A tiny, portable, Kubernetes-shaped control loop for your work
# TODO — design review findings

設計レビュー(2026-06)で挙がった改善点。重要度順。`file:line` は指摘時点の目安。

## 🔴 Critical — 主張と実装の乖離 / 安全性

- [x] **C1. 「git = memory」が実装に存在しない** — README/manual が "git = the loop's
      memory" と謳うが `git init/add/commit` はコードに皆無。
      *対応済み: 主張を「memory is the files in the data dir」に撤回(#feat/remove-looop-run)。*
      将来 git 履歴(各tick=1コミット、ロールバック、`looop diff`)を入れるなら別タスクで再検討。
- [x] **C3. single-writer 不変条件を `looop run` が破る** — pulse 稼働中でも拒否せず
      並走し journal/goals/PLAYBOOK をレース。
      *対応済み: `looop run <goal>` を全廃(#feat/remove-looop-run)。*
- [x] **C2. RULE 1「1 tick = 1 move」の強制** — typed-action 化で解決。decider は
      `.decision.json` に1アクションを emit し、**looop が唯一の executor**。モデルが
      暴走しても tick あたり1 move にコードで限定される(#feat/typed-action-executor)。
      *これで十分とし、完全サンドボックス化(下記)は **キャンセル***
      ~~残タスク(完全サンドボックス化):~~
      - ~~`run_shell` をコードでゲート(deny-list:rm -rf / git push / gh pr merge / kubectl delete 等)~~
      - ~~decider のツール権限を落とす(stage B:調査は sensor、出力は .decision.json のみ)~~
      - ~~`--dangerously-skip-permissions` を外す(上記が済めば不要に)~~

## 🟠 High — コスト暴走 / 堅牢性

- [x] **H1. 失敗 tick にバックオフがない**`.last-tick-hash` は成功時のみ書込のため、
      毎回失敗する tick が cadence ごとに無限リトライ&無限課金。
      *対応済み: `.tick-backoff` 状態ファイルに「同一 world hash での連続失敗数」を記録し、
      指数バックオフ(60s·2^(n-1)、上限1h)。beat の成功判定を「usable な decision を emit
      した時のみ hash commit」に厳密化(runner crash / 不正 decision / no-decision は全て
      失敗扱いで backoff、hash 未commit)。`src/tick.rs`*
- [x] **H2. 予算上限 / サーキットブレーカがない**`looop cost` で可視化のみ。
      *対応済み: config `max_daily_usd`(正の値で有効、既定 off)。tick は AI 呼び出し前に
      ledger の当日合計を集計し、上限到達で skip + `tick.budget` warn イベント。ローカル深夜で
      リセット。F3 と統合。`src/cost.rs`(spent_today/daily_budget), `src/tick.rs`*
- [x] **H3. claude ランナーの tick コストが計上されない** — pi は `| looop _fmt` を通すが
      claude tick は通さないため常にゼロ行。runner 間で会計挙動が非対称。
      *対応済み: `_fmt` を claude の stream-json `result.total_cost_usd` も計上できるよう拡張
      (pi=per-message 加算 / claude=累計値採用)。default claude tick を
      `--output-format stream-json --verbose | _fmt` に変更し両 runner で対称に計上。
      `src/cost.rs`, `src/config.rs`(doc に非対称の経緯も明記)*
- [x] **H4. `looop tick` が snapshots を共有してレースする**`tick()`/`cmd_tick` は共有
      `snapshots/` を wipe→再生成。二重起動 / cron 重複で互いの snapshots を消し合う。ロック未取得。
      *対応済み: (1) lock 取得を `run::acquire_lock`(atomic mkdir + PID liveness 再利用)に
      整理。(2) その後 `looop tick` サブコマンド自体を廃止(pulse は `tick()` をプロセス内で
      直接呼ぶので subcommand は不要)。beat runner は単一インスタンスの pulse のみになり、
      共有 snapshots のレースは原理的に消滅。`src/run.rs`, `src/main.rs`, `src/tick.rs`*

## 🟡 Medium — 整合性 / 設計

- [x] **M1. 環境変数の名前空間汚染**`export_env` が裸の `CONFIG` / `CLAIMS_DIR` /
      `REPORTS_DIR` / `COST_LEDGER` を全子プロセスに export。特に `CONFIG` は衝突の温床。
      *対応済み: `LOOOP_CONFIG` / `LOOOP_CLAIMS_DIR` / `LOOOP_REPORTS_DIR` /
      `LOOOP_COST_LEDGER` に統一。プロンプト/CONTRACT は相対パス+`$LOOOP_BIN`参照で
      影響なし。`LOOOP_CONFIG` export は子の config をプロファイルに固定する副次効果も。`src/main.rs`*
- [x] **M2. `setup` ブートストラップが死んだ corpse で詰まる** — starter PLAYBOOK の
      "if a session setup already exists, do nothing" が Exited/Killed の corpse も
      「exists」と解釈し最大 retention(3d) 停止しうる。
      *対応済み: seed PLAYBOOK を「RUNNING な setup のみ block、dead corpse は無視して
      新規開始(`looop ls` で state 確認)」に厳密化。`src/seed/PLAYBOOK.md`*
- [x] **M3. PLAYBOOK 編集は wake するが sensor 編集は wake しない(非対称)**      `world_hash` は sensors/*.sh をハッシュしない。
      → *対応済み: 意図的な挙動として `src/worldhash.rs` の doc コメントと `manual.txt`
      の ONE BEAT に明記(sensor script 編集は snapshot が変わるまで wake しない)。*
- [x] **M4. 判断は弱モデル・実行は強モデル** — tick=sonnet/low、worker=opus/medium。
      *対応済み: pi の default tick を `claude-opus-4-8 --thinking low` に格上げ
      (最重要の一手を最強モデルが下す)。worker は opus/medium 据え置き。world-hash
      gate と1手のみの emit でコストは限定。config コメントに rationale 明記。`src/config.rs`*
- [x] **M5. config がプロファイル非スコープ**`LOOOP_DATA_DIR` で分離してもconfigは共有。
      *対応済み: config を `<data_dir>/looop.json` に移動。プロファイルを分ければ
      config も自動で分かれる。明示の `$LOOOP_CONFIG` は引き続き最優先(配線共有用)。`src/paths.rs`*

## 🟢 Feature / UX

- [x] **F1. `looop journal [--tail N]`** — 意思決定ログを一級コマンドで読む(今は cat か
      `log pulse`=イベント列のみ)。
      *対応済み: `src/journal.rs``cmd_journal``--tail N`/`-n N`)。help/README/
      補完(zsh/bash)に追加。あわせて既に廃止した `tick` サブコマンドを補完からも除去。*
- [ ] **F2. `looop tick --dry-run`** — プロンプトを組み立てて表示するだけで LLM を呼ばない
      (課金なしでプロンプト調整)。
- [x] **F3. `max_daily_usd` サーキットブレーカ** — H2 と統合。→ *H2 で実装済み。*
- [ ] **F4. `looop doctor`** — runner が PATH にあるか / config が parse できるか /
      データディレクトリ書込可か を診断。
- [ ] **F5. オンボーディング — runner 自動検出** — default が `pi` 固定 + モデル名ハードコード。
      claude しか無いユーザは config 編集必須。初回に検出。