第 2 課:ctx を知る:あらゆる能力の入口
一言で言うと:ctx は DSH におけるあらゆる能力の入口です——モデル、ツール、セッション、コマンド、サンドボックス、スキルはすべて ctx というコンテキストオブジェクトにぶら下がっています。ctx.xxx こそが DSH の API 面であり、ctx を学べば、DSH 全体を開く鍵を手に入れたも同然です。
1. ユーザーストーリー:なぜすべてのドキュメントが ctx.xxx と言うのか
D さんは昨日 dsh --profile headless "このリポジトリを要約して" で最初のタスクを動かしたばかり。今日は意気揚々と DSH のプラグインドキュメントを開いてみたところ、「最も身近な見知らぬ人」に囲まれていることに気づきました:
- ツールを追加したい?ドキュメントには
ctx.tools.register(...)とある; - モデルを変えたい?ドキュメントには
ctx.llm.registerAdapter(...)とある; - セッションを管理したい?ドキュメントには
ctx.sessionsとある; - コマンドを実行したり、サンドボックスを制限したり、スキルを載せたい?それぞれ
ctx.shell、ctx.sandbox、ctx.skillsとある。
ほぼすべてのサンプルコードが ctx で始まるのに、どのドキュメントも最初にあの最も基本的な質問に答えていません:ctx とは一体何なのか?なぜすべての能力がそこから生えてくるのか?
D さんの困惑は、彼が鈍いからではなく、ちょうど DSH の最も核心的な設計に触れてしまったからです:DSH はすべての能力を同じオブジェクトにぶら下げており、そのオブジェクトが ctx(context、コンテキスト)と呼ばれます。本課の任務は、この「入口」を徹底的に解き明かすことです——この課を終えれば、どんな ctx.xxx を見ても、それがどの種類の能力に属するか一目で分かるようになります。
🎁 たとえ話:ctx はエージェントの部屋にある「分電盤」のようなものです。照明(モデル)、コンセント(ツール)、電話(セッション)、水道管(コマンド)……すべての設備の電気はこの一つの箱から引き出されています。新しい機器を設置したければ分電盤で新しい口を探し、ある機器に電気が来ているか確認したければ、これも分電盤で調べます。
2. ctx = コンテキストオブジェクト:すべての能力がここにぶら下がる
まず、Cordis 入門ドキュメントからの一節を覚えておきましょう(出典:docs/cordis-primer.zh.md):
コンテキストはサービスのコンテナである。各サービスは安定した
ctx.<key>(例:ctx.tools、ctx.llm、ctx.sessions)を占め、他のプラグインは具体的な実装をインポートするのではなく、key でサービスを検索する。
分解して見ると、この文は三つのことを言っています:
- ctx は「コンテナ」:それ自身は仕事をしません。「サービス」を入れておくためのものです;
- 各サービスは安定した key を一つ占める:
ctx.toolsはツールレジストリ、ctx.llmはモデルアダプターレジストリ、ctx.sessionsはセッション……能力が違えば key も違い、互いに衝突しません; - key でサービスを探し、実装を import しない:プラグイン作者がある能力を使いたいとき、それが具体的にどのパッケージで実装されているかを知る必要はなく、key を宣言して直接呼び出すだけでよい——これこそ第 1 章で語った「能力は継ぎ目(シーム)」です:実装を変えてもインターフェースは変えません。
では DSH を開封したとき、ctx には実際どんなサービスがぶら下がっているのでしょうか?アーキテクチャドキュメントには実際の「能力サービス」一覧があります(出典:docs/architecture.zh.md)。そのうち数行を抜粋します:
| ctx キー | パッケージ群 | 役割 |
|---|---|---|
ctx.llm | llm/ | アダプターレジストリとモデルのストリーミング呼び出し |
ctx.tools | core/tools | ツールレジストリと実行パイプライン |
ctx.sessions | dsh-session | メモリ内のイベントソーシングセッション |
ctx.shell | shell/ | フォアグラウンドおよびバックグラウンドのコマンド実行 |
ctx.sandbox | sandbox/ | ホストのファイルシステムとカーネルを共有するように制限されたプロセス |
ctx.skills | skill/ | skill(スキル)プロバイダーレジストリと段階的開示 |
ctx.xxx 就是 DSH 的 API 面:所有能力都挂在 ctx 这个入口上
図にあるこれらは氷山の一角にすぎません——完全なリストにはさらに ctx.agents(アクティブなエージェント)、ctx.fs(ファイルシステム)、ctx.lsp(コードセマンティクス)、ctx.web(検索)、ctx.jobs(バックグラウンドタスク)、ctx.goals(目標)、ctx.workflowEngine(ワークフロー)……実に数十のサービスがあります。覚えておく結論は一つだけです:DSH では、能力と関わるときは常に「ctx 上で key を一つ取り出し、それを呼び出す」という形になります。 これが本課のタイトルの意味するところです:ctx.xxx こそが DSH の API 面です。
実際のコードはどんな姿でしょうか?これがリポジトリにある「最小ツールプラグイン」の完全な形です(出典:docs/cookbook/adding-a-tool.zh.md):
import type { Context } from 'cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const inject = ['tools'] // ① 宣言:このプラグインは ctx.tools サービスが必要
export function apply(ctx: Context) {
ctx.tools.register(defineTool({ // ② 使用:ctx.tools 上にツールを登録
name: 'read_file',
description: 'Read a file from disk.',
parameters: {
path: { type: 'string', required: true, description: 'Absolute path' },
limit: { type: 'number' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args, exec) {
return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
},
}))
}
重要なコードは二行。これを理解すれば DSH プラグインを書けるようになったも同然です:
export const inject = ['tools']:依存の宣言。プラグインが「私はctx.toolsというサービスが必要です」と言うと、Cordis はサービスの準備ができてからプラグインを起動します(第 1 課の「プラグインはサービスの可用性に基づいてアクティブ化される」に呼応);ctx.tools.register(...):能力の登録。ツールを ctx にマウントすると、schema が自動的にプロンプトの組み立てに流れ込みます——モデルはすぐにこのツールを見て、呼び出せるようになります。
登録自体は可逆的な副作用です:プラグインがアンロードされると、登録は自動的に取り消され、ツールは ctx から消え、システムは元通りになります(出典:docs/cordis-primer.zh.md:「登録は可逆的な副作用……reload や teardown の際に期待通り取り消される」)。
3. 一つの ctx に三つのもの:エフェクト + コエフェクトの統一実体
第 2 章で読んだ論文を覚えていれば、ここで「なるほど!」となる対応関係があります:ctx は適当に付けられた名前ではなく、論文中の統一コンテキスト型 Γ∞ のランタイムにおける化身なのです。
論文によれば、一つの ctx には同時に三つのものが入っています(Γ∞ ≔ μΓ. Γ × (Γ → Γ) × Σ):
| 成分 | 入っているもの | 平たく言うと | 答える問い |
|---|---|---|---|
| Γ | 現在のコンテキスト状態 | 世界は今どんな姿か | 私はどこにいる |
| Γ → Γ | 累積逆関数 | これまで何を変更し、どうやって戻るか | 私は何を変えた |
| Σ | 依存テーブル | 今どんなものが必要か | 私は何が必要か |
このうち:
- 「私は何を変えた」がエフェクト(effect):ファイルの変更、プロセスの起動、ツールの登録……ロールバック可能であることが求められます;
- 「私は何が必要か」がコエフェクト(coeffect):あるサービス、ある設定……自動的に接続されることが求められます。
論文の最も美しい一歩は、「エフェクトコンテキスト」と「コエフェクトコンテキスト」を同じ ctx 実体に統合したことです——だから DSH では常に一つの ctx とだけ付き合えばよく、それが同時に三つの問いに答えます:私はどこにいる、私は何を変えた、私は何が必要か。
この理論は Cordis コアライブラリのいくつかの実際の API として実装されています(出典:Cordis コアライブラリの簡略化された対応関係):
ctx.effect(callback) // コンテキストを変更する「唯一の入口」:自動追跡され、取り消し可能な dispose を返す
ctx.set(key, value) // コエフェクトを提供:サービス/値を ctx にマウント(内部では一回の ctx.effect)
ctx.get(key) // コエフェクトを要求:key でサービスを取得、失敗しない
つまり、隠れたイースターエッグを理解できるようになりました:第 1 課で「ロードすれば即座に有効、アンロードすれば即座に復元」と言いました——なぜ復元できるのでしょうか?サービスをマウントすること(ctx.set、ctx.tools.register)は本質的にすべて可逆的なエフェクトであり、アンロード時に逆関数に従って一つずつ取り消されるからです。正しさは開発者の注意深さに頼るのではなく、ctx という構造自体によって保証されます。論文の一節に呼応すると:正しさは「開発者の自律」から「構造的保証」へと変わったのです。
💡 数式が怖いですか?一つの文だけ覚えれば十分です:ctx = アイデンティティ(私はどこにいる) + 変更記録(私は何を変えた) + 依存リスト(私は何が必要か)、三つのものが一つの実体に入っているから、着脱はクリーンで追跡可能なのです。
4. スコープ:すべての agent に専用の agent.ctx がある
最後の重要な概念:ctx は「グローバルで一意」ではなく、すべての agent が自分自身の ctx を持っています。
アーキテクチャドキュメントの原文(出典:docs/architecture.zh.md):
すべての agent はスコープ化された
agent.ctxを所有する。共有ストレージは、そのツール、プロンプト、コマンドエントリをグローバルエントリの上に重ね合わせると同時に、ドメインごとのビューを保持する。
dsh-scope パッケージがこのすべての実装基盤です(出典:packages/core/scope/README.zh.md):
createScope(ctx, key)はタグ付きの Cordis コンテキストを作成し、その基盤となる fiber はそのコンテキストを通じて行われたすべての登録を所有する。……agent loop(エージェントループ)はライブな agent ごとにスコープを作成する。
この二つの文章は同じメカニズムを語っています。二点に分ければ分かります:
- 共有ストレージのオーバーレイ:グローバル層にはすでに基本能力(すべての agent が共有するツールやプロンプト)がマウントされており、各 agent のスコープはグローバルの上に自分のプライベートなエントリをさらに重ねます。オーバーレイ = グローバルのものも見えるし、プライベートのものも見える;
- ドメインごとのビュー:agent A が登録したものは A 自身にしか見えず、agent B の世界には存在しません——二つの agent が同時に動いても、互いに干渉せず、互いを破壊しません。
これは第 1 課の起動フローの第 ③ ステップ「スコープ化された ctx が準備完了」とぴったり呼応しています:agent loop はライブな agent ごとにスコープ付きの ctx を作成し、agent の登録は自分のスコープのライフサイクルにのみ属します——agent が終了すれば、スコープは dispose され、そのすべての登録は跡形もなく取り消されます。
🎁 たとえ話:ある会社(グローバル ctx)には共用の会議室とプリンターがあり、誰でも使えます。各部門(agent のスコープ)にはさらに自分たちのオフィスがあります——部門の壁の内側のことは、他の部門からは見えません。部門が解散すればオフィスは空になり、共用施設は影響を受けません。
ポイントの振り返り
- ctx はコンテキストオブジェクトであり、サービスのコンテナ:各能力は安定した
ctx.<key>を占め、他のコードは key でサービスを探し、実装を import しません——ctx.xxxこそが DSH の API 面です。 - すべての能力は ctx にマウントされたサービス:
ctx.llmはモデル、ctx.toolsはツール、ctx.sessionsはセッション、ctx.shellはコマンド、ctx.sandboxはサンドボックス、ctx.skillsはスキル……数十のサービスが、一つの入口に。 - ctx は「エフェクトコンテキスト + コエフェクトコンテキスト」の統一実体(Γ∞):一つの ctx に「私はどこにいる(状態)」「私は何を変えた(エフェクト、ロールバック可能)」「私は何が必要か(コエフェクト、自動接続)」が同時に入っており、コアライブラリは
ctx.effect、ctx.set、ctx.getでこれを実装しています。 - 登録すれば即座に有効、アンロードすれば即座に復元:サービスのマウントは本質的に可逆的なエフェクトであり、正しさは ctx の構造によって保証され、開発者の自律には頼りません。
- スコープ:すべての agent はスコープ化された
agent.ctxを所有します——プライベートなエントリはグローバルエントリの上に重ねられ、ドメインごとのビューは互いに隔離されています。agent が終了すればスコープは dispose され、すべての登録が取り消されます。
🚀 次課では、能力がびっしり詰まった ctx がどう動き出すかを見ます:第 3 課「エージェントループとセッション」——agent がどのように何周も感知し、考え、行動し、すべてのステップを追記専用のセッションログに書き込んでいくかを。
セルフテスト · ctx を知る
回答を終えたら「解答を送信」をクリックすると、正誤と解説を確認できます。
