コンテンツにスキップ

Present レイヤ

present レイヤは、エージェントがバルクデータを LLM 出力トークンを経由せずに ユーザへ表示するための仕組みである。大きな結果——検索ヒットの表、ファイル、構造化 API ペイロード——を得たエージェントは、データの ハンドル と宣言的な view を ユーザ向けサーフェスへ直接ルーティングする。バルクバイトはユーザに直接届き、LLM が再打鍵 することはない。

課題: バルクデータは出力トークンを消費する

エージェントが外部データを取得して表示したいとき、データは従来 LLM を2度往復していた。

  • 入力 — ツール結果が LLM コンテキストに入る。reyn はこれを既に解決済み: 大きな結果は ref ファイルに落ち、LLM はスキーマ + プレビューのみを見て、必要に応じて全値を読み戻す (ワークスペース と offload 機構を参照)。
  • 出力 — データをユーザへ見せるために、LLM は出力トークンとして再打鍵する。present レイヤが解決するのはこの軸である。500 行の再打鍵は高コストで、出力予算に収めるため LLM は 要約・切り詰めを行い——結果としてユーザ忠実度を失う。

offload された ref ファイルは既に「データファイル + ハンドル」である。欠けていたのは、その ハンドルと view をユーザのサーフェスへ直接送るプリミティブだった。それが present op である。

LLM は形を見て、ユーザは中身を見る

present レイヤを定義づける非対称性:

LLM はスキーマ + プレビューから作業しパスをバインドする。レンダラは view を LLM が一度も取り込んでいない全データに結合する。ユーザはすべてを見て、LLM は形だけを 見る。

これは欠陥ではなく設計上の契約である。表示は無料、計算は有料。 N 行の表示は出力トークン ほぼ 0。データを変換(ソート・フィルタ・質問への回答)しようとした瞬間に、エージェントは ref を読みトークンを支払う。表示を安く、変換を高くしておくことが要点である。

提示前に LLM が実際にデータを読んでいたかは OS が計算できる事実である(データはインラインか、 ref の事前読み取りがセッションに現れるか)。present レイヤはブラインド提示を禁止しない—— ブラインドを監査可能にし、監査イベントの注釈として記録する。

宣言的モデル — 決して実行可能でない

view は宣言的なコンポーネントツリーであり、コードではない。読み取り専用の固定 カタログから構成される。

