基盤となるカーネル、Cordis

CordisはKoishiコミュニティによるTypeScriptプラグインフレームワークです。DSHは独自のプラグインシステムを実装していません。このフレームワークを利用しているため、DSHプラグインはCordisプラグインとなっています。

Contextはサービスの集積所です

すべてのプラグインは、慣習的にctxと呼ばれるContextを受け取ります。サービスはctx.tools、ctx.llm、ctx.sessionsといった安定したキーの下で管理されます。ツールテーブルを必要とするプラグインは特定のキーを参照しますが、具体的な実装は参照しません。そのため、プラグイン側で気づくことなく、そのキーの背後にある実装を変更することが可能です。

context
import type { Context } from 'cordis'

// Services live on the context under stable keys.
// A plugin reaches for the key, not for a concrete class.
export function apply(ctx: Context) {
  ctx.tools     // the tool table
  ctx.sessions  // the append-only session log
  ctx.llm       // whichever model adapter is loaded
}

Serviceを実装するものはすべてプラグインです

実際には、これはapply(ctx)の本体を持つ関数、またはCordisが現在のContextにライフサイクルをマウントするServiceを継承したクラスを意味します。それ以外に、登録マニフェストや継承すべきプラグインのベースクラスは存在しません。

ロード順序は宣言されるものであり、順序付けられるものではありません

プラグインは必要なものをinjectにリストアップします。Cordisは、リストされたすべてのサービスが存在するまでそれを保留状態に保ち、その後applyを呼び出します。起動順序を手動で管理する必要はなく、依存関係よりも先に到達したプラグインは、クラッシュする代わりに単に待機します。

inject
export const inject = ['tools', 'sessions']

export function apply(ctx: Context) {
  // Cordis holds this plugin in a pending state until both
  // services exist, so neither lookup below can be undefined.
  const session = ctx.sessions.current()
  ctx.tools.list().forEach((tool) => session.note(tool.name))
}

applyの内部では、injectで指定されたすべてのものが存在することが保証されます。

副作用は可逆的です

ctx.onやctx.effectを通じて行われた登録は追跡されます。プラグインがアンロードまたはリロードされると、Cordisはその記録をたどり、それぞれを元に戻します。リスナーは削除され、サービスは解放され、タイマーはクリアされます。これにより、プラグインのホットスワップが、緩やかなメモリリークを引き起こすことなく安全に行えるようになります。

reversible effect
export function apply(ctx: Context) {
  const dispose = ctx.tools.register('read_file', async ({ path }) => {
    return readFile(path, 'utf8')
  })

  // Returning the disposer is what makes the plugin removable:
  // unloading it takes the tool back out of the table.
  return dispose
}

イベントをディスパッチする4つの方法

プラグインはサービスだけでなくイベントを通じても相互に通信し、ディスパッチモードによってリスナーが実行できることが決定されます。

emit

ファイア・アンド・フォーゲット(送信後に放置)。すべてのリスナーが実行され、何も返されません。

parallel

すべてのリスナーが並行して実行され、呼び出し元はそれらすべてを待ちます。

serial

リスナーは順に実行され、いずれかが値を返すまで実行が続けられ、その値が結果となります。

waterfall

各リスナーはnextを受け取り、引数を変換したり、処理を委譲したり、ショートサーキット(早期終了)させたりすることができます。

これがDSHでどのように現れるか

すべてのDSH機能はContext上のサービスです。リストを読むことが、ハーネスが実際に何で構成されているかを理解する最も早い方法です。

  • モデルアダプターはサービスキーを要求します。プロバイダーを切り替えるということは、同じキーに対して異なるプラグインをロードすることを意味します。
  • 各ツールは可逆的なエフェクトとしてツールテーブルに自身を登録するため、ツールを削除することはそのプラグインをアンロードすることを意味します。
  • セッションサービスは追記専用(append-only)のログを所有します。ストレージバックエンドは、その背後にある個別のプラグインです。
  • エージェントループ自体がプラグインです。そのため、MinimalやPTCのようなプリセットは、カーネルを共有しながらも大きく異なることができます。

Cordisの背後にある構成可能性(composability)モデルは、時空間構成可能性(spatiotemporal composability)に関する論文にまとめられており、DSHのリポジトリからリンクされています。