Daemon と設定

loopkeepd はすべてをこなす常駐プロセスです。トリガーを監視し、run を実行し、policy を 評価し、イベントログを書き、通知を配送します。CLI とデスクトップアプリはクライアントで、 閉じても実行中のものは何も変わりません。

1 つの daemon が登録済みのすべての workspace を提供します。各 workspace はそれぞれ 独立した context を持ちます。自分のイベントログ、policy、信頼状態、workflow です。

制御

lk start      # 別名: up
lk stop       # 別名: down — 実行中の run は次の起動時に interrupted として復元
lk restart
lk status

クライアントは daemon とローカルの socket、既定では /tmp/loopkeepd.sock で話します (LOOPKEEP_SOCKET で上書き — daemon とクライアントの両方に設定してください)。 ネットワークポートは開きません。ペアリングした場合の Console 接続は、外向きの WebSocket 1 本です。

ディスク上のファイル

グローバルは ~/.config/loopkeep/ の下(XDG_CONFIG_HOME を尊重。 LOOPKEEP_CONFIG は設定ファイルのパスを上書き):

~/.config/loopkeep/
├── config.yaml       # daemon の設定(並列数の上限、retention、attended のアイドルタイムアウト、multiplexer、tui.keybindings)
├── policy.yaml       # あなたの home policy layer
└── device.json       # Console のペアリング資格情報。パーミッション 0600 で書かれる

device.json は Console とペアリングしたときに作られ、このデバイスのトークンと署名用の 秘密鍵を持ちます。SSH 鍵と同じように扱ってください — 別のマシンにコピーしたり dotfiles リポジトリに入れたりせず、そのマシンで改めてペアリングします。ペアリングを解くなら lk logout が確実です。デバイスを無効化したうえでファイルを消します。手でファイルを消すだけ だと、このマシンが接続しなくなるだけなので、漏れた可能性があるなら Console からも無効化して ください。どちらの場合もローカルの run は動き続けます — Console に依存していないからです。

workspace ごとは <workspace>/.loopkeep/ の下:

.loopkeep/
├── config.yaml           # この project の上書き(concurrency.max、multiplexer)
├── policy.yaml           # project policy(これは commit する)
├── policy.local.yaml     # あなた個人の上書き(これは gitignore する)
├── workflows/            # workflow の定義
├── secrets               # secret の名前だけ — 値は OS の keychain にある
├── events.db             # append-only のイベントログ(SQLite)
└── worktrees/<run_id>/   # 各 run の隔離された git worktree

config.yaml

concurrency:
  global_max: 4 # 全 workspace を通じた実行中の run
retention:
  worktree_days: 14 # 完了した run の worktree を保持する期間。省略すると永久保持
attended:
  idle_timeout: 30m # attended run がこの時間放置されたら終了(エージェント停止・あなたの入力なし)。省略でタイムアウトなし
run:
  branch_template: "{workflow_name}/{run_id}" # 各 run が作業する git ブランチ名の雛形。省略でこの既定
notifications:
  slack:
    workspace: T012345 # Slack workspace/team ID
    channel: C012345 # Slack channel ID
multiplexer:
  id: tmux # herdr | tmux | zellij | cmux — 省略で未設定、"none" で無効
  options:
    session: work # attended の pane を開く herdr のセッション名。省略すると使用中のセッションに開く
    space: loopkeep # attended の pane を置くマルチプレクサのセッション/workspace
    tab: automation # その中の tab/window
tui:
  keybindings: # TUI の既定キーを上書きする。action 名は up/down/approve/deny/steer/take_over/quit など
    approve: ["y"]
    down: ["j", "down"]

concurrencyretention は CLI(全 workspace の上限は lk global-max、単一 workspace の concurrency.maxlk workspace-maxlk retention)とデスクトップ アプリから管理でき、編集はどちらの経路でもこのファイルに書き戻されます。multiplexerattended run をどこに開くかで、project 側は自分の .loopkeep/config.yaml に 同じキーを書いて上書きします。どちらのレイヤーもアプリから編集できます。

notifications.slack は、デスクトップの通知チャネルに加えて Slack の通知チャネルを 1 つ設定します。 workspacechannel は両方を空でない値にするか、両方を省略しなければなりません。省略時は デスクトップだけです。設定 → Slack 通知 から 2 つを設定できます。承認リクエストは即時に、 notify レベルの項目はスケジュールされた digest だけで届きます。ここで選ぶのは人向けの通知チャネルで、 workflow の safe-outputs.slack-post の宛先ではありません。

この通知経路にはデバイスのペアリングが必要です。ペアリングされていないデバイスで notifications.slack を設定しても Slack には何も届かず、失敗は daemon のログに残すだけです。 受信箱のアテンションにはなりません。

投稿前に、Slack 通知の各フィールドから URL、www. で始まるアドレス、メールアドレス、<#C…> の チャンネル参照、permalink を取り除き、フィールドごとに 300 文字へ切り詰めます。そのため、エージェントが 通知文へ加えた pull request の URL は Slack 通知には入りません。

run のブランチ名

