前2回の記事を通して、Runtime のセマンティクスを比較的明確に整理できました。
今回は完全な Cordis も DeepSeek Harness も実装しません。目標は1つだけです。依存関係を動的に扱うシステムで最も重要なライフサイクル挙動を検証することです。
目指すべき結果は次のとおりです:
A provides db
B requires db
B provides repo
C requires repo
すべて読み込み終えると:
A ACTIVE
B ACTIVE
C ACTIVE
A をアンロードすると:
db disappears
↓
B unloads
↓
repo disappears
↓
C unloads
A を読み込み直すと:
db appears
↓
B reloads
↓
repo appears
↓
C reloads
実装は、このユニットの demo/ ディレクトリにあります。Node.js を使ってそのまま実行できます。
Runtime が保持する4つのコアオブジェクト
Demo の PluginRuntime が保持するのは次の4つです。
services
service name → provider
fibers
plugin id → Fiber
consumers
service name → Set<Fiber>
providerVersion
monotonic provider identity
各項目の内容は次のとおりです:
services
service name → provider
fibers
plugin id → Fiber
consumers
service name → Set<Fiber>
providerVersion
monotonic provider identity
実装の動きを観察しやすくするため、Demo では逆依存インデックスを明示的に保持しています。Cordis に現在ベンダリングされている実装の reflect.notify() は、Registry 内の Fiber を走査し、inject と isolation scope に基づいて絞り込みます。両者の意味は同じですが、選んだデータ構造が異なります。[1]
Fiber の状態は次のように簡略化しています:
PENDING
LOADING
ACTIVE
UNLOADING
DISPOSED
FAILED、Config、Scope、Isolation、HMR などの機能は実装していません。
Plugin 定義
各 Plugin が宣言するのは次の2つだけです:
requires は Coeffect、すなわちプラグインが実行環境に要求する依存関係です。
setup() では次を 호출します:
これにより Effect が生成されます。
この2つの部分が連携して、Plugin の動的なライフサイクルを決定します。
Effect の所有権
Context の実装はごく小さいものです:
重要なのは、provide() 自体も effect() を経由して登録される点です。
Fiber unload
↓
dispose effects
↓
remove provided service
Service のライフサイクルは、そのまま Fiber に紐づきます。
reconcile にポーリングは不要
Runtime は Service の変化を検知すると、次を呼び出します:
その Service に依存している Fiber だけを取得します:
続いて、対象 Fiber ごとに次を実行します:
Fiber は依存関係から新しい epoch を計算します:
依存関係が満たされていない場合:
epoch = null
Fiber は PENDING 状態になるはずです。
すべての依存関係がそろっている場合:
db@1|cache@2
Fiber は ACTIVE 状態になります。
Provider を置き換えたとき、Service name が変わらなくても:
db@1
↓
db@7
epoch が変わり、reload がトリガーされます。
ライフサイクル中の再変更を処理する
見落としやすい問題があります:
B 正在 UNLOADING
↓
依赖重新出现
その時点で Runtime が直接 load() を呼ぶと、同じ Plugin に対して setup と cleanup が同時に実行されてしまいます。
Demo では直列化された reconcile loop を使用します:
環境の状態が新たに変化した場合でも、設定するのは次の値だけです:
needsReconcile = true
現在のライフサイクル処理が完了してから、最新の環境に基づいて1回計算し直します。
これは Cordis が epoch + inertia で解決している問題と同じです。1つの Fiber のライフサイクル遷移は直列化する必要がありますが、対象の依存状態は遷移中にも変化し続けます。[2]
依存チェーンの伝播
Demo では次の3つの Plugin を使います:
初回インストールでは、意図的に誤った順序を使うことにします:
先頭2つの Fiber は、依存関係が満たされないため PENDING で止まります:
feature PENDING
repository PENDING
database ACTIVE
database が db を提供すると:
db changed
↓
repository reconcile
↓
repository ACTIVE
↓
repo changed
↓
feature reconcile
↓
feature ACTIVE
ロード順は依存関係から導出され、呼び出し側が手作業で保証する必要はなくなりました。
Provider のアンロード
次のように実行します:
database Fiber が db をクリーンアップします:
remove db
↓
notify repository
repository は UNLOADING に入り、自分が提供する repo もクリーンアップします:
remove repo
↓
notify feature
その後、feature もアンロードされます。
最終状態:
database DISPOSED
repository PENDING
feature PENDING
ここには見落とされがちなエッジケースがあります。
repository と feature は削除されていません。引き続きインストール済みの Plugin ですが、現在の依存環境では実行できません。
したがって、次の2つを区別する必要があります:
installed
と:
active
動的 Plugin システムでは、通常、この2つの状態を同一視することはできません。
Provider の復旧
database をもう一度インストールします:
新しい Provider には新しい version が付きます。
db@2 appears
↓
repository reload
↓
repo appears
↓
feature reload
下流の Plugin を再登録する必要はありません。Application に手動で起動させる必要もありません。
Runtime は依存関係の宣言から、アクティブなチェーン全体を復旧します。
Demo で意図的に省いたもの
実際の Cordis では、次のことも処理する必要があります:
これらの機能は、Demo が検証しようとしている中核モデルを変えるものではありません:
Service change
↓
dependent Fiber reconcile
↓
load / unload
↓
Effect apply / revert
↓
more Service changes
依存グラフ全体は、この局所的なルールを通じて、次第に新しい安定状態へ到達します。
Demo から Agent Harness へ戻る
Demo の Service 名を Agent の機能に置き換えると、
llm
tools
sessions
sandbox
agentLoop
簡略化した Harness が得られます:
LLM Provider を置き換えると:
old llm provider disappears
↓
agentLoop temporarily unloads
↓
new llm provider appears
↓
agentLoop reloads against new dependency epoch
这就是、Everything is a Plugin の成立に必要な基盤となるライフサイクル機能です。
第3ユニットは、ここまでで一続きの完全な進化経路をなしています:
次のユニットでは、Web に視点を移します。Agent の実行ライフサイクルとブラウザ接続を分離したあと、Server Runtime、SSE、Replay、Client Runtime、UI Projection をどう構成すべきかを考えます。
Demo
実行します:
cd demo
node src/demo.mjs
ファイル構成:
demo/
├── src/
│ ├── runtime.mjs
│ └── demo.mjs
└── README.md
参考資料
[1] Cordis ソース: https://github.com/deepseek-ai/deepseek-harness/tree/master/vendor/cordis/src [2] Cordis 論文: https://github.com/cordiverse/paper [3] DeepSeek Harness アーキテクチャ: https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md