ポリシーと信頼

policy はアテンションのルーターです。エージェントが提案するすべてのアクション — 編集、コマンド、MCP のツール呼び出し — は、実行される前に 決定的にあなたの ルールで評価され、4 つのレベルのいずれかに振り分けられます。エージェントが自分の レベルを決めることはありません。

レベル何が起きるか
auto-approveあなたなしで実行。通知なし。
notify実行し、止めずに受信箱を更新する。配送時期はチャネルによる。
ask-firstrun は止まり、受信箱であなたの決定を待つ。
denyアクションはきっぱり拒否。run は続き、監査記録が残る。

上の deny は policy による通常の判定です。監査記録を書き込めないためにアクションを拒否する場合は 別です。アクションは実行せず、run は失敗として終わり、書き込みに失敗した 監査記録は残りません。

policy の置き場所

~/.config/loopkeep/policy.yaml        # home — あなた、すべての project で共通
<workspace>/.loopkeep/policy.yaml            # project — チーム、commit してレビューする
<workspace>/.loopkeep/policy.local.yaml      # project-local — あなた、この project だけ(gitignore する)

レイヤーは意図的に「最も近いものが勝つ」ではありません。2 種類のルールは異なる 振る舞いをします:

  • default は出発点です。project-local → home → project の順で解決し、最初に 一致したものが勝ちます — project の default はいつでも自分向けに上書きできます。
  • floor(allow_override: false)は交渉不能の最低ラインです。すべてのレイヤーに またがる、一致するすべての floor が適用され、最も厳しいものが勝ちます。これは 両側に効きます。チームの floor をあなた個人の設定で緩めることはできず、home policy の floor は、信頼していないリポジトリの中でもあなたを守ります。

ルールを書く

ルールはアプリの ポリシー 画面(project のレイヤー)または 個人ポリシー(home の レイヤー)で編集します。Rules エディタ フォームが 1 項目ずつ組み立て、Raw タブが ディスク上の policy.yaml そのものを持ち、入力に合わせてキーと値を補完します。ファイルを直接 編集してもかまいません。スキーマはどちらでも同じです:

version: 1
policies:
  ask-me: { level: ask-first, timeout: { after: "72h", then: abort } }
rules:
  - id: prod-migration
    match:
      paths: ["migrations/**", "infra/prod/**"] # gitignore 形式の glob、リスト内は OR
      tool: [bash, edit] # 異なる match キーは AND
    policy: ask-me
    allow_override: false
    reason: "Production changes always get a human"
defaults:
  level: notify
delivery:
  digest: { cron: "0 9 * * *" }
  quiet_hours: ["23:00-07:00"]

ルールは 5 つの軸で突き合わせます。pathstooltagsworkflow、そして command(生のシェルコマンドに対する部分一致)です。ルールはレベルをインライン (level:)で持つか、名前付き policy を参照します(policy:)— 両方は不可です。 完全なスキーマは policy file リファレンス にあります。

2 つの軸には注意が要ります。tags は workflow の自己申告で、command はシェル変数で かわせます — 便利なケースのエスカレーションに使い、硬い floor はエージェントが 書き換えられない pathstool で書いてください。

評価の仕組み

提案された各アクションについて:

  1. default を解決する — project-local → home → project の順で最初に一致した default ルール。なければ、どこかのレイヤーが宣言した最も厳しい defaults.level
  2. floor を計算する — すべてのレイヤーで一致する allow_override: false の ルールのうち最大のレベル、加えて組み込みの floor。
  3. 自己申告を適用する — エージェントは自分のアクションを sensitive と申告できます。 これはレベルを 上げる ことしかできず、下げることはできません。
  4. 最終レベル = この 3 つの最大値。

floor がより低い default を上書きしたとき、その引き上げは監査イベントとして記録され ます — 監督が実際に働いた瞬間が見えます。

組み込みの floor

loopkeep 自身の監督の仕組みへの書き込みは常に ask first で、どのレイヤーもこれを 緩められません。対象は、あなたの loopkeep 設定、workspace の .loopkeep/ ディレクトリ(エージェントが正当に作業する run の worktree は除く)、実行中の workflow 自身の定義ファイル、loopkeep のバイナリ、そしてイベントログです。floor は deny ではなく ask-first です — あなたの判断を必ず求めるだけで、アクションを 拒否するわけではありません。シェルコマンドも検査されますが、保護されたパスが 書き込みの位置 に来たときだけです。リダイレクトの宛先か、rmcpmvteeddsed -i の引数です。保護されたファイルを読むだけ、あるいはコマンドの どこかに名前が出てくるだけでは floor はかかりません。エージェントが、自分を監督する policy をこっそり編集することはできません。アプリは、これらの保護対象を 個人ポリシー 画面に読み取り専用で一覧表示します。