コンポーネント 表示
text / markdown 文字列(markdown は CommonMark として描画)
code 言語指定付きソース(ハイライト用)
diff 統合 diff
keyvalue ラベル/値の行カード
table 行 × 列
list 箇条書き
image TUI が URL を自ら fetch し半ブロック Unicode セルとして描画する(HalfBlockImage、#4474 — 実ピクセル Kitty/Sixel 経路は試みられたが廃止された: reyn が対象とする端末上では実際には一度も描画に届いていなかった)。resolution stage を持たない surface(--cuireyn pipe)は [image: <alt>] という dim テキストのプレースホルダーにフォールバックする
artifact LLM の成果物を参照(またはinline text)で運ぶ、renderingでは運ばない node——node は何であるか(media_type)とどこに在るか(source/content)を宣言し、どう見せるかはclientの裁量(#4482)。slot契約の全体は present op リファレンス を参照

データは JSON Pointer (RFC 6901) のパスバインディングで view に結合され、{"$bind": "<pointer>"} として構造的に表現される。table / list のパスは行相対(各反復行に対する 相対)で解決される。$bind でないものはすべてリテラルである。

マークアップも HTML もコードも、LLM からレンダラへ渡ることはない。安全性はプリミティブの ——精査済みカタログ + パスバインディング——から来る。これは reyn の構造的 write-gate と同じ「構造による安全」哲学である。v1 は表示専用で、ボタン・フォーム等の 対話コンポーネントは存在しない。UI なりすまし(偽の同意ダイアログ)は対話性があって初めて 有害になるため、reyn はこのクラス全体を構造的に回避する。対話性は将来、既存の介入(同意) 経路を通す。

2つの安全レイヤ: guard と renderer 規律

葉文字列の安全性は2層に分割される。単一の「すべてエスケープ」戦略はマークアップ不活性な シンクを破壊するためである。

  • presentation-guard(サーフェス普遍、出力シーム)。 無条件で——特に未取り込みデータ に対して——すべての描画葉(ラベル・リテラル・バインド値)を1つのシームで通す。どの コンポーネントが受け取るかに関わらずサーフェスに危険な脅威を無害化する。端末では ESC / 制御シーケンスの除去(OSC / CSI——特に OSC-52 クリップボード)がその全仕事である。HTML エスケープはしない(端末で <div> は不活性なリテラルであり、エスケープは code/diff を 破壊する)し、Rich コンソールマークアップのエスケープもしない。将来の web サーフェスは HTML/JS エスケープをそのguard 戦略として、コアに触れずに差し込む。
  • renderer 規律(コンポーネント単位、描画 API 安全)。 Rich コンソールマークアップ ([red]…[/]) はサーフェス普遍の脅威ではない——特定の print 呼び出しでのみ解釈され、 不活性シンクは解釈しない。よって端末レンダラはマークアップ安全性を構造的に達成する: すべての 信頼できない葉をマークアップ不活性な Rich オブジェクトへ流し、葉の内容に対して Rich に マークアップ解釈を求めない。どこにもエスケープなしで注入は不可能になる。

バインディング単位のサイズ上限は、ルートポインタを text にバインドしてファイル全体を 吐くのを防ぐ: 上限を超えた leaf 文字列にはコンパクトな … (+N chars — full data in the ref) 末尾が付く。table/list バインディングは構造上無制限なため、present は配列バインディングも 先頭 N 行にキャップする — キャップされた table/list ノードには同種の可視 tail (…N more — full data: <ref>; インラインデータには再取得可能な ref がないため ref 節は省かれる) が付き、LLM だけでなくオペレータにも行が落とされたことが見える。ref は常に全忠実度の脱出ハッチである: フィルタや高い上限で再提示するか、直接読む。

失敗せず劣化する — 4段フォールバック

バインディングのミスがユーザのデータアクセスを失わせることはない。view 解決は4段で 劣化し、最後の段は常に描画する

  1. 登録 view — オペレータのレジストリの名前付き view。
  2. インライン blueprint — LLM が書いたコンポーネントツリー。op 検証で構造的にゲートされる。
  3. デフォルトビューア — データのから合成された blueprint(list[dict]tabledict → スカラのキーを keyvalue に、入れ子の値ごとにそれ自身のノードを追加、 スカラ → text、diff-sniff → diff)。同じ bind → guard → render 経路を通る。
  4. ジェネリック — 最終キャッチ: 構造化データは YAML として text に、プレーンテキストは そのまま。

フォールバックは全ミスの view や未知の view 名で発火する——ハードエラーには ならない。op の ack要求された view の統計に加え、実際に描画した段を示す note を報告するため、ブラインドなエージェントは数トークンで自己修正できる: 多数の path_not_found は「view がこのデータ形に合わない」、guard_stripped は「view のバグでなく guard による 無害化」、coerced エントリ(#3664: 型変換によるリシェイプ——値はユーザーに届いたため bindings_dropped には含めない)は「パスは正しいがコンポーネントが違う」と読める。

名前付き view はオペレータが設定ファイルで登録する。LLM が書くのはインライン blueprint のみである。これは reyn の write-gate 文化を反映する——耐久的で再利用可能な面は オペレータ所有である。

viewblueprint の両方を省略することも有効(FP-0055 PR-1): 解決は stage 1-2 を 完全にスキップして直接 stage 3 から始まる——present(data_ref=...) 単独で「そのまま見せる」。 これは要求されたレンダリングそのものであり、フォールバックではない: ack はデフォルトビューア 自身の統計を運び、note は付かない(stage 3 自身がさらに stage 4 へ劣化しない限り)。

監査ファースト、そして replay-as-cache

すべての提示は耐久的な presented 監査イベントを発行し、ref + 統計のみを運び、内容 バイトは決して運ばない——データは既に ref ファイルに耐久化されているため、イベントは軽量に 保たれる。イベントはデータ ref、view 名(またはインライン blueprint のハッシュ、両方 未指定なら null)、mode(view | blueprint | default)、サーフェス、OS 計算の ingested 注釈、バインディング統計を記録する。

提示はキャッシュであり、presented イベントが真実である。セッションが replay または rewind されると、presented イベントはベストエフォートで 再描画される: ref がまだ読めればその内容をデータの形から再合成し(イベントはバイトを保存して いない)、ref が消えていれば(GC 済み・利用不可)——あるいはデータがインラインで永続化 されていなければ——期限切れプレースホルダを監査イベントを指して表示する。クラッシュにも 古い描画にもならない。present は保持ウィンドウに何もピン留めしない: ref は既存のライフ サイクルを保ち、会話履歴に提示バイトが含まれないため、圧縮すべき新しいものはない。

関連