コンテンツにスキップ

エージェントライフサイクルフック

フックは、reyn セッションの 8 つのライフサイクルポイントで、コンテキストの注入・自己継続トリガー・サンドボックス内副作用実行を行う、薄いオペレーター/Skill スコープレイヤーです。

フックは既存の 2 つの仕組みの上に構築されています: 統合インボックス(ターンにメッセージを供給するチャネル)と P6 ライフサイクル(イベントストリーム)。新しい OS 機構は追加しません。フックを使うワークフローは OS の変更を必要としません(P7)。

ライフサイクルポイント

フックはスコープと方向の組み合わせで 8 点に発火します:

スコープ _start _end
session session_start session_end
turn turn_start turn_end
skill skill_start skill_end
task task_start task_end

各ポイントはawaited ディスパッチです。シェルの完了・プッシュのキューへの登録が終わってから、ライフサイクルポイントが次へ進みます。これにより、シェルフックはそのタイミングに同期的にアクセスできます(例: session_start シェルフックは最初のターンが始まる前に完了します)。

実装上のアンカー:

  • turn_end は terminal stop_reason で発火
  • skill_start / skill_endSkillRegistry.start() / .complete() で発火
  • task_start は Control IR の _create op で発火。task_end_update_status(status → completed)と _abort(status → aborted)の両方で発火 — 開始したタスクは終了方法に関わらず必ず対応する task_end が発火する
  • skill_end は現状クリーン完了時のみ発火 — 割り込み・エラーは #2068 で追跡中

3 つの設定スキーム

各エントリは相互排他な 3 つのスキームのちょうど 1 つを持ちます:

  • template_push — 設定の Jinja2 テンプレートから組み立てるプッシュ指示。
  • shell_exec — 純粋な副作用として実行するサンドボックスコマンド(出力は無視)。
  • shell_pushstdout が JSON プッシュ指示であるサンドボックスコマンド。template_push と同じ経路でプッシュされます(違いは指示のソースのみ: キャプチャした stdout か Jinja2 レンダーか)。

3 つのケイパビリティ

これらのスキームは均一に 3 つの振る舞いケイパビリティを提供します:

C — コンテキスト注入(wake: false のプッシュ)

[hook:name] 属性付きのシステムメッセージが統合インボックスにキューされます。次のターンで一緒に届きます — 追加ターンは発生しません。LLM がすぐに行動しなくてよい読み取り専用コンテキスト(メトリクス・タイムスタンプ・取得済みファクト)を付加するのに使います。template_push または shell_push の指示が wake: false のときに生成されます。

E — 自己継続(wake: true のプッシュ)

C と同じですが、wake: true フラグがランループに新しいターンを即座に開くよう指示します。これがフックの差別化ケイパビリティです: turn_end フックは人間の入力なしにエージェントを再起動できます。ループバルブ で制限されます。template_push または shell_pushwake: true のときに生成されます。

