タイムトラベル:巻き戻しと再開¶
Reyn のタイムトラベルシステムは、agent を過去の任意のチェックポイントに巻き戻し、そこから分岐させます。クラッシュリカバリとは別の機能であり、異なるメカニズムを使い、異なる目的に対応します。
クラッシュリカバリ(WAL + スナップショット復元)は予期しない障害の後に agent を自動的に復元します。状態は WAL(
.reyn/state/wal.jsonl)と seq キーのスナップショットから再構築され、ユーザーには透過的で、停止した場所まで前方再生します。タイムトラベルはユーザーが意図的に開始する過去のポイントへの巻き戻しであり、新しい履歴ブランチをフォークするオプションがあります。
概念¶
チェックポイント¶
チェックポイントは agent の状態の境界を示します。Reyn は以下のタイミングでチェックポイントを作成します:
- ターン境界 — LLM がレスポンスターンを完了した後
- プランステップ境界 — 各プランステップが完了した後
- フェーズ境界 — スキルラン内の各 OS フェーズ遷移の後
各チェックポイントは単調増加するシーケンス番号(seq)を持ちます。seq はすべてのタイムトラベル操作をアドレス指定するグローバルクロックです。
巻き戻し¶
/rewind は agent を過去のチェックポイントに巻き戻します。ランタイム基板 — agent の会話状態とスキルラン実行メモ — を目標 seq のスナップショットに復元します。
Reyn がタイムトラベルするのは自身の .reyn/ 状態のみです。ユーザーのワークスペースファイルは HEAD のままです。何がリカバリコアになるかの完全な分類は .reyn/ ディレクトリレイアウト を参照してください。
追記専用の履歴¶
Reyn は履歴を書き換えません。過去のチェックポイント(seq N)に巻き戻すとき:
- リセットレコードが現在の先端を超えた新しい seq Rに追記され、ターゲット N を保持します。そのレコードが新しい先端になり、agent の状態が N 時点に復元されます。
- N より前の履歴は保持されます。
(N, R)の範囲のターンは現在のブランチで放棄されます。ブランチツリーから到達可能ですが、消去はされません。
つまり巻き戻しは常に回復可能です:ブランチを切り替えることで放棄された未来に戻れます。
ブランチング¶
巻き戻し後は現在のブランチを上書きする代わりにそのポイントからフォークできます:
- アンドゥ(アクティブブランチ checkout) — 現在のブランチ内での巻き戻し。ブランチのタイムラインが seq R に戻ります。
- フォーク切り替え(非アクティブブランチ checkout) — 別の seq にある既存の放棄ブランチへの切り替え。ブランチレジストリがすべてのブランチを血統で追跡します。
checkout(seq) プリミティブが両方を実装します:目標 seq がアクティブブランチにある場合はアンドゥ、放棄ブランチにある場合はフォーク切り替えです。
アクトターン巻き戻し¶
より細かい粒度として、ライブなスキールラン内でアクトターン境界(現在のターン内のステップ)まで巻き戻せます。これは Ghost-Replay メカニズムを使うランタイム専用操作です:コミット済みステップのメモが目標 seq で切り捨てられ、再起動時に目標より前のステップがゴーストとして再生(0 トークン、記録済み結果を再生)され、目標より後のステップが再実行されます。ユーザーのワークスペースファイルには影響しません。
アーキテクチャ¶
PITR スナップショットと WAL 差分再構成¶
各チェックポイントはAgentSnapshot を保存します(agent の会話状態のポイントインタイムスナップショット:インボックス、メッセージ履歴、プラン状態)。巻き戻し時:
- 目標 seq 以前のスナップショットが特定されます。
- スナップショットと目標 seq の間の WAL(
StateLog、.reyn/state/wal.jsonl)イベントが差分として適用されます。完全な履歴ではなくデルタのみを再生します。
WAL は同期耐久ログ(追記ごとに fsync)であり、P6 監査イベントログとは別物です。WAL と監査イベントの区別は Events を参照してください。
グローバル単一 seq WAL と一貫カット¶
すべての WAL イベントはグローバル単一シーケンス名前空間を共有します。seq N での一貫カット巻き戻しは明確に定義されます:「seq N+1 が書き込まれる直前のすべての基板の状態」。グローバル seq がカットを正確にします。基板ごとのクロックを調整する必要はありません。
カットはSession と Agent にまたがるプロセスグローバルでもあります:1 つの WAL があるため、単一のリセットレコードがすべてのロード済み Session と Agent を目標 seq にアトミックに移動させます。巻き戻しは 1 つの Session にスコープされません。Per-Session の粒度は永続性(スナップショットが Session ごとに再キー化)とクラッシュリカバリ再生にありますが、巻き戻し操作自体にはありません。
追記専用リセットレコードとブランチ状態¶
巻き戻しがコミットされると、リセットレコードが WAL の独自の新しい seq R(巻き戻しターゲット N とは別)に追記されます。レコードには target_n=N が記録されます。(N, R) の開区間(巻き戻しターゲットとリセットレコード自体の間に存在した seq)は放棄されます(dead-branch id=R)。アクティブな seq はその補集合です:現在のブランチチェーンのどの放棄区間にも属さないすべての seq。ブランチレジストリはリセットレコードのチェーンからブランチ状態を派生させます:各レコードの target_n と id=R が 1 つの放棄区間を定義します。
ブランチレジストリ¶
ブランチレジストリはすべてのフォーク血統を追跡します:フォーク切り替えが放棄区間から新しいブランチを作成すると、レジストリは起源 seq と新しいブランチの identity を記録します。ピッカー UI はレジストリからツリービューを描画します。
checkout プリミティブ¶
checkout(seq) はすべての巻き戻しとフォーク切り替え操作のコアプリミティブです。rewind_to は checkout に is_active ガードを追加した薄いラッパーです。
is_active_seq(seq)が true の場合:アンドゥ — 現在のブランチを seq に巻き戻します。is_active_seq(seq)が false の場合(seq が放棄区間にある):フォーク切り替え — seq の放棄ブランチをアクティブにし、現在のブランチの先端を新しい放棄区間として残します。
is_active_seq は seq ≤ tip と等価ではありません。seq が現在の先端以下でも、以前の巻き戻しから放棄区間内にある場合があります。アクティブ性は先端に対する位置からではなく、リセットレコードチェーンから派生します。
巻き戻しとフォーク切り替えの両方において、ランタイム再構成は is_active(目標ブランチの正しいフォーク血統パスを追従)を尊重します。
アクトターン Ghost-Replay¶
アクトターン巻き戻し(ターン内粒度)は SkillResumeCoordinator.plan_for_act_turn_rewind を使います:SkillResumeAnalyzer が完全な ResumePlan を構築し、committed_steps が seq ≤ target_seq でフィルタリングされます。OSRuntime.run(resume_plan=...) 経由での再起動で、メモ内のステップがゴーストとして再生(0 LLM トークン)され、カットオフを超えたステップが再実行されます。既存のクラッシュリカバリディスパッチパスを再利用するため、新しいランタイムの配線は不要です。
境界世代¶
すべてのチェックポイント境界で Reyn は AgentSnapshot — その seq でのランタイム状態(インボックス、メッセージ履歴、プラン状態)— を書き込みます。ワークスペースのコミットはありません。境界アーティファクトは単一のランタイムスナップショットです。checkout(seq) は目標 seq 以前のスナップショットを特定し、WAL イベントを前方再生して正確な目標状態に到達します。設定状態は専用の世代ストアから再構成されます(.reyn/ ディレクトリレイアウト 参照)。
WAL と監査イベントの分離¶
WAL(StateLog、.reyn/state/wal.jsonl)と P6 監査イベントログ(EventStore、.reyn/events/<run_id>.jsonl)は異なる契約を持つ別々のログです:
| WAL (StateLog) | 監査ログ (EventStore) | |
|---|---|---|
| 目的 | クラッシュリカバリとタイムトラベル再構成 | 監査証跡と再生 |
| 耐久性 | 追記ごとに fsync(同期) | ローテーションベース(追記ごとの fsync なし) |
| ライフサイクル | スナップショット後に切り詰め可能 | 追記専用、ローテーションベース |
| 統合 | 禁止 — 同期耐久性の要件が異なる | — |
2 つのログを混同・統合しないでください。詳細は Events を参照してください。
復元とスキーママイグレーション¶
restore_to_seq は復元された世代に対してスキーママイグレーションを再実行します(_open が最初のオープン時に使う同じ _migrate_columns ヘルパー経由)。これにより復元された世代が現在のスキーマになります。追加列が導入される前にスナップショットされた世代も復元時に自動アップグレードされます。「古いスキーマがスナップショットに凍結されている」リスクはありません。マイグレーションは冪等です:PRAGMA table_info ガードがすでに存在する列をスキップするため、現在スキーマの世代に再実行しても安全な no-op になります。追加的なスキーマ進化に対して設計で堅牢です。
バージョン間復元はコンストラクションで対応しています。 古い列セットで書き込まれたスナップショット(追加列の導入前にキャプチャされた世代)もクリーンに復元されます。restore_to_seq はファイルスワップ再オープン後に冪等な列マイグレーションを再実行し、復元されたデータベースを現在のスキーマに自動的にアップグレードします。同一バージョンの世代に制限はありません:冪等なマイグレーションが保証です。
コストとランタイム専用オプトアウト¶
タイムトラベルはデフォルトで有効であり、境界ごとに一定のコストがかかります。2 つの要因:
- WAL の追記ごと fsync — クラッシュで何も失わない同期耐久性(上記の WAL テーブルを参照)。
- AgentSnapshot 生成 — 各チェックポイント境界で書き込まれるランタイムスナップショット。
どちらもクラッシュリカバリとタイムトラベルの信頼性のための必要コストです。どちらもオプトアウト不可です。
クラッシュリカバリとの関係¶
| クラッシュリカバリ | タイムトラベル | |
|---|---|---|
| トリガー | 予期しない障害時に自動 | ユーザー起動(/rewind) |
| 方向 | 再開のための前方再生 | 過去のチェックポイントへの後退 |
| ワークスペース | 巻き戻しなし | 巻き戻しなし(.reyn/ 状態のみ — git 不使用) |
| ブランチング | なし | フォーク / ブランチツリー |
| メカニズム | SkillResumeAnalyzer + WAL 前方再生 |
PITR スナップショット + WAL 差分再構成、git 不使用 |