Compare commits
3 Commits
a07ccb0158
...
ffd59b05a1
| Author | SHA1 | Date | |
|---|---|---|---|
| ffd59b05a1 | |||
| d2f2b7920d | |||
| 357fedc1a1 |
7
TODO.md
7
TODO.md
|
|
@ -3,6 +3,9 @@
|
|||
- Agent Skills を Workflow として ingest → [tickets/agent-skills.md](tickets/agent-skills.md)
|
||||
- パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md)
|
||||
- Pod CLI: マニフェスト関連フラグの整理 → [tickets/pod-cli-manifest-flags.md](tickets/pod-cli-manifest-flags.md)
|
||||
- Pod: 空応答ターン (Submit 後 AI 応答ゼロで Pause/Cancel) を自動巻き戻し → [tickets/pod-empty-turn-rollback.md](tickets/pod-empty-turn-rollback.md)
|
||||
- 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)
|
||||
- llm-worker のエラー耐性
|
||||
- HTTP transient リトライ → [tickets/llm-worker-transient-retry.md](tickets/llm-worker-transient-retry.md)
|
||||
- ストリーム途中失敗時の継続 → [tickets/llm-worker-stream-continuation.md](tickets/llm-worker-stream-continuation.md)
|
||||
|
|
@ -10,8 +13,10 @@
|
|||
- TUI 拡充
|
||||
- Run 中の入力キューイング → [tickets/tui-input-queue.md](tickets/tui-input-queue.md)
|
||||
- ユーザーマニフェストのモデル設定 wizard → [tickets/tui-user-model-setup.md](tickets/tui-user-model-setup.md)
|
||||
- auto-kick 由来ターンが描画されない → [tickets/tui-pod-event-render.md](tickets/tui-pod-event-render.md)
|
||||
- spawn 失敗時に Pod の stderr が TUI に表示されない → [tickets/tui-spawn-error-surface.md](tickets/tui-spawn-error-surface.md)
|
||||
- role:system の system message を TUI に表示する仕組み → [tickets/tui-system-message-render.md](tickets/tui-system-message-render.md)
|
||||
- 巻き戻されたターンの入力テキストを編集領域に復元 → [tickets/tui-empty-turn-restore.md](tickets/tui-empty-turn-restore.md)
|
||||
- ステータスライン `↑` をキャッシュ控除後の純アップロード量に → [tickets/tui-upload-tokens-net.md](tickets/tui-upload-tokens-net.md)
|
||||
- Manifest: Tool Output / File Upload 上限の分離とデフォルト緩和 → [tickets/manifest-output-upload-limits.md](tickets/manifest-output-upload-limits.md)
|
||||
- メモリ機構
|
||||
- 使用頻度メトリクス + Knowledge 化候補レポート → [tickets/memory-usage-metrics.md](tickets/memory-usage-metrics.md)
|
||||
|
|
|
|||
43
tickets/pod-empty-turn-rollback.md
Normal file
43
tickets/pod-empty-turn-rollback.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Pod: 空応答ターンの自動巻き戻し
|
||||
|
||||
## 背景
|
||||
|
||||
`Method::Run` でユーザーが Submit したあと、AI 応答が一切 history に積まれないまま Pause / Cancel されると、現状の Pod は以下の状態を確定させる:
|
||||
|
||||
- `worker.history` に user message と attachment 由来の system message が残る
|
||||
- `pod.user_segments` に当該入力が push 済み
|
||||
- `session_store` 側にも `save_user_input` の delta が commit されており、続いて `save_delta`(実体は空 or attachments のみ)/`save_turn_end`/`save_run_completed(interrupted=true)` が積まれて head_hash が前進する
|
||||
|
||||
結果として「ユーザー発話だけがあり、AI 応答ゼロ」のターンが履歴に commit され、次の Run はその上に積み増される。Submit を取り消して書き直す・別の話に切り替える、といった操作のたびにノイズが残ってしまう。
|
||||
|
||||
人間操作の TUI ではこのケースが頻発するので、Submit 前の状態に丸ごと巻き戻す仕組みがほしい。
|
||||
|
||||
## 要件
|
||||
|
||||
- Pod controller のターン処理で、`Method::Run` 起点のターンが以下を **両方** 満たす場合は Submit 直前の状態に巻き戻す:
|
||||
- 終了が Pause / Cancel 由来である(`Method::Pause` / `Method::Cancel` を受けて `WorkerError::Cancelled` で抜けた経路)
|
||||
- そのターンで `worker.history` に LLM 応答に由来する entry(assistant message / tool_use / tool_result)が **一つも** append されていない
|
||||
- 巻き戻しは Submit 起点で生まれた変更を全て取り消す:
|
||||
- `worker.history` を Submit 前の長さに truncate
|
||||
- `pod.user_segments` から push した分を pop
|
||||
- `pending_attachments` を破棄
|
||||
- `session_store` の head_hash を Submit 直前まで戻し、`save_user_input` / `save_delta` / `save_turn_end` / `save_run_completed` の commit を相殺
|
||||
- `runtime_dir.write_history` / `write_status` で永続化済みの `history.json` / `status` を同期
|
||||
- 巻き戻し成立時の最終 status は **Idle**(Resume すべき AI ターンは存在しないため Paused にしない)
|
||||
- 巻き戻しの有無はクライアントが判別できるようイベントで伝える(`Event::RunEnd` の variant 拡張、または専用イベント)。これにより TUI 側で「巻き戻されたので入力欄に戻した」等のフィードバックが組める。
|
||||
- 対象は `Method::Run` 起点のターンのみ。Notify 起点の自動 Run(`run_for_notification`)と `Method::Resume` は対象外。
|
||||
- Mid-run compaction 後の resume で LLM 応答ゼロになるケース(`do_compact_and_resume` 経路)は、Submit 前の history snapshot が依然有効である限り同様に巻き戻せる設計とする。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- TUI / pod_cli いずれの経路でも、`Method::Run` → AI 応答ゼロで Pause / Cancel すると Pod の in-memory 状態(`worker.history`, `user_segments`, `pending_attachments`, status)が Submit 前と一致する
|
||||
- 同条件で session_store / `history.json` / `status` の永続側も Submit 前と一致する
|
||||
- AI 応答が 1 件でも積まれていたターンは従来通り、巻き戻さずに Paused / Idle で確定する
|
||||
- クライアントが受け取るイベントから巻き戻しの有無が分かる
|
||||
- ユニットテストで「assistant 応答ゼロでの Cancel」「assistant 応答ゼロでの Pause」「tool_use 1 回のあとの Cancel(巻き戻さない)」「Notify 起点の Cancel(対象外)」の 4 ケースを最低限カバーする
|
||||
|
||||
## 範囲外
|
||||
|
||||
- 複数ターンに遡る undo(→ `tickets/pod-session-fork.md` で Fork として実装する計画)
|
||||
- ユーザーの明示操作で「このターンを巻き戻す」を選ばせる UI(自動条件のみ)
|
||||
- TUI 側で「巻き戻された入力を編集領域に復元する」等の UX(→ `tickets/tui-empty-turn-restore.md`)
|
||||
39
tickets/pod-parent-turn-callback.md
Normal file
39
tickets/pod-parent-turn-callback.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# 親が投げたターンのみを子→親 callback の対象にする
|
||||
|
||||
## 背景
|
||||
|
||||
子 Pod は `controller.rs` の `run_with_cancel_support` で turn 終了時に `PodEvent::TurnEnded` / `PodEvent::Errored` を `parent_socket` 宛に fire-and-forget している。現状この発火条件は「`pod_future` が `Finished` / 失敗で抜けたら」であり、その turn が何起点かを区別していない。
|
||||
|
||||
子 Pod のターンは複数の経路で開始しうる:
|
||||
|
||||
- 親からの `Method::Run`(`SpawnPod` の初回起動 / `SendToPod`)
|
||||
- 子内部での `Method::Notify` 起点の auto-kick(`run_for_notification`)。Notify の出どころは孫 Pod からの `PodEvent`、system reminder、外部 Notify など多岐にわたる
|
||||
- 将来的に増える可能性のある自走経路
|
||||
|
||||
入れ子 Pod を本格的に使い始めると、子の Notify 起点 turn が頻発する。これらが完了するたびに親へ `TurnEnded` が飛ぶと、親の `NotifyBuffer` には「自分が投げていないターンの完了」が積まれ、`pending_history_appends` で history に system message として commit され、親まで auto-kick される。親の history は「自分が把握していない子の内部活動」のノイズで埋まり、auto-kick の連鎖も意味的に正当化しづらい。
|
||||
|
||||
子→親の callback は、親が投げた仕事の消化境界を伝えるための信号に絞りたい。
|
||||
|
||||
## 要件
|
||||
|
||||
- 子 Pod が parent へ送る `PodEvent::TurnEnded` は、親由来の `Method::Run` を起点とする turn が `Finished` で完了した場合に限る。
|
||||
- 「親由来」の判定は「`Method::Run` で開始された turn」とする。SpawnPod 初回起動 / SendToPod はどちらも `Method::Run` 経由なので両方対象になる。
|
||||
- `run_for_notification` 起点の turn は完了しても `TurnEnded` を上げない。
|
||||
- `Method::Resume` 起点の turn は親由来として扱う(親が再開を指示した turn のため)。
|
||||
- `PodEvent::Errored` も同じスコープに揃える。Notify 起点 turn の worker 失敗は `parent_socket` への報告対象外とする(親が知らない指示の失敗報告になるため)。`Event::Error` でローカルに通知される経路は維持。
|
||||
- `PodEvent::ShutDown` は turn とは独立した Pod プロセス終了通知なので、本チケットの対象外(従来通り常に発火)。
|
||||
- `ScopeSubDelegated` も turn とは独立しているので対象外。
|
||||
- 親側の受信処理(`apply_event_side_effects` / `NotifyBuffer` への投入 / history への append)は変更しない。発火源を絞ることで自然にノイズが減る前提。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- 子 Pod を spawn して `SpawnPod` の初回 Run または `SendToPod` で投げた turn が `Finished` で完了すると、親の history に当該 `TurnEnded` 由来の system message が 1 件 append される。
|
||||
- 子 Pod が孫 Pod からの `PodEvent::TurnEnded`(または他の Notify)で auto-kick された turn が完了しても、親の history には何も append されない。
|
||||
- 親由来 turn が worker エラーで失敗すると親に `Errored` が届く。Notify 起点 turn の worker エラーは親に届かない。
|
||||
- ユニットテストで「`Method::Run` 完了 → 親に届く」「`run_for_notification` 完了 → 親に届かない」「`Method::Resume` 完了 → 親に届く」「Notify 起点 turn の Errored → 親に届かない」を最低限カバーする。
|
||||
|
||||
## 範囲外
|
||||
|
||||
- Pod プロセス自体の永続化/復元(別途検討)
|
||||
- `ShutDown` / `ScopeSubDelegated` の発火条件変更
|
||||
- 親が投げた個々の Run を ID で識別して相関させる仕組み(現状は「届いた / 届かない」で十分なので導入しない)
|
||||
56
tickets/pod-session-fork.md
Normal file
56
tickets/pod-session-fork.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Pod: 任意ターンからの Fork(複数ターン巻き戻し)
|
||||
|
||||
## 背景
|
||||
|
||||
`tickets/pod-empty-turn-rollback.md` は「直近 Submit が AI 応答ゼロのまま中断された」極めて狭いケースだけを自動で巻き戻す簡易フォーム。それを超える「3 ターン前から別の方針でやり直したい」「ある分岐は捨てて別ルートを試したい」といった **複数ターン巻き戻し** は、過去ターン境界からの Fork として実装する。
|
||||
|
||||
session_store には既に primitive が揃っている:
|
||||
|
||||
- `session::fork(state)` — 現状から新 session_id へ分岐(`crates/session-store/src/session.rs:400`)
|
||||
- `session::fork_at(source_id, at_hash)` — 既存セッションログ上の任意 entry hash から分岐(同 :424)
|
||||
- `SessionOrigin { session_id, at_hash }` — `SessionStart.forked_from` に出自を記録
|
||||
|
||||
未着手なのは Pod / protocol / クライアントへの露出と、ターン境界 ↔ entry hash の対応付け。
|
||||
|
||||
## 要件
|
||||
|
||||
- Pod に「現セッションから Fork して新セッションへ切り替える」操作を追加。Fork 起点はターン境界で指定する:
|
||||
- protocol に新 Method(仮: `Method::Fork { from: ForkPoint }`)を追加
|
||||
- `ForkPoint` は最低限「ターン番号」「entry hash」のいずれかで起点を指す
|
||||
- ターン番号 → entry hash の解決は Pod / session_store 側で行う(`save_turn_end` のログ entry を境界として使うのが自然)
|
||||
- Fork 後の Pod 状態:
|
||||
- 新 session_id を active に切り替え、worker.history を fork 起点までの内容で再構築
|
||||
- 元セッションは破壊されない(後から switch back 可能な前提を残す)
|
||||
- 走行中(Running / Paused)状態での Fork は拒否し、Idle 限定
|
||||
- Fork ツリーが追跡可能であること:
|
||||
- `forked_from` chain が session_store のログから辿れる(既存挙動の確認込み)
|
||||
- クライアントから「このセッションの祖先 / 子孫」を引ける API(最低限、`SessionOrigin` を読める形)
|
||||
- pod_cli / TUI からの呼び出しインターフェースの設計:
|
||||
- 本チケットで protocol 上の Method は確定させる
|
||||
- pod_cli の引数仕様もここに含める(最低限 `pod fork --turn N` 程度)
|
||||
- TUI 側の UX(ターンを選択して fork する操作)は別チケット
|
||||
- セッション切り替え後の `runtime_dir` の扱いを decide:
|
||||
- 1 つの runtime に対して active session が切り替わる形
|
||||
- セッションごとに別 runtime を持つ形
|
||||
- のどちらが今の構成と整合するか調査の上で決定
|
||||
|
||||
## 完了条件
|
||||
|
||||
- `Method::Fork` で過去ターン起点の新セッションが作成され、そこから `Method::Run` で続行できる
|
||||
- 元セッションが fork 後も独立に存在し、別 Pod プロセスから resume できる(破壊されていない)
|
||||
- Fork ツリーが session_store のログから機械的に辿れる
|
||||
- pod_cli から fork → 新セッションでの run まで通せる
|
||||
- `pod-empty-turn-rollback` の自動巻き戻しと共存(自動巻き戻しは本機能を使わずに従来通りの直接 truncate 方式で良い、と決めるならその根拠も明記)
|
||||
|
||||
## 範囲外
|
||||
|
||||
- Fork ツリーの可視化 UI(TUI / GUI)— 別チケット
|
||||
- TUI 上での「ターンを選んで fork」UX — 別チケット
|
||||
- 異なる Fork 間でのマージ
|
||||
- 過去ターンの物理削除型リベース(fork は常に非破壊)
|
||||
- 自動 GC / 古い fork の整理
|
||||
|
||||
## 依存 / 関連
|
||||
|
||||
- `tickets/pod-empty-turn-rollback.md`(直近 Submit のみの自動巻き戻し。本チケットは汎用 fork)
|
||||
- session_store の既存 `fork` / `fork_at` を流用
|
||||
35
tickets/tui-empty-turn-restore.md
Normal file
35
tickets/tui-empty-turn-restore.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# TUI: 巻き戻された入力の編集領域への復元
|
||||
|
||||
## 背景
|
||||
|
||||
`tickets/pod-empty-turn-rollback.md` で Pod 側に「Submit 後 AI 応答ゼロで Pause/Cancel した場合に Submit 前の状態へ自動巻き戻す」仕組みを入れる。Pod は巻き戻しの有無を `Event::RunEnd`(または専用イベント)で TUI に通知する。
|
||||
|
||||
TUI 側は現状 `submit_input` 時点で `input.clear()` を呼んでおり、Submit 後はテキストが失われる。Pod が巻き戻したのに TUI 側だけ「空入力欄 + ターンヘッダーが画面に残った状態」では UX が破綻する。Pod の巻き戻しに合わせて、TUI も Submit 前と同等の見た目に戻す。
|
||||
|
||||
## 要件
|
||||
|
||||
- Pod から巻き戻し通知を受け取った場合:
|
||||
- 直近 Submit でレンダリングしたブロック(`Block::TurnHeader`, `Block::UserMessage`, それに付随する `[File: ...]` 等の attachment ブロック、`Block::TurnStats` 等の Run 由来ブロック全般)を画面から取り除く
|
||||
- 入力欄に当該 Submit のテキスト(typed segments)を復元し、カーソル位置はテキスト末尾
|
||||
- `running` / `paused` / `assistant_streaming` / `current_tool` / `run_*_tokens` 等のターン状態は Idle 相当にリセット
|
||||
- 復元は 1 ターン分のみ(複数巻き戻しは Pod 側でも対象外)
|
||||
- 入力欄に未送信の文字(Run 中にユーザーが書き始めたもの、tui-input-queue が入ったらキュー内容)が既にある場合の振る舞いを決める:
|
||||
- 既入力を上書きしない方針が素直(巻き戻し復元分は別バッファに置いて「↑キーで呼び出し」等にする手もある)
|
||||
- tui-input-queue とのバッティングはチケット着手時に再確認
|
||||
- 巻き戻された旨をユーザーに 1 行のヒント(status line / トースト等)で出す。サイレントに戻すと「画面が消えて何が起きたか分からない」事故になりやすい
|
||||
|
||||
## 完了条件
|
||||
|
||||
- TUI で Submit → AI 応答が始まる前に Pause/Cancel すると、画面が Submit 前と一致し、入力欄に元のテキストが戻っている
|
||||
- AI が 1 トークンでも応答した後の Pause/Cancel では従来通り(巻き戻さず Paused / Idle)
|
||||
- 巻き戻しが起きたことがユーザーから視認できる
|
||||
|
||||
## 範囲外
|
||||
|
||||
- 複数ターン巻き戻し(→ `tickets/pod-session-fork.md` で Fork として実装する計画)
|
||||
- 巻き戻し履歴のスタック化(直前 1 件のみ復元)
|
||||
- pod_cli / 他クライアントの同等 UX(本チケットは TUI 限定)
|
||||
|
||||
## 依存
|
||||
|
||||
- `tickets/pod-empty-turn-rollback.md` の Pod 側実装 + 巻き戻し通知イベントの確定
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# TUI で Pod への外部入力 (Notify / PodEvent) が描画されない
|
||||
|
||||
## 背景
|
||||
|
||||
Pod が `Method::Notify` / `Method::PodEvent` を socket 経由で受信すると、controller は内容を notify buffer に積み、Idle なら `pod.run_for_notification()` で新しいターンを起動する(`crates/pod/src/controller.rs`)。auto-kick されたターンの assistant 出力 (`Event::TurnStart` / `TextDelta` / `TurnEnd` 等) は通常通り broadcast Event として全クライアント(TUI 含む)に配信される。
|
||||
|
||||
## 問題
|
||||
|
||||
socat で稼働中の codex-oauth pod の socket に `Method::PodEvent::TurnEnded` を 1 行流したところ、socat 側の subscribe には turn が完全に流れてきた(thinking_delta / text_done / turn_end 取得済み)が、同じ pod を起動している TUI 画面には新ターンが描画されなかった。
|
||||
|
||||
`Method::Run` 経由の通常ターンは TUI に表示されるので、broadcast 配信そのものは生きている。原因は **Pod が受信した外部入力 (`Method::Notify` / `Method::PodEvent`) が broadcast event として echo されておらず**、TUI からは「何も入力がない状態で突然ターンが始まる」ように見えていることにある。auto-kick ターンが描画されない件はこの下流症状の一つ。
|
||||
|
||||
## 要件
|
||||
|
||||
- Pod が socket で受信した外部入力のうち、活動ログとして残すべきもの (`Method::Notify` / `Method::PodEvent`) を broadcast event として全 subscriber に echo する。
|
||||
- TUI はその event を user message / assistant text と並列のログ要素として描画する。
|
||||
- auto-kick 由来ターン (`TurnStart` 以降) は既存経路で従来通り表示される。Notify / PodEvent 受信が表示されるようになれば、ターン境界の出所はログ上で自然に区別できる。
|
||||
|
||||
## 範囲外 / 非目標
|
||||
|
||||
- LLM 注入テキスト (`notify_wrapper` 適用後の wrapped string) を UI に見せるかは別判断。本チケットでは **raw メッセージをそのまま echo** する形で着地する。UI 側で wrapper を適用したくなったら、別途 catalog を引く形で対応する。
|
||||
- `starts_turn` 等の「auto-kick フラグ」を新 event に持たせない。ターン境界制御は `TurnStart` の責務であり、入力 echo event はあくまで入力ログ要素のみを表す。
|
||||
- protocol に追加する Event variant は **入力 echo の責務だけ**を持ち、UI 通知 (toast / OS 通知) を兼ねない。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- socket に `Method::Notify { message }` を流すと、全 subscriber(TUI 含む)のログにその通知本文が user / assistant と並列の要素として表示される。
|
||||
- socket に `Method::PodEvent::TurnEnded` 等を流すと、その受信を示すログ要素 + 後続 auto-kick ターンの thinking / assistant text / turn_end が、user 由来ターンと同様に TUI に表示される。
|
||||
- 追加した broadcast event は `Method::Notify` / `Method::PodEvent` の payload と一対一対応し、`starts_turn` のような派生フラグを持たない。
|
||||
47
tickets/tui-system-message-render.md
Normal file
47
tickets/tui-system-message-render.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# TUI で role:system の system message を表示する
|
||||
|
||||
## 背景
|
||||
|
||||
Pod は user 入力の `@<path>` chip / `/<slug>` chip を submit 時に展開し、`Item::system_message` を `pending_attachments` 経由で worker.history に commit している:
|
||||
|
||||
- `@<path>` ライブ submit: `Pod::run` → `resolve_file_refs` (crates/pod/src/pod.rs:825,852) → `PodFsView::resolve_file_ref` (crates/pod/src/fs_view.rs:119) で `[File: <path>]\n<body>` を生成
|
||||
- compact worker の auto-read: `mark_read_required` で nominate された再読対象が `PodFsView::render_auto_read` (crates/pod/src/fs_view.rs:84) で `[Auto-read file: <path>:<range>]\n<body>` として乗る
|
||||
- `/<slug>` ライブ submit: `Pod::run` → `resolve_workflow_invocations` (crates/pod/src/pod.rs:826,876) → `crate::workflow::resolve_workflow_invocation` (crates/pod/src/workflow/mod.rs:74) で `[Workflow /<slug>]\n<body>` と requires Knowledge 毎の `[Workflow /<slug> requires Knowledge #<req>]\n<body>` を生成
|
||||
|
||||
いずれも `PodInterceptor::on_prompt_submit` (crates/pod/src/ipc/interceptor.rs:114) で `PromptAction::ContinueWith(extras)` 経由で worker.history に commit され、history.json に永続化されている (CLAUDE.md「許される加工」原則に整合)。
|
||||
|
||||
## 問題
|
||||
|
||||
LLM のコンテキストには乗っているが TUI には何も出ない。TUI 側で role:system の Item が表示経路に乗っていないため:
|
||||
|
||||
1. **ライブ側**: 解決済み system message を運ぶ broadcast event が `protocol::Event` に存在しない (crates/protocol/src/lib.rs:204–326)。`UserMessage` で `@<path>` / `/<slug>` chip 自体は表示されるが、解決された本体は出ない。失敗時のみ `Alert` で出るが、`Alert` はユーザー向け一過性通知で永続化されない (crates/protocol/src/lib.rs:328–348) ため表示経路として不適切。
|
||||
2. **履歴復元側**: `App::restore_history` (crates/tui/src/app.rs:650–702) の match が `role: "user"` / `"assistant"` 以外を `_ => {}` で握り潰す。history.json に system role で残っているのに resume 時に消える。
|
||||
|
||||
結果として「`@<path>` や `/<slug>` を submit したのに、本当に読まれたのか / 何が context に乗ったのか TUI からは判別できない」状態になっている。
|
||||
|
||||
## 要件
|
||||
|
||||
- `Item::system_message` (role:system) を user / assistant メッセージと並列のログ要素として TUI に表示する**一般的な仕組み**を入れる。種別ごとの個別パッチではなく、role:system が来たら一律で表示経路に乗せる形にする。
|
||||
- 仕組みとして最小限カバーすべき system message:
|
||||
- `[File: <path>]` (ライブ `@<path>`)
|
||||
- `[Auto-read file: <path>:<range>]` (compact 後の auto-read)
|
||||
- `[Workflow /<slug>]`
|
||||
- `[Workflow /<slug> requires Knowledge #<req>]`
|
||||
- 表示の単一の情報源は永続化された history (= history.json の role:system Item)。ライブ submit 時 / 履歴復元時 / 別 client subscribe 時 の三経路で同じ Block バリアントを通る。
|
||||
- 表示は本文プレビュー(数行 + 残行数 / 切り詰め注記)程度で良い。`[Auto-read file: ...:<range>]` の range ラベルは可視化する。workflow 本体と requires Knowledge は同じ workflow 起動に紐づく一連と分かる粒度で識別できる。
|
||||
- 解決失敗時の従来経路は維持: `@<path>` は `Alert`、workflow は user-invocation エラーとして即座に Pod から返る (`Pod::validate_workflow_invocations`)。
|
||||
|
||||
## 範囲外 / 非目標
|
||||
|
||||
- `<system-reminder>` 注入機構そのものの汎用化や、notify_wrapper 適用後の本文表示。これらは別所(`session-todo-reminder` 等)。本チケットは**表示側の仕組みのみ**で、将来 `<system-reminder>` 系が role:system Item として history に乗るようになれば、同じ表示経路にそのまま流れる前提。
|
||||
- 表示形式の凝った装飾(シンタックスハイライト / 折り畳み UI)。最初は素のテキストプレビューで十分。
|
||||
- `model_invokation: true` のみの workflow(user_invocable=false)の表示は対象外。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- `@<path>` を submit すると、user message ブロックに続けて自動読み取り結果が TUI に出る。本文プレビューが視認できる。
|
||||
- `/<slug>` を submit すると、workflow 起動結果のログ要素が TUI に出る。`requires` がある場合は Knowledge 注入もそれと分かる形で出る。
|
||||
- compact 後の resume で `[Auto-read file: ...]` が同じログ要素として復元・表示される。
|
||||
- 別 client が後から subscribe して `Event::History` を受けた場合も、同じログ要素として描画される。
|
||||
- ライブ event と history 復元の表示が一致する(同じ Block バリアントを通る)。
|
||||
- 解決失敗時の従来経路(`Alert` / user-invocation エラー)は維持される。
|
||||
41
tickets/tui-upload-tokens-net.md
Normal file
41
tickets/tui-upload-tokens-net.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# TUI: ステータスライン `↑` をキャッシュ控除後の純アップロード量にする
|
||||
|
||||
## 背景
|
||||
|
||||
TUI ステータスラインと `Block::TurnStats` で表示している `↑{input}/↓{output}` の `↑` が、tool ループの長いターンで現実離れした大きな値になる。
|
||||
|
||||
原因は2つ重なっている:
|
||||
|
||||
1. **累積範囲**: `crates/tui/src/app.rs:469-470` で `Event::Usage` ごとに `run_input_tokens += input_tokens` を加算し、`RunEnd` で 0 リセット。1ターン内の全リクエストの input_tokens を単純合計している。
|
||||
2. **`input_tokens` の規約**: `crates/llm-worker/src/llm_client/event.rs:61-66` のコメント通り、`input_tokens` は「送信した prompt prefix 全体(cache_read + cache_creation 込み)」の占有量で、Anthropic scheme でも `convert_usage` で cache 込みに正規化済み。tool ループでは各リクエストが直前までの history 全体を prefix として送る → そのほとんどが cache hit → cache_read 分が毎リクエスト重複加算される。
|
||||
|
||||
結果として `↑` が「このターンで新たにアップロードしたトークン数」として読めず、ユーザー体験が悪い。
|
||||
|
||||
## 方針
|
||||
|
||||
**ターン内累積は維持**しつつ、各リクエストの加算値を `input_tokens - cache_read_input_tokens`(= cache_creation + 非キャッシュ入力 = 「このリクエストで full price で送った分」)に変える。
|
||||
|
||||
そのために protocol-level `Event::Usage` に cache 内訳を追加し、pod が worker UsageEvent から passthrough する。llm-worker 内部 (`UsageEvent`) と session-store (`LogEntry::LlmUsage`) には既に cache_read / cache_creation が載っているので、protocol の穴埋めだけで済む。
|
||||
|
||||
cache_creation は「このリクエストで新たにキャッシュに書いた分」で実質アップロードに含めて良い扱いとする(料金的にも full price 側)。よって表示式は `input_tokens - cache_read_input_tokens`。
|
||||
|
||||
## 要件
|
||||
|
||||
- protocol `Event::Usage` (`crates/protocol/src/lib.rs:284`) に `cache_read_input_tokens: Option<u64>`(最低限これ。cache_creation も載せるかは実装時に判断)を追加。
|
||||
- pod controller (`crates/pod/src/controller.rs:228-233`) が worker の `UsageEvent` から cache_read を passthrough する。
|
||||
- TUI 側 (`app.rs:469`, `block.rs:44-48`, `ui.rs:407-422 / 778-793`) は `input_tokens - cache_read_input_tokens` を累積・表示する。`Block::TurnStats` のフィールド意味も同方向に揃える(純アップロード量)。
|
||||
- `pod/examples/pod_protocol.rs:76` のサンプルプリントも整合させる。
|
||||
- 既存テスト・session-store 側ログ(`LogEntry::LlmUsage` は cache 内訳を既に持っているので変更不要)が壊れないこと。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- TUI で tool ループの長いターンを回しても、`↑` が同じ history を毎回再カウントせず、現実的なオーダーに収まる。
|
||||
- `Block::TurnStats` の `↑` も同じ意味(純アップロード)で揃う。
|
||||
- protocol / pod / TUI が cache_read 情報を受け渡せる経路ができている。
|
||||
|
||||
## 範囲外
|
||||
|
||||
- TUI 以外のクライアント(pod_cli 等)の表示変更。
|
||||
- 料金計算・課金表示の追加(あくまで「アップロード量」の意味付け修正)。
|
||||
- セッション全体(複数ターン)の累積表示。
|
||||
- cache hit 率のような追加指標の表示。
|
||||
Loading…
Reference in New Issue
Block a user