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(--cui、reyn 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段で 劣化し、最後の段は常に描画する。
- 登録 view — オペレータのレジストリの名前付き view。
- インライン blueprint — LLM が書いたコンポーネントツリー。op 検証で構造的にゲートされる。
- デフォルトビューア — データの形から合成された blueprint(
list[dict]→table、dict→ スカラのキーをkeyvalueに、入れ子の値ごとにそれ自身のノードを追加、 スカラ →text、diff-sniff →diff)。同じ bind → guard → render 経路を通る。 - ジェネリック — 最終キャッチ: 構造化データは 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 文化を反映する——耐久的で再利用可能な面は オペレータ所有である。
view と blueprint の両方を省略することも有効(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 は既存のライフ
サイクルを保ち、会話履歴に提示バイトが含まれないため、圧縮すべき新しいものはない。
関連¶
- リファレンス: present op & サーフェス — op 引数、
カタログ、バインディング、登録、ack、
presentedイベント、replay 注記。 - Control IR — op カタログ内の
presentop。 - Events — replay と監査ログ。
- ワークスペース — present が消費する ref と offload 機構。