From 65a5e68035ecae3de68bb34461aca04f31b3a0fc Mon Sep 17 00:00:00 2001 From: Hare Date: Thu, 14 May 2026 04:12:40 +0900 Subject: [PATCH] =?UTF-8?q?ticket:=20=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=88=E3=82=B3=E3=83=AB=E3=81=A8=E6=B0=B8?= =?UTF-8?q?=E7=B6=9A=E5=8C=96=E3=81=AB=E3=81=8A=E3=81=91=E3=82=8B=E3=82=B7?= =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=A0=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=AE=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 1 + tickets/system-item-unify.md | 80 ++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tickets/system-item-unify.md diff --git a/TODO.md b/TODO.md index 0b538aa6..c6ac7089 100644 --- a/TODO.md +++ b/TODO.md @@ -8,6 +8,7 @@ - Pod: 任意ターンからの Fork(複数ターン巻き戻しを汎用化) → [tickets/pod-session-fork.md](tickets/pod-session-fork.md) - Pod: 子→親の TurnEnded/Errored callback を親由来ターンのみに絞る → [tickets/pod-parent-turn-callback.md](tickets/pod-parent-turn-callback.md) - Pod: セッションログをバックエンドにした Pod 単位の永続化 → [tickets/pod-persistent-state.md](tickets/pod-persistent-state.md) +- Pod: System 注入経路 (Notify / PodEvent / HookInjectedItems) を SystemItem 一本に統合 → [tickets/system-item-unify.md](tickets/system-item-unify.md) - 永続化層のセマンティック整理 → [tickets/persistence-semantics.md](tickets/persistence-semantics.md) - Exchange / Turn / Call セマンティクス整理 → [tickets/exchange-turn-call-semantics.md](tickets/exchange-turn-call-semantics.md) - llm-worker のエラー耐性 diff --git a/tickets/system-item-unify.md b/tickets/system-item-unify.md new file mode 100644 index 00000000..6b1f539a --- /dev/null +++ b/tickets/system-item-unify.md @@ -0,0 +1,80 @@ +# Event / LogEntry: System 注入経路を SystemItem 一本に統合する + +## 背景 + +エージェントシステム (= ユーザー由来でも LLM 由来でもない、Pod 自身) が LLM context に注入する `role:system` の `Item::Message` は、現状 3 系統の ad-hoc 経路で並走している: + +1. **`Method::Notify`** — 外部からの非同期メッセージ + - controller → `Event::Notify { message }` (生 message echo) + - `pod.push_notify(message)` → `NotifyBuffer` → `pending_history_appends` で `[Notification] ` の system_message として history に commit +2. **`Method::PodEvent`** — 子 pod のライフサイクル通知 + - controller → `Event::PodEvent(event)` (typed echo) + - `render_event` で 1 行整形 → `NotifyBuffer` (Notify と合流) → 同じく `[Notification] ` として commit +3. **Interceptor 内部注入** — `@` / `#` / `/` の解決結果 + - `PodInterceptor::on_prompt_submit` の `ContinueWith` で `[File: ]` / `[Knowledge: ]` / 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: ...]`) 頼みで脆い +- 新しい注入種 (`` 等) を足すたびに 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 }` +- 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` を内包 (`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 }` で wire tag は `system_items` +- `SystemItem` の kind 列挙は最低限以下を含む: + - `Notification { message }` (`Method::Notify` 由来) + - `PodEvent { event: PodEvent }` (子 pod ライフサイクル) + - `FileAttachment { path, content }` (`@` 解決) + - `Knowledge { slug, body }` (`#` 解決) + - `Workflow { slug, body }` (`/` 解決) + - 将来追加可能 (`Reminder` 等) を見越した拡張点 +- Pod 側の `NotifyBuffer` / `format_notify` / `render_event` / `Interceptor::on_prompt_submit ContinueWith` は `SystemItem` を中間表現として通る。 worker.history への append は最終的に `Item::system_message` + 対応する `SystemItem` 1 件を `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 する」 加工原則に整合する整理 (現実装の経路を統一形にするだけで、原則自体は変わらない)