run.branch_template は、各 run が自分の worktree で作業する git ブランチの 名前を決めます。展開できる変数は {workflow_name}{run_id}{workspace_name} の 3 つです。 省略すると既定の {workflow_name}/{run_id} になります。workflow 側は x-loopkeep: ブロックの branch_template で上書きできるので、優先順は workflow → このファイル → 既定です。

値は git が受け付ける形に整えられるため、Nightly deps: fix という名前の workflow は Nightly-deps--fix/<run_id> になります。ブランチ名を作れない雛形(未知の変数、閉じていない 波括弧)は daemon のログに出したうえで既定へ倒します — 起動は止めません。デスクトップアプリは 保存時にそうした雛形を弾きます。

{run_id} を入れない指定も有効で、使い道があります。agent-work のような固定名にすると、 その workflow は 1 本のブランチを育てていき、各 run のチェックポイントが前の run の作業の上に 積み重なります。git は 1 つのブランチを同時に 1 か所でしかチェックアウトできないため、その ブランチが使用中(別の run、またはあなた自身のチェックアウト)のときに始まった run は、 黙って別の場所で作業する代わりに、その理由を示して失敗します。

attended.idle_timeout は、エージェントが止まったあと attended run があなたを待つ時間に 上限をかけます — 超えると loopkeep が run を終了し(中断・再開可能、受信箱に一報)。値は policy の timeout と同じ表記の duration で — 30m2h90s500ms、無単位は秒 — 設定しない限りオフです。project 側は自分の .loopkeep/config.yaml に書き、そちらがここの 全体値を上書きします。どちらもアプリから編集できます。全体の既定は設定 →「バックグラウンド サービス」、project ごとの上書きはプロジェクト設定です。

イベントログ

システムがすることはすべて — トリガーの発火、状態の変化、提案されたアクション、 あなたの決定 — が events.db の中の不変の記録で、workspace ごとです。受信箱、run の 一覧、監査証跡はすべてこのログから派生したビューです。ただし、イベントログを 読み取れないときや監査記録を書き込めないときに受信箱へ出す 1 枚のカードだけは、現在の状態から作ります。 ずれる第二の帳簿はありません。 記録が更新・削除されることはありません — 訂正は新しいイベントです。ファイルは読み取り専用の 証跡として扱ってください。起動時に永続化できなければ、daemon は履歴を静かに失うのではなく 起動を拒否します。

実行中にイベントログの読み取り・書き込みに失敗した場合

実行中にイベントログの読み取りまたは書き込みに失敗しても、daemon は動き続けます。読み取りの 失敗中は run の履歴を取得できないことがあります。書き込みが失敗し、監査記録を書けなかった アクションは実行せずに拒否し、対象の run は失敗として終わります。 書き込みに失敗した監査記録は残りません。受信箱にはこの障害を知らせるカードを 1 枚だけ表示し、 lk statusDEGRADED と報告します。このカードは承認リクエストではないため、lk approve 0 は 拒否します。保存領域へのアクセスが復旧すると、daemon はこのカードを消し、障害についての通知を 出します。

監督の証跡もここから来ます。誰が、いつ、どのルールが有効なもとで、何を承認したかは、 再構成ではなくクエリです。

workspace レジストリ

lk workspaces                          # 一覧
lk workspaces add <path> [--name <n>]
lk workspaces remove <id>              # home は削除できない

.loopkeep/ を含む未登録のパスに対するコマンドは、その場で登録します。

トラブルシュート

症状考えられる原因
CLI が daemon は動いていないと言うlk start。カスタムの LOOPKEEP_SOCKET を使うなら、daemon と CLI が一致していることを確認する。
workflow が自動で発火しないworkspace が信頼されていない(lk trust)、workflow が disable(lk enable <workflow>)、またはグローバル pause が on(lk resume --all)。
run が waiting のままあなたを待っている — lk inbox を確認し、lk approve <seq> で決める。
再起動後に run が interrupted になる想定どおり: lk stop は実行中の run を次の起動時にそう復元する。lk rerun する。
run_* の worktree が溜まるretention が off か長い — lk retention <days>、または lk cleanup で今すぐ一掃する。
lk statusDEGRADED と報告する実行中にイベントログの保存領域で障害が起きた。読み取りができないことがあり、書き込みに失敗した場合は監査できないアクションを実行せずに拒否し、対象の run は失敗として終わる。受信箱にはこの障害を知らせるカードを 1 枚だけ表示する。保存領域を修復すると、復旧時にカードを消して通知を出す。カードは承認リクエストではないため、lk approve 0 は拒否する。
Console に接続できないペアリングが失敗している間、lk statusremote: connecting (last error: …) を表示する。詳しい理由は下の診断ログにある。

診断ログ

daemon は診断ログを ~/.local/state/loopkeep/loopkeepd.log(XDG_STATE_HOME を 尊重)に書き、5 MB でローテーションして 1 世代前を loopkeepd.log.old として残します。 アダプターの spawn エラー、Console 接続の失敗など、workspace ごとのイベントログには 出ない daemon レベルの詳細が載ります — run がイベントを出す前に失敗したときや、 ペアリングが接続できないときはここを見ます。