7.2 KiB
Event / LogEntry: System 注入経路を SystemItem 一本に統合する
背景
エージェントシステム (= ユーザー由来でも LLM 由来でもない、Pod 自身) が LLM context に注入する role:system の Item::Message は、現状 3 系統の ad-hoc 経路で並走している:
Method::Notify— 外部からの非同期メッセージ- controller →
Event::Notify { message }(生 message echo) pod.push_notify(message)→NotifyBuffer→pending_history_appendsで[Notification] <msg>の system_message として history に commit
- controller →
Method::PodEvent— 子 pod のライフサイクル通知- controller →
Event::PodEvent(event)(typed echo) render_eventで 1 行整形 →NotifyBuffer(Notify と合流) → 同じく[Notification] <rendered>として commit
- controller →
- Interceptor 内部注入 —
@<path>/#<slug>//<slug>の解決結果PodInterceptor::on_prompt_submitのContinueWithで[File: <path>]/[Knowledge: <slug>]/ workflow 本文の system_message を history に append- wire echo は無し
これらは全部 「人でも LLM でもなく、エージェントシステムが LLM に与えた情報」 という同一カテゴリで、history への commit 形 (role:system の Item::Message) もほぼ同じだが、wire event 側は echo/typed/未送信が混在し、TUI 側のブロックも Block::Notify / Block::PodEvent / Block::SystemMessage の 3 つに分かれている。
加えて LogEntry::HookInjectedItems という命名が誤称: 実際に注入しているのは公開 Hook ではなく Interceptor で、内部機構専用の経路。hook.rs モジュール doc でも 「Hook は read-only な公開 extension surface」 「内部機構は Interceptor を使う」 と明確に分離されている。
このばらつきの結果:
- wire 上、同じ通知が
Event::Notify(生) +Event::HookInjectedItems(整形版) の 2 重に流れて TUI が重複描画した (pod-state-from-session-log改修中に表面化) - kind 判別がテキストプレフィックス (
[Notification] .../[File: ...]) 頼みで脆い - 新しい注入種 (
<system-reminder>等) を足すたびに 1 系統増える設計圧力 Method::Notifyの "Notify" 語感が view-only なAlerter(本来 "Notification" 寄り) とぶつかっている
LLM は role:system を生成しないため、worker.history 中の role:system 項目は構造的にすべてこのエージェント注入経路に由来する。この性質を型として表に出す。
方針
Tool パターンに倣って 「1 つの concept + kind ベース dispatch」 に統合する。
- wire event は 1 種類:
Event::SystemItem { kind, payload }(1 件ずつライブ配信) - LogEntry は kind 揃いで batch する単一バリアントに置き換え、
Hook命名を捨てる:LogEntry::SystemItems { ts, items: Vec<SystemItem> } - Pod 内部の注入路 (NotifyBuffer /
format_notify/render_event/ Interceptor.ContinueWith) は 全部「kind 付きSystemItemを作って worker.history に commit」 という単一形式に合流 - TUI は kind 別に Block を出し分け (現
ToolCallBlockがツール別に見た目を出すのと同じ構造)
単数/複数の使い分けは既存パターンに揃える:
- 1 件単位の wire event は
Event::SystemItem(Event::TextDeltaと同じ呼吸) - 永続バッチは
LogEntry::SystemItemsでVec<SystemItem>を内包 (LogEntry::AssistantItems/ToolResultsと同じ呼吸)
Method::Notify / Method::PodEvent は外部 API としてはそのまま残す (入口の意味付けは別)。 中で SystemItem::Notification / SystemItem::PodEvent に変換されて以後は単一経路、という整理。
Event::Alert (= LLM context に乗らない純 UI 通知) は 別経路として明確に残す。 view-only な persistent stream (Alerter の subscribe_with_snapshot) としてすでに正しく機能している。 "Notification" 語感の衝突は、本チケットで context 注入側を SystemItem に rename することで解消する (Notification は SystemItem の一 kind に格下げ、Alerter が "Notification" 語感の本来のオーナーに戻る)。
要件
- wire event は 1 種類:
Event::SystemItem { kind, payload }で全注入が乗る。Event::Notify/Event::PodEvent/Event::HookInjectedItemsは protocol から削除 - LogEntry は
HookInjectedItemsを rename + items を kind 付き typed shape に置換。 新名LogEntry::SystemItems { ts, items: Vec<SystemItem> }で wire tag はsystem_items SystemItemの kind 列挙は最低限以下を含む:Notification { message }(Method::Notify由来)PodEvent { event: PodEvent }(子 pod ライフサイクル)FileAttachment { path, content }(@<path>解決)Knowledge { slug, body }(#<slug>解決)Workflow { slug, body }(/<slug>解決)- 将来追加可能 (
Reminder等) を見越した拡張点
- Pod 側の
NotifyBuffer/format_notify/render_event/Interceptor::on_prompt_submit ContinueWithはSystemItemを中間表現として通る。 worker.history への append は最終的にItem::system_message+ 対応するSystemItem1 件をLogEntry::SystemItemsとして commit - TUI は
Event::SystemItemを kind で dispatch して描画する。 既存Block::Notify/Block::PodEvent/Block::SystemMessageをBlock::SystemItem(SystemItemBlock)に集約 (or 既存 Block を再利用しつつ駆動イベントだけ統一) Method::Notify/Method::PodEvent(外部入口 API) は名前を維持し、内部でSystemItem::Notification/SystemItem::PodEventに変換されるEvent::Alert/Alerterは無変更
完了条件
Event::Notify/Event::PodEvent/Event::HookInjectedItemsが protocol から削除されているLogEntry::HookInjectedItemsが削除され、LogEntry::SystemItemsに置き換わっている (旧 wire tag を deserialize alias で残すかは実装判断)- TUI が
Event::SystemItem駆動で system 系ブロックを構築している。 ライブ通知の二重描画が起きない Method::NotifyとMethod::PodEventは外部 API としては変わらず動くEvent::Alert/Alerter経路は無変更
範囲外
Method::Notify/Method::PodEventの rename (入口名の整理は別の話)Event::Alert/Alerter系の変更- 旧 session log (
hook_injected_itemsを含む) のファイル変換: deserialize alias で読めるところまでで、ファイル書き換えは行わない - TUI 内の
Block::SystemItem詳細な視覚設計
関連
- 前提となる
tickets/pod-state-from-session-log.md(state 正本を session log に統合) の後続。 同チケット内でEvent::HookInjectedItemsを導入したが、 直後に「Hook 命名は誤り」「Notify/PodEvent と二重」と判明したため本チケットで整理する - CLAUDE.md の 「context に乗せる前に history に commit する」 加工原則に整合する整理 (現実装の経路を統一形にするだけで、原則自体は変わらない)