ワークフロー

workflow は markdown ファイルです。YAML frontmatter が「いつ発火し、何をしてよいか」 を宣言し、本文がエージェントへの自然言語の指示になります。workflow は <workspace>/.loopkeep/workflows/*.md に置かれ、ほかのファイルと同じように project と一緒にバージョン管理されます。

---
on:
  schedule:
    - cron: "0 3 * * *"
---

Review yesterday's commits and update CHANGELOG.md with anything user-visible.
{{ trigger }}

形式は GitHub Agentic Workflows と互換です。上流に存在するキーは 上流の意味を保ち、loopkeep は新しいキー(ローカルのトリガー、ローカルの safe-outputs)を追加するだけです。loopkeep 固有の設定は x-loopkeep の下にネストして 置きます。未知のキーは警告付きで保持され、捨てられることはありません。

すべてのキーは workflow frontmatter リファレンス に記載しています。

編集と実行

アプリで ワークフロー を開きます。+ 新規 workflow は、トリガー、プロンプトの 本文設定(engine、concurrency、budget、dispatch、launch、coalesce、Slack の safe-output)のフォームと、markdown ファイル全体を扱う Raw タブを持つエディタを開きます — 編集したブロックの外側のコメントやキーは 保持されます。Edit は既存の workflow を同じエディタで開きます。▶ 実行 は手動 run を 開始し(manual トリガーの無い workflow はヒントを出します)、workflow ごとのスイッチが 自動トリガーをオン/オフします。これらは project が信頼されていない間でも動きます。

Raw タブはただのテキスト欄ではありません。入力に合わせて frontmatter のキーと値、本文の {{ trigger.* }}、そして連携済みアカウントから取得した名簿(repos / channels / labels / workflows)を補完します。

トリガー context

何が発火させたのかを構造化して記述したもの — トリガーの種別、ソースイベント、上流の出力 — を 本文から使えます。全体を入れるなら {{ trigger }}、1 つの値だけなら {{ trigger.event.branch }} のように書きます。書かなければ何も足されません(context の無い 本文になります)。これによって run-completed の workflow は上流の run の出力を見られますし、 Slack で発火した workflow は自分をメンションしたメッセージを見られます。

gateway が locator を渡した場合、対応する GitHub・Slack の発火には trigger.resource として入り、 GitHub の issue・pull request・comment、または Slack メッセージを指す座標になります。無ければ そのパスは空の値に展開されます。Slack の channel ID が C で始まる run では、本文に正確な {{ trigger.thread }} を書くと、daemon がエージェントを起動する前に現在のスレッドをローカルで 取得します。判定するのは現在の公開状態ではなく ID の接頭辞です。公開として作成されたチャンネルは あとで private に変えても C ID のままで、private として作成されたチャンネルの ID と DM の ID では 空の値になります。取得した本文は信頼できない入力として扱ってください。resource の形と遅延取得の規則は トリガー を参照してください。

engine

engine: { id: claude, model: claude-sonnet-5 }

loopkeep が動かせるのは今は claude エンジンだけで、Claude Code CLI をサブプロセス として動かします。engine を省略しても run は claude で動きます(gh-aw の既定は copilot ですが loopkeep はそれを動かさないので、notice がログに出ます)。それ以外の engine id は run 開始時に失敗し、黙ってフォールバックはしません。modelparams は workflow ごとなので、軽い heartbeat の workflow は小さいモデルで動かし、重い作業は 大きいモデルで動かせます。gh-aw が定義しない engine id を使うなら x-loopkeep の下に engine を書きます — loopkeep は root の engine よりこちらを優先します(詳しくは frontmatter リファレンス)。

safe-outputs

safe-outputs は、workflow が生み出してよい効果を宣言します:

safe-outputs:
  create-pull-request: {} # GitHub ネイティブ、あなたの gh 認証を使う
  local-commit: {} # run の worktree で commit する
  notify: {} # inbox に通知を投稿する
  slack-post:
    channel: C012345 # 必須。エージェントは別の宛先を選べない
    workspace: T012345 # 複数候補から選ぶときは必須。1 件に解決できるときだけ省略可
  slack-reply: {} # run を起こした Slack の会話にだけ返信
  dispatch-workflow:
    allowed: [incident-triage] # 別の workflow を発火(明示的な allowlist が必須)
  emit-output: {} # chaining 用の構造化 JSON 出力

safe-output を宣言しても監督は迂回されません。エージェントが提案するすべての アクションは、実行前になお policy 評価 を通ります。emit-output だけが例外で、これは下流の workflow 向けのデータを生むだけなので常に auto-approve されます。

Slack の呼び出しは宣言でゲートされます。slack_post は固定宛先を示す slack-post.channel が無いと使えず、slack_reply は Slack で発火した run でも slack-reply: {} が無いと使えません。どちらも組み込みの notify floor から始まり、policy で ask-first まで引き上げられます。

どちらの Slack 出力にも、このデバイスと Console のペアリングが必要です。ペアリングされていない デバイスでは両方とも deny になります。slack_post には Slack 接続も必要です。workspace を省略した ときにコネクタ一覧を取得できなければ、宛先を推測せず deny します。

投稿を試みたあとに Slack から失敗が確定した場合も、timeout で成否不明になった場合も、run は 失敗させず受信箱にアテンションを作ります。timeout ではすでに届いている可能性があるため loopkeep は 再試行せず、重複投稿を避けるためエージェントも再試行してはいけません。

workflow を連鎖する

run どうしが直接やり取りすることはありません。3 つの経路がつなぎます:

  1. run の出力 — エージェントが emit-output で JSON を出し、run-completed で購読している workflow がそれをフィルタでき(if: event.output.severity == "high")、トリガー context として受け取ります。
  2. dispatch — エージェントが dispatch-workflow で別の workflow を発火させると 決め、入力の payload を渡します。対象は allowed リストに載っている必要があり、 dispatch 自体も policy 評価を受けます。
  3. ファイルシステム — ファイルを書き、file-watch のトリガーで下流の作業を 起こします。多段のパイプライン(PDF を置く → 抽出 → 要約)に最適です。

連鎖は暴走しないよう深さで制限されます — トリガー を参照してください。

予算

x-loopkeep:
  budget:
    tokens: 200000
    time_ms: 900000
    steps: 30
    daily_runs: 300
    daily_tokens: 500000

値はすべて整数です。予算を超えた run は failed になります(理由は budget exceeded)。 あなたの承認を待っている時間は time 予算には数えられません。

並列数

x-loopkeep:
  concurrency:
    max: 1
    on_limit: skip # queue(既定)| skip | replace

queue は空きが出るまで新しい run を保留し、skip は発火を捨て(黙ってではなく 記録される)、replace は実行中のものを中断して新しいものを優先します — 最新だけが 意味を持つ heartbeat に便利です。すべての workflow を通じた daemon 全体の上限は lk global-max で設定します。

tag

x-loopkeep:
  tags: [deploy, migration]

tag は policy が突き合わせられる意味的なラベルです。workflow が自己申告するものなので、 エスカレーションの便利手段として扱ってください — 硬い安全ルールは、workflow が 偽れない pathstool で突き合わせるべきです。