Skip to content

feat(history): split the history into topics, resumable from the list (room_log, room.rs, session.rs, config.rs, sidecar, index.html, main.ts, styles.css, docs) - #116

Merged
smileygames merged 6 commits into
mainfrom
115-topic-history
Aug 27, 2026
Merged

feat(history): split the history into topics, resumable from the list (room_log, room.rs, session.rs, config.rs, sidecar, index.html, main.ts, styles.css, docs)#116
smileygames merged 6 commits into
mainfrom
115-topic-history

Conversation

@smileygames

Copy link
Copy Markdown
Member

Closes #115

履歴をトピック単位に分ける。左の列はトピックの一覧になり、選べばそのトピックが部屋へ戻って続きが話せる。トピックは会話の記録ではなくセッションの器であり、そこに居た CLI セッションが自分の文脈を持って戻る。

入ったもの

  • 一トピック一ファイル + 索引(決定5)。logs/main/{topic}.jsonllogs/main/index.json。索引はトピック名・作成時刻・{account_id: session_uuid} を持つ。実在はディレクトリ側にあり、索引に無い .jsonl は読み出し時に拾う——これで新規トピックの遅延生成が安全になる。
  • 区切りは手動(決定1)。新規 を押して切る。起動も新しいトピックを開くが(Master 判断5)、二つは独立している。何も話さずに閉じた回は一覧に残らない。
  • 過去トピックの再開(決定2・3)。一覧から選ぶと部屋の本文へ戻る。feat(room): the room keeps no history #48 の「過去ログを #room の本文へ復元しない」はここで巻き戻り、「起動時の部屋は新規である」は残る。
  • 復帰の二段構え(決定4)。アカウントの 再開コマンド{session_id} を書くと、トピックが持つ id が差し込まれる(B)。持たない CLI にはサイドカーの read_room_history を与える(C)。履歴を PTY へ流し込む力業は採らない。
  • 復帰できない席があってもトピックは開く(決定6)。新規のセッションとして着席し、必要になったら自分で引く。resume だったかどうかは状況行で言う。
  • 既存 logs/main.jsonl の移行(決定8)。移動であり、捨てない。
  • トピック名の自動生成とその場での変更(決定9)。

触っていない一線

部屋から参加者へ過去を push しない。席を取る前の発言が届かないことは変わらず、#31 / #39 が退けた「部屋が誰の既読を持つ」形には入らない。読み出しは参加者が自分から引く pull のみである。

実装時に埋めた形

決定が形を指定していなかった箇所は issue 本文の「実装時の確定」に記録した。要点は二つ。

  • --session-id {uuid} 相当」は欄を増やさず、同じ {session_id} 置換子を起動オプションへ書く形にした。どのフラグが id を運ぶかは CLI ごとの問いであり、再開コマンド が答えているのと同じ問いである。
  • トピックへ入ると床を空にし、席を入口へ戻す。残すと、開き直したトピックで最初に発言した参加者が前のトピックの保持窓ごと「見落とし」として返される。

確認

CI と同じものをローカルで通している: npm run build / npm run sidecar:check / npm run sidecar:test / cargo check / crates/mcp-configcrates/room-floorcargo test{session_id} の置換は crates/mcp-config のテスト対象に入れ、トピック切り替え時の床の初期化は crates/room-floor にテストを足した。サイドカーのラウンドトリップテストは pull の往復を辿る。

実機での操作は未確認である。 実機で辿っていない経路は docs/0-requirements.md の「未実装」に並べた——--session-id で配った id が実際に --resume で戻るところ、エージェントが必要な場面で自分から読み出しを引くかどうか、既存ログの移行が該当する。

#114(起動時の全読み・全描画)は決定5の形で解消しているが、閉じる操作は行っていない。

Claude Lin & Lay added 5 commits August 27, 2026 19:06
部屋のログを一本の流れから「一トピック一ファイル + 索引」へ移す。トピックは会話の記録ではなくセッションの器であり、開き直せばその会話が部屋に戻る。