F — 外部副作用(shell_exec

サンドボックス内でコマンドを実行します。reyn は JSON イベントをコマンドの stdin に書き込み、stdout と stderr は無視します。外部状態の更新(ログエントリの書き込み・メトリクスの発信・Webhook へのポスト)に使います。安全モデルは サンドボックス を参照してください。

計算されたプッシュ(shell_push

stdout が単一の JSON オブジェクト {"push_when": bool, "wake": bool, "message": str, "session"?: str}(最初の 3 つは必須)であるサンドボックスコマンドです。stdout は template_push が生成するのと同じプッシュ指示にパースされ、同一の C/E 経路でディスパッチされます — つまりコマンドがランタイムにプッシュするか(push_when)・どう(wake)・何を(message)を決定します。stdout は純粋な JSON である必要があります(ログは stderr へ)。いかなる失敗(非ゼロ終了・無効な JSON・必須/型不一致フィールド)もプッシュをスキップします(フェイルセーフ)。ライフサイクルポイントは常に続行されます。session はパースされ前方互換のために保持されますが、クロスセッションルーティングはまだ配線されていません(現状は no-op。ディスパッチャーは現在のセッションにプッシュします)。

wake フラグとランループ

wake(デフォルト true)が C と E を分けます。ランループは各ターン後にインボックスをドレインします:

  1. キューされた全フックメッセージを収集します。
  2. wake: false のメッセージは次のターンのコンテキストとして含められます(wake: true がなければ次の人間駆動ターンまで保留)。
  3. wake: true が 1 つでも存在すれば、ループは1 ターンを発火します — 同じバッチの wake: false メッセージはそのターンのコンテキストとして一緒に届きます。

フックが設定されていないか、現在のライフサイクルポイントに一致しない場合、ループはフックなしのセッションとバイト同一です。ハッピーパスでオーバーヘッドはゼロです。

忠実性

プッシュは会話に追加される新規の [hook:name] 属性付きシステムメッセージです。既存の履歴を変更しません — オブジェクト同一性レベルで検証済みです(内容の等価比較ではなく)。

シェル出力は意図的に無視されます。reyn はトランスフォームフック(コンテキストやアーティファクトストリームを書き換えるフック)をサポートしません。実際のリダクション・トランケーション・コンテンツフェンスは OS レイヤーで実装されており、可視・イベント記録・監査可能です(secret-handling および コンテンツレイヤー防御 を参照)。

awaited ディスパッチアーキテクチャ

フックは HookDispatcher によってディスパッチされます。これは各ライフサイクルポイントでの第一級の同期 awaited 呼び出しです。EventLog サブスクライバーとは異なります:

仕組み タイミング 用途
HookDispatcher awaited 第一級 フック — ライフサイクルポイントが次へ進む前に完了必須
EventLog サブスクライバー sync-inline、await なし リアルタイムコンソールレンダー、アナリティクス
WAL 追記専用の永続ログ クラッシュリカバリ
P6 監査イベント async-tolerant 監査トレイル、リプレイ、eval

サブスクライバーは sync-inline で await できません — emit 時点でのファイア&フォーゲットです。プロセス終了を待つ必要があるシェルフックはサブスクライバーとして実装できません。HookDispatcher がこれを解決します。

各フックは独自の try/except ブロックでラップされます。フックの失敗はフック名に属して記録されますが、ライフサイクルポイントを中断したり LLM 出力に伝播したりしません。

ループバルブ

E(自己継続)は暴走するフック駆動セッションを防ぐために制限されています:

  • カウンター: safety.loop.max_hook_driven_turns(デフォルト 25)は最後の人間ターン以降のフック駆動ターン数をカウントします。
  • リセット: カウンターは人間ターンのたびにゼロにリセットされます。
  • 上限到達時: 設定された safety.on_limit アクションが発火します — warnask_userabort。いずれもセッションを生かし続けます(サイレントキルなし)。
  • 制限なし: max_hook_driven_turns: 0 に設定すると上限を無効化できます。

バルブは障壁ではなく安全網です。適切に設計された自己継続フックはキャップに達する前に完了します。バルブはバグや予期しないワークフローの動作によって開きっぱなしになるループを捕捉します。

サンドボックス

シェルフックは、Control IR の shell_exec op と同じバックエンド非依存のサンドボックス抽象化の中で実行されます: Seatbelt(macOS)、Landlock/seccomp(Linux)、Noop(非対応プラットフォーム)、またはコンテナバックエンド。安全なデフォルトが適用されます:

  • network: false — アウトバウンドネットワークをブロック
  • サブプロセス生成なし
  • コンセント失敗クローズ: サンドボックスバックエンドが確認できない場合、サンドボックスなしで実行するのではなくシェルフックを拒否します

コンセントと許可リスト

シェルフックコマンドを実行する前にオペレーターのコンセントが必要です。コンセントフローはライブの介入リスナーが接続されているかによって変わります:

  • TUI セッション — コンセントは統合介入バスを通じてルーティングされ、Pending タブのモーダルとして表示されます: "Shell hook <name> wants to run a command"(フックに設定された name: フィールド、または未設定の場合は汎用メッセージ)。3 つの選択肢:
  • [A]lways — 許可し許可リストに永続化します(~/.reyn/shell-hooks-allowlist.jsonREYN_SHELL_HOOKS_ALLOWLIST 環境変数で上書き可)。同じコマンドの将来の実行は自動承認されます。
  • [y]es — 今回の実行のみ許可します。
  • [n]o — スキップ(失敗クローズ)。
  • 非 TUI (reyn runmcp-serve、ヘッドレス) — バス使用前の動作にフォールバック: TTY stdin が利用可能なら stdin プロンプト、TTY でない場合は拒否。
  • 許可リストヒット — 許可リストに既存のコマンドはすべてのサーフェスでプロンプトなしにサイレント自動承認で実行されます。

コンセントは全体を通じて失敗クローズです: サンドボックスバックエンドが確認できない場合、サンドボックスなしで実行するのではなくフックを拒否します。

フルバックエンドモデルは sandbox、より広いコンセントアーキテクチャは permission model を参照してください。

P6 イベント: hook_shell_executed

すべてのシェルフック実行 — サイレント自動承認の実行も含む — は hook_shell_executed P6 イベントを発行します。このイベントは TUI Events タブ("tool" グループ)に次のように表示されます:

shell_exec: <コマンド> [rc=N]

(プッシュモードのフックは shell_push: プレフィックスになります。)コマンドが exit 0 の場合、リターンコードのサフィックスは省略されます。これにより、コンセントパスに関わらずシェルフックアクティビティの完全な監査トレイルをオペレーターに提供します。

設定

フックは reyn.yamlhooks: キーの下で宣言します。フルスキーマは reyn-yaml リファレンス § hooks ブロック を参照してください。

簡単な例 — turn_end 自己継続 template_pushsession_start shell_exec、stdout がプッシュを決める turn_end shell_push:

hooks:
  - on: turn_end
    template_push:
      message: "Run complete. Check for pending tasks."
      wake: true

  - on: session_start
    shell_exec: "echo session-started >> /tmp/reyn-hooks.log"

  - name: dynamic
    on: turn_end
    shell_push: "scripts/decide-next.sh"   # {"push_when":true,"wake":true,"message":"..."} を出力

最初のフックの wake: true は各 turn_end の後に新しいターンをトリガーし、メッセージをシステムコンテキストとして注入します。session_startshell_exec はログ行を追記します(出力は破棄)。shell_push はコマンドを実行し stdout をパースし、指示がそう言うときだけプッシュします。

未実装(Deferred)

以下のケイパビリティは設計済みですが未実装です:

  • クロスセッションプッシュ — プッシュ指示の session フィールドはパースされ保持されます(template_pushshell_push とも)が、別のセッションのインボックスへルーティングする配線はまだありません。現状プッシュは常に現在のセッションに着地します。
  • エージェントレベル・フェーズレベルフック — ターン内の細粒度ポイント(稀なユースケース。session/turn/skill/task が一般的なケースをカバー)。
  • 割り込み・エラー時の skill_endskill_end は現在クリーン完了時のみ発火します。エラー・割り込みパスは #2068 で追跡中。

参照