loopkeep のツールのうち 4 つは、パスではなくツール名で効く floor を持ちます。 ask_humanask-first を、notify_humanslack_replyslack_postnotify を 下回りません。エージェントがあなたに届くための経路は、どのレイヤーの設定でも 塞げないということです。逆にこれより厳しくすることはできます。

これを 2 つの backstop が締めくくります。承認画面は常に生の diff かコマンドを主表示 とし(エージェント自身の要約は副次)、verdict なしに実行されているアクションを 検知したら、run はただちに一時停止します。

信頼

clone したばかりのリポジトリは、緩い policy と前のめりなトリガーを積んでいるかも しれません。そこで policy は direnv のように動きます。workspace を信頼するまで、その project policy と自動トリガーは無効です — あなたの home policy と手動の run だけが 動きます。信頼は policy ファイルのハッシュを記録します。あとで編集があると、あなたが diff を承認するまで workspace は信頼されていない状態に戻ります。あなた個人の floor は、 信頼に関係なくどこでも適用されます。

アプリでは、ポリシー 画面でルールを確認して 保存して信頼(または 信頼し直す、 信頼されていないバナーの ポリシーで信頼する)を選びます。ターミナルからは lk trust。 信頼した workspace の trigger を policy を変えずに止めたいときは lk untrust(アプリの プロジェクト設定 → すべてのトリガーを停止)。承認は残るので、policy が変わっていなければ lk trust で再確認なしにそのまま再開できます。

explain と test

すべてのアテンションはその provenance を示します — どのルール、どのレイヤー、どの ファイルの何行目か。同じ仕組みをオンデマンドでも使えます。

ポリシー 画面の Policy を確認 カードは、ツール(と任意の paths・tags)を受け取り、 最終レベルと、どのレイヤーのどのルールが決めたかを表示します。期待するレベルに対して テストすることもできます。

explain は一致した各ルール、そのレイヤーとファイルの場所、上書き可能かどうか、 そして最終レベルを表示します。test は結果から exit code を決めるので、policy の 変更もほかのコードと同じように CI テストに載せられます。

digest と quiet hours

delivery(home と project-local レイヤーのみ)は通知が届く 方法 を整えるもので、 レベル は決して変えません。notify の項目はすぐ受信箱に見えます。接続中のデスクトップには、 quiet hours 外なら live の受信箱更新もすぐ届きます。 delivery.digest.cron は別に 1 つへまとめた digest 通知をスケジュールし、既定は毎日 09:00 です。 この cron と quiet_hours はどちらも daemon のマシンのシステムローカル時刻を使います。 workflow の on.schedule cron は引き続き UTC です。

quiet window は固定幅の HH:MM-HH:MM で、開始と終了は異なる時刻にします。開始を含み、終了は 含みません。日付をまたげ、複数の window は和集合になります。v0.3 では曜日指定はありません。 不正なエントリは警告してその 1 件だけを 無視し、正しいエントリは残します。警告は daemon のログに書かれます。live の notify 更新は window 中 保留し、終了時に順序を保った 1 batch で流します。window 内に期日が来た digest は workspace ごとに 保持・結合し、終了まで待ちます。

ask-first は常にすぐ鳴り、digest を待たず、保留中の notify を早く放出することもありません。 設定 → Slack 通知 で workspace と channel を指定すると、同じゲートから Slack にも送ります。 承認リクエストは個別に即時、notify はスケジュールされた digest だけで届きます。Slack のメッセージは 情報表示であり、承認・拒否は loopkeep から行います。

リポジトリの permission 設定は尊重される

リポジトリの .claude/settings.json があるツールに確認を求めている場合 (permissions.ask)、loopkeep はそれをエスカレーション専用の引き上げとして ask-first に取り込みます — アクションのレベルを上げることはできても、下げることは できません。workspace ごと、またはグローバルに、個人レイヤーで import_repo_ask: false にすればオプトアウトできます。