- `logs/{room}/{topic}.jsonl` と `logs/{room}/index.json`。索引はトピック名・作成時刻・`{account_id: session_uuid}` を持つ(決定5)。
- ディレクトリが実在であり、索引はその注釈である。索引に無い `.jsonl` は読み出し時に拾う。これにより新規トピックの遅延生成が安全になる——起動のたびに空の行が索引へ残らず、投稿がファイルへ届いて索引へ届く前に落ちても、ファイルだけから索引を建て直せる。
- 起動時は新しいトピックを開く。索引へは書かない(Master 判断5、決定5から導かれること)。
- 既存の `logs/main.jsonl` は移動して一トピックとして引き継ぐ。捨てない(決定8)。
- トピック名は最初の発言の冒頭から自動生成し、後から変更できる(決定9)。
- `history` / `history_result` フレームを追加し protocol を 6 へ。参加者は現在のトピックの過去発言を自分で引ける。pull のみであり、部屋から push する経路は作らない(決定4C)。
- トピック切り替えで床を空にし、席の `since` を戻す。前のトピックの発言を「見落とし」として返す閉路を作らないため。

#115
… resume line

トピックが持つ `{account_id: session_uuid}` を起動へ差し込む。復帰の手段は二段構えであり、ここはネイティブ resume を持つ CLI の側(決定4B)。

- アカウントに `再開コマンド` を追加。`{session_id}` を含む一行であり、先頭語が command になる(例: `claude --resume {session_id}`)。
- 「こちらが UUID を決める」側は欄を増やさず、同じ `{session_id}` を起動オプションに書く形にした。どのフラグが id を運ぶかは CLI ごとの問いであり、再開コマンドが答えているのと同じ問いである。書かなければ id を配らない——resume を持たない CLI が恒常的に置かれる状態がこれである。
- 判定は「アカウントが resume を持つか」ではなく「このトピックがこのアカウントのセッションを持つか」で行う。どちらかが欠ければ新規起動であり、失敗ではない。読み出し(決定4C)が残るため。
- 起動チェック(`reject_incompatible_flags` / `declares_settings`)は解決後の行に対して走る。アカウントの行を見て別の行を spawn する形にしない。
- `StartedSession.resumed` を画面へ返す。トピックは復帰できた席とできなかった席の両方を抱えて開く(決定6)。

#115
`say_to_room` の隣に `read_room_history` を置く(決定4C)。部屋は過去を配らないままであり、変わるのは参加者が自分で取りに行けることだけ。#31 / #39 が退けた「部屋が誰の既読を持つ」形には触れない。

- `history` / `history_result` フレームで往復する。相関は `request_id`。投稿の相関が `message_id` であるのと同じ形。
- 既定 50 件、上限 200 件。`before` に message_id を渡すとその手前が返る。トピックに上限が無い以上、一回の tool 結果へ全部載せる形は採らない。
- 押し付けない。起動時に流し込む力業は、トピックが伸びるほど毎回の起動が高くつき、CLI は流し込まれた文を自分の入力と区別できず、端末ペインにその塊が見える(#84)。
- 「発言する道具は一本」の制約は投稿の軸のものであり、この道具は投稿しない。ラウンドトリップテストでその境界を書き直した。
- protocol 6。

#115
…o the room

左の列を「過去の発言を一本の流れで並べる帯」から「トピックの一覧」へ変える。求められていた形はこちらであり、#48 が埋めた粒度が外れていた。

- 一覧は新しい順。選ぶとそのトピックが部屋へ戻り、続きを話せる(決定2)。#48 の「過去ログを `#room` の本文へ復元しない」はこの issue で巻き戻る。
- `新規` が唯一の区切りである。起動も新しいトピックを開くが、二つは独立している(決定1、Master 判断5)。
- 起動時の現在トピックは索引に無い。一覧に無くても部屋のトピックとして描く——一件だけ「まだ書き留められていないトピック」が在るのが、毎回の起動直後の正しい状態である。
- トピック名はダブルクリックでその場で直す。Enter が決定、Escape とフォーカス喪失が取消(決定9)。
- 読み戻した行は部屋の行と同じ形で描き、透かしは消す。トピックへ入ると床は空になるため、前のトピックの `message_id` を申告しても位置 0 に落ちる。等価な値ではなく本当のことを言う。
- アカウントに `再開コマンド` の欄を追加し、起動オプションの `{session_id}` を説明した。
- 起動が resume だったかどうかを状況行で言う。復帰できなかった席も着席する以上、黙って同じ言葉で済ませない(決定6)。

#115
実装と同じ PR で要求仕様と README を更新する。

- 「部屋のログ」をトピック単位へ書き換え、「トピック」の節を新設した。器としてのトピック、遅延生成、索引とディレクトリの照合、二段構えの復帰、切り替え時の床の初期化。
- 「履歴」を「トピックの一覧」へ書き換えた。#48 の「過去ログを `#room` の本文へ復元しない」がここで巻き戻ることと、「起動時の部屋は新規である」が残ることを、別々に書いた。
- 部屋プロトコルの表へ `history` / `history_result` を足し、protocol 6 の由来を記した。pull であって push ではないことをフレームの節にも書いた。
- 「再開コマンド」の節を新設し、起動オプションの `{session_id}` を説明した。
- 受容したトレードオフに三件を足した。読み戻した行が宣言色を持たないこと、置換子を起動オプションへ書かせること、索引の照合が `cargo test` の外に在ること。
- 未実装へ、実機で辿っていない経路を並べた。`--session-id` で配った id が実際に `--resume` で戻るところ、エージェントが自分から読み出しを引くかどうか、既存ログの移行。

#115
@smileygames smileygames self-assigned this Aug 27, 2026
…count

再開コマンドの分割結果が空かどうかを見ていたが、その条件は成立しない——空でない文字列を分割すると必ず一つ以上の語が出るためである。実際に通るのは引用符だけの行であり、そちらは空の語を一つ返す。空のまま command として渡せば、人が選んでいない名前で何も起動しない。

条件を「先頭の語が空か」へ移した。一つの条件で両方を覆う。文言も一行へ収めた(継続行の空白が畳まれて残っていた)。

#115

@smileygames smileygames left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自己レビュー(親 / auto モード)

brake 1 は適用外。rules/evolution/initiator-autonomy.md の self-evolution PR 定義は LI_PLUS_REPO のガバナンス面を変えることを条件②として要求し、本リポジトリは USER_REPO5 である。

merge は保留する。 auto モードに人間ゲートは無く、規約上この自己レビューの通過で merge できる。今回は親の判断で Master の確認を待つ。理由は #48 の顛末にある——本文の粒度が Master の意図と違っていたとき、実装も CI も self-review も「親が書いた本文」に照らして測るため、下流の誰も気づけなかった。この PR は同じ本文の書き手が同じ立場で書いており、同じ構造が残っている。

受け入れ基準の照合

決定 実装 結果
1 手動区切り room_new_topic。起動と独立 pass
2 再開できる 一覧から選び #room へ読み戻す pass
3 セッションの器 Topic.sessions{account_id: session_uuid}、account id keyed) pass
4B 再開コマンド 再開コマンド フィールド+{session_id} 置換(crates/mcp-config、テスト4本) pass
4C サイドカー pull read_room_historysay_to_room は唯一の発話手段のまま pass
Master 5 起動時は新規トピック RoomState::new が起動ごとに TopicRef を発行 pass
遅延生成 room_new_topic は何も書かない。索引と .jsonl は最初の投稿で生まれる pass
AI 5 ファイルの形 logs/main/{topic}.jsonllogs/main/index.json pass
AI 6 一部失敗でも開く 復帰できない席は新規着席 pass
AI 8 既存ログ移行 一トピックとして move(copy でも drop でもない) pass
AI 9 トピック名 最初の投稿の冒頭から自動生成、編集可。TITLE_CHARS は文字数単位 pass

設計として評価する点

  • 実在をディレクトリ側に置き、索引を注釈とした。 read_index が索引に無い .jsonl を拾うため、投稿がファイルに着いてから索引に着くまでのクラッシュ窓が閉じる。遅延生成の安全性がこれ一つで担保され、同じ仕組みが旧 main.jsonl の移行も運んでいる。指示していない構造で、指示より良い。
  • Floor::reset は指示に無く、かつ load-bearing。 これが無いと、再開したトピックで最初に話した人が弾かれ、前のトピックの保持窓を「見逃し」として返される——実際には読んだ発言を、である。crates/room-floor にテストがある。
  • topic_id は UUID で、タイトルとは別。 タイトルは編集されるため、名前に追随するファイル名は投稿を置き去りにする。人の入力がパスに届かない形にもなっている。
  • declares_session_id が発行を門番している。 プレースホルダを書いていないアカウントには id を発行しない。発行だけして誰も渡さない id を索引が記録する状態を防ぐ。

スコープ

逸脱なし。仕様に無い判断3件は実装側が issue 本文の ### 実装時の確定(2026-08-27、実装 AI) に記録した。指示どおりの扱い。

規約面

PR タイトル純 ASCII(非 ASCII バイト 0)。PR 本文 Closes #115、日本語。コミット6本すべてタイトル純 ASCII、本文に日本語と #115 参照。docs 同 PR(docs/0-requirements.md / README.md)。CI 全コミット緑。

mergeStateStatus は実装側の報告時点で BLOCKED だったが、現在 CLEANmain にブランチ保護は設定されておらず(gh api .../protection は 404)、必須レビューも無い。報告時点の CI 確定待ちを見たものと判断する。

持ち越し

  • #114 はこの PR の決定5で解消するが、close は Parent retains。merge 後に親が閉じる。
  • 実機未検証。 --session-id で渡した id が claude --resume で実際に戻るか、参加者が必要な場面で read_room_history を自分で引くか——どちらも観測でしか閉じない。「push しない」が「一度も pull されない」に潰れる形が残っている。docs/0-requirements.md 未実装 に記載済み。

@smileygames

Copy link
Copy Markdown
Member Author

実機確認の結果(親 / 2026-08-27)

115-topic-history0f9c673 を worktree で tauri build --debug し、実際のアプリデータに対して確認した。dev server は使わずフロント埋め込みのバイナリを直接起動している。

通ったもの

確認項目 実測
旧ログの移行(決定8) logs/main.jsonllogs/main/071faeb2-….jsonl686 bytes で一致。move であり copy でも drop でもない
自動命名(決定9) タイトル ハロ~created_at は投稿自身の時刻
タイトル不変 同トピックに後から発言してもタイトルは変わらない
起動時は新規トピック(Master 判断5) 部屋は空で起動
遅延生成 画面に 未命名 が出ている間、索引は 1 件のまま。投稿が着いた時点で 2 件目が生まれた。アプリ再起動を挟んでも空行は残らない
トピック切り替え(決定2) 過去トピックが部屋へ戻る。状況行に「トピック「ハロ~」を開きました。」
Floor::reset 再開したトピックで発言 → 弾かれない。前トピックの内容が missed として返ることもない
書き込み先 再開後の発言は正しいトピックのファイルへ(3 行)。他方は 1 行のまま
再開コマンド UI(決定4B) フィールドあり。プレースホルダ 例: claude --resume {session_id}、解決後のコマンド行がプレビューされる
{session_id} 置換 実際に起動した CLI のコマンドラインに --session-id fc7c3171-ec5a-4fca-8f61-4361fb015080
索引へのセッション記録(決定3) sessions: { "<account_id>": "<session_uuid>" }。アカウント id keyed
--resume による復帰 セッションを終了 → 再度起動で状況行が「Claude Lay を再開しました」。端末に前セッションの往復がそのまま復元され、ctx 10% を保持
文脈の実在 復帰前に預けた合言葉を部屋から尋ねたところ、復帰後のセッションが正しく回答。表示の復元ではなく文脈そのものが戻っている
部屋 ↔ channel 人間の発言が channel 経由で参加者へ届き、say_to_room で部屋へ戻る往復を確認

「トピックはセッションの器」が端まで成立している。

見つかった不具合 → #117

セッションを起動するとその時点でトピックが索引に書かれるが、発言が無ければ .jsonl は作られない。結果、索引にエントリがあって投稿ファイルが無いトピックが生まれ、タイトルが空の行として一覧に残る。

read_index は索引に無い .jsonl を拾う一方、ファイルの無い索引エントリを落とす経路が無い。片方向の整合しか無い状態。#117 として分離した。この PR の受け入れは阻害しない。

軽微な観測(issue 化せず記録のみ)

未命名 の行の日時が、実体化の瞬間に起動時刻から最初の発言時刻へ変わる。トピックは発言があって初めて存在するという設計からは筋が通るが、一覧上では日時が飛んで見える。

環境側の落とし穴(実装の不具合ではない)

Claude セッションの内側から pullcept を起動すると CLAUDE_CODE_CHILD_SESSION を継承し、子の CLI で transcript 保存が無効になる。この状態では --resume は復帰先を持たないが、エラーは出ず静かに文脈なしで起動する。確認の初回はこれで空振りし、環境変数を除去して再実行した。通常の起動経路では発生しないが、黙って失敗する経路として記録する。

判定

受け入れ基準は全て満たしている。#117 を分離した上で merge する。

@smileygames
smileygames merged commit e698ab4 into main Aug 27, 2026
2 checks passed
@smileygames
smileygames deleted the 115-topic-history branch August 27, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(history): split the history into topics, resumable from the list

1 participant