diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-06-29 02:36:40 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-06-29 02:36:40 +0900 |
| commit | 4c6a831aa2a57e2bcc20aa3fc2f1b7172f5be285 (patch) | |
| tree | 074021381abf57cad434430f25027115b681b8a9 /docs/architecture.md | |
| parent | 8d38b06f11c713e547d70468340242f23322152c (diff) | |
feat: 会話メモリのプロトタイプを実装
- 6種類のMCPツールと入力検証を追加
- バイナリログ、スナップショット、復旧、圧縮処理を実装
- Unicode n-gram検索とドメインモデルを追加
- 単体テストと統合テストを追加
- アーキテクチャと実装状況を文書化
- Cコードの記述スタイルを統一
Diffstat (limited to 'docs/architecture.md')
| -rw-r--r-- | docs/architecture.md | 363 |
1 files changed, 355 insertions, 8 deletions
diff --git a/docs/architecture.md b/docs/architecture.md index f23f06a..edf2dfa 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,9 +1,14 @@ # Architecture -`tiny-convmem-mcp` currently proves only the MCP integration boundary. The -executable composes an mcpc stdio server and registers a single `health` tool. +設計項目ごとの実装・テスト状況は +[設計・実装・テスト状況一覧](implementation-status.md)を参照してください。 -Future conversation-memory work is split into these dependencies: +`tiny-convmem-mcp` is a single-process Windows C17 MCP server backed by a +durable append-only conversation log. The executable owns the stdio dispatch +boundary and uses mcpc's bundled mjson parser through a local compatibility +layer. + +Conversation-memory work is split into these dependencies: ```text MCP transport and tools -> domain contracts <- storage adapters @@ -11,11 +16,353 @@ MCP transport and tools -> domain contracts <- storage adapters - `src/mcp` translates MCP requests and responses. It may depend on mcpc and domain contracts, but it must not contain persistence logic. -- `src/domain` will own conversation and message concepts without depending on +- `src/domain` owns conversation and message concepts without depending on mcpc or a database. -- `src/storage` will implement domain persistence contracts using a storage - engine selected in the next design phase. +- `src/storage` implements the versioned binary log, recovery, snapshot, and + compaction using Win32 file APIs. + +## Implemented prototype contract (v1) + +The six public tools are `write_message`, `read_messages`, `search_messages`, +`delete_message`, `rebuild_index`, and `get_system_health`. The detailed +"ProtoType Architecture v0.0.1" below is retained as the original proposal; +where it conflicts with this section, the implemented contract is authoritative. + +- `write_message` creates the ULID and UTC millisecond timestamp. A PUT or DEL + succeeds only after `FlushFileBuffers` succeeds. +- Deletion is immediately invisible but remains a tombstone until synchronous + compaction. `metadata.deleted` is not part of immutable Message data. +- Metadata is limited to optional `tool_call_id`, `tags`, and `memory_tier`. + Tiers are labels only; no automatic lifecycle operation is performed. +- Search uses ASCII-case-folded Unicode codepoint 2/3-grams, verifies hash + matches against the original codepoints, and ranks exact substrings first. +- A log record contains magic, format version, type, payload length, sequence, + and payload CRC32. PUT, DEL, and generation marker records are supported. +- A valid snapshot contains immutable messages and a log generation/offset. + Startup loads it only when the active log generation matches, then replays the + tail. Otherwise startup performs a full log replay. +- An incomplete final record is truncated. Unknown versions/types, invalid + lengths, non-monotonic sequences, or CRC errors within a complete record stop + startup without silently skipping data. +- `--data-dir <path>` is mandatory and guarded by a process-exclusive lock. + +The prototype remains Windows x64/MSVC and single-process. Retention, +encryption, pagination, multiple writers, automated summarization, and secure +physical erasure are outside v1. + +# ProtoType Architecture v0.0.1 +Tiny-ConvMem-MCP: FINAL TOOL INTERFACE SPECIFICATION (v1.0) + +この定義は、システムと外部(LLMプロセス)との間の「契約書」となります。これで実装に着手できますね! +No. Tool Name カテゴリ 機能概要 入力引数 (Arguments) 出力値 (Response) +1 write_message SET/WRITE 新しいメッセージイベントをログファイルへ追記し、インデックスを更新する。 session_id, role, content, timestamp status: Enum(OK, ERROR) +2 read_messages GET/READ 特定セッションIDの全メッセージを時系列順に読み出す。(フル履歴取得) session_id: 文字列 history: 配列(メッセージオブジェクト) +3 search_messages GET/READ クエリに基づいてログファイルから関連するメッセージを特定し、返す。(インデックス利用が前提) query, session_id (任意), limit: 整数 results: 配列(メッセージオブジェクト) +4 delete_message SET/MANAGE 特定のメッセージIDを指定してログから完全に削除し、インデックスをクリーンアップする。 message_id: 唯一ID, session_id (任意) status: Enum(OK, NOT_FOUND) +5 rebuild_index SET/MANAGE インデックスファイルを再生成し、データ整合性を回復させる。(メンテナンス) なし status: Enum(OK, ERROR) +6 get_system_health GET/READ システムの稼働状況とログファイルの健全性レポートを提供する。 なし report: JSON (ヘルスステータス) + + +データ構造設計:メッセージオブジェクト (Message Object Definition) +🎯 基本方針 + +全てのデータは**「不変(Immutable)」**であることを原則とします。一度記録されたイベントは変更せず、削除が必要な場合は新しいエントリで処理するか、専用の delete_message コマンドで対応します。 +📝 メッセージオブジェクト (Message) の構造定義案 + +メッセージを構成する要素を分解し、データ型を明確に定義します。(C++でのStructやJSONスキーマとして使用できます) + +{ + "message_id": "UUID/ULID文字列", // ★最重要。削除・参照のユニークキー + "session_id": "ユーザー固有のセッション識別子", + "timestamp": 1720843200000, // UTCミリ秒タイムスタンプ (UTC厳守) + "role": "ENUM (user | assistant | system)", // メッセージの発信元 + "content": "メッセージの本文(テキストデータ)", // 記録したい主要な情報 + "metadata": { // 追加情報格納用。拡張性を持たせるための辞書型 + "tool_call_id": "もしこのメッセージがツール呼び出しの結果ならIDを保持", + "is_system_message": true/false, // システム操作やプロンプト定義など + "checksum": "(オプション)データの完全性を保証するためのハッシュ値" + } +} + +⚙️ 補足的なデータ構造の定義 + +ログシステムをより堅牢にするために、メッセージとは別に必要な型も定義しておくと完璧です。 + + LogEntry (永続化層の最小単位): + 実際にディスクに書き込まれる最も小さいデータ単位。(上記 Message のバイナリ/JSON表現) + SearchIndexEntry (インデックス構造体): + 検索効率を最大化するための、非常に軽量なメタデータ。 + message_id: どのメッセージかを示すキー + session_id: 属するセッションID + keywords_hash: メッセージ内容から抽出されたキーワードのハッシュ値(またはリスト) + file_offset: ログファイル内での物理的な開始位置 + data_size: そのメッセージが占めるバイトサイズ + +📐 Tiny-ConMem-MCP: ストレージ制御 統合設計仕様 +1. アーキテクチャ概観 + +┌─────────────────────────────────────────────────────────────┐ +│ 外部プロセス (LLM Host) │ +└──────────────────────────┬──────────────────────────────────┘ + │ stdio (JSON-RPC) +┌──────────────────────────▼──────────────────────────────────┐ +│ L1: Protocol Layer (mcpc) │ +│ MCPメッセージの受信・ツールディスパッチ │ +└──────────────────────────┬──────────────────────────────────┘ + │ +┌──────────────────────────▼──────────────────────────────────┐ +│ L2: Memory Core (ビジネスロジック) │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Write Queue │ │ In-Memory │ │ Search │ │ +│ │ (保留中キュー) │ │ Index │ │ Engine │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ +└─────────┼────────────────┼────────────────┼──────────────────┘ + │ │ │ +┌─────────▼────────────────▼────────────────▼──────────────────┐ +│ L3: Persistence Layer (永続化層) │ +│ │ +│ ┌──────────┐ ┌────────────┐ ┌───────────────────┐ │ +│ │ active.log│ │snapshot.bin│ │ compaction worker │ │ +│ │(append- │ │(状態保存) │ │(ログ整理・容量削減) │ │ +│ │ only) │ └────────────┘ └───────────────────┘ │ +│ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ + +2. データ構造定義 +2.1 Message Object(永続化対象の基本単位) + +全ツールの入力・出力の中心となるデータです。一度書き込まれたら不変(Immutable)で、変更は新エントリの追記で表現します。 + +// Message (ログ1レコードの論理構造) +{ + "message_id": "ULID文字列", // 削除・参照のユニークキー + "session_id": "セッション識別子", // セッション単位のグルーピング + "timestamp": 1720843200000, // UTC ミリ秒 + "role": "user | assistant | system", + "content": "メッセージ本文", + "metadata": { + "tool_call_id": "(任意)ツール呼び出しID", + "tags": ["カレー", "設計"], //(任意)検索用タグ + "memory_tier": "short | mid | long", // 後述のメモリ階層フラグ + "deleted": false // 論理削除フラグ + } +} + +2.2 LogRecord(ディスク上の物理フォーマット) + +active.log に追記される物理レコードです。Message Object をシリアライズしたものに、検証用のメタ情報を付加します。 + +┌──────────┬──────────┬─────────────────────┬──────────┐ +│ record │ payload │ payload (serialized │ checksum │ +│ length │ type │ Message Object) │ (CRC32) │ +│ (4 byte) │ (1 byte) │ (可変長) │ (4 byte) │ +└──────────┴──────────┴─────────────────────┴──────────┘ + +payload_type: + 0x01 = PUT (メッセージの書き込み) + 0x02 = DEL (メッセージの論理削除) + 0x03 = SNAPSHOT_MARKER (snapshot境界マーカー) + +2.3 In-Memory Index(メモリ上のインデックス) + +メモリ上に保持する高速検索用の目次です。これが SearchIndexEntry の実体で、search_messages / read_messages の高速化を担います。 + +struct IndexEntry { + uint64_t file_id; // ログファイル識別子 (1 = active.log 等) + uint64_t offset; // レコードの開始バイト位置 + uint32_t length; // レコード長 + int64_t timestamp; // メッセージのタイムスタンプ + uint8_t type; // PUT / DEL + // セッションID・メッセージID は + // ハッシュテーブルの Key として使用 +}; + +// メモリ上のデータ構造(ハッシュテーブル + リスト) +HashTable<message_id, IndexEntry*> // message_id → 位置情報 +HashTable<session_id, List<message_id>> // session_id → 所属メッセージ一覧 +HashTable<keyword_hash, List<message_id>> // 検索用キーワード逆引き + +3. 書き込みパイプライン(write_message / delete_message) + +┌─────────────┐ +│ MCP Tool 呼出 │ write_message / delete_message +└──────┬──────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 1: メモリ更新 │ In-Memory Index に即座に反映 +│ │ (PUT → 追加, DEL → フラグ更新) +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 2: キュー追加 │ Write Queue に LogRecord を積む +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 3: バッチ書き込み │ flush_interval_ms (1000ms) または +│ │ max_pending_records (128件) に +│ │ 達したら write() で一括追記 +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 4: 定期 fsync │ fsync_interval_ms (5000ms) ごとに +│ │ fsync() でディスクに確実に落とす +└─────────────────────┘ + +設計パラメータ: + +#define FLUSH_INTERVAL_MS 1000 // キュー→ファイルの書き出し間隔 +#define FSYNC_INTERVAL_MS 5000 // fsync() の実行間隔 +#define MAX_PENDING_RECORDS 128 // キューの最大保留件数 + +4. 読み込みパイプライン(read_messages / search_messages) + +┌─────────────┐ +│ MCP Tool 呼出 │ read_messages / search_messages +└──────┬──────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 1: インデックス │ In-Memory Index から該当レコードの +│ 検索 │ (file_id, offset, length) を取得 +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 2: pread() │ ログファイルの該当オフセットのみ +│ 部分読み出し │ pread(fd, buf, length, offset) +│ │ ※ログ全体は読み込まない +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 3: デシリアライズ│ LogRecord → Message Object に復元 +│ │ deleted フラグを確認し、 +│ │ 削除済みなら除外 +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Step 4: レスポンス │ JSON配列として MCP 経由で返却 +│ 返却 │ +└─────────────────────┘ + +5. 起動時復旧シーケンス + +プロセス起動 + │ + ▼ +┌──────────────────┐ +│ Step 1: │ +│ snapshot.bin 読込 │ 最後のスナップショットから +│ │ In-Memory Index を復元 +└──────┬───────────┘ + │ + ▼ +┌──────────────────┐ +│ Step 2: │ snapshot 作成時点以降の +│ ログ再生 │ active.log を先頭から順に読み、 +│ │ PUT / DEL レコードを適用 +│ │ (index を更新していく) +└──────┬───────────┘ + │ + ▼ +┌──────────────────┐ +│ Step 3: │ インデックスが完全に再構築された状態で +│ サービス開始 │ stdio 受付を開始 +└──────────────────┘ + +6. Snapshot 処理(rebuild_index の一部としても動作) + +現在有効なデータ (deleted=false) を列挙 + │ + ▼ +snapshot.tmp にシリアライズして書き出し + │ + ▼ +fsync(snapshot.tmp) + │ + ▼ +rename(snapshot.tmp → snapshot.bin) + │ ← アトミック操作。クラッシュしても古い snapshot.bin が残る + ▼ +完了 + +役割: 起動時のログ再生量を減らし、起動時間を短縮する。 +7. Compaction 処理 + +トリガー: ログファイルサイズが閾値を超えた または rebuild_index 呼出 + │ + ▼ +現在有効なデータ (deleted=false) のみを列挙 + │ + ▼ +new_active.log に有効レコードのみを書き出し + │ + ▼ +fsync(new_active.log) + │ + ▼ +rename(new_active.log → active.log) + │ ← アトミック操作 + ▼ +古いログファイルを削除 + │ + ▼ +新しい snapshot.bin も生成 (compaction 後の状態を保存) + │ + ▼ +In-Memory Index の file_id / offset を更新 + +役割: ログ肥大化を防ぎ、ディスク使用量を削減する。 +8. メモリ階層とデータライフサイクル + +Message Object の metadata.memory_tier を用いて、LLM会話の寿命を管理します。 + +┌─────────────┬──────────────────────────┬──────────────────┐ +│ 階層 │ 内容 │ 保存場所 │ +├─────────────┼──────────────────────────┼──────────────────┤ +│ short (短期) │ 直近の会話メッセージ │ メモリ + active.log│ +│ │ │ (即座に参照可能) │ +├─────────────┼──────────────────────────┼──────────────────┤ +│ mid (中期) │ セッションの要約・抽象化 │ active.log + │ +│ │ │ snapshot │ +├─────────────┼──────────────────────────┼──────────────────┤ +│ long (長期) │ 重要なユーザー設定・ │ snapshot 優先で │ +│ │ 継続的記憶 (key-value) │ 永続化 │ +├─────────────┼──────────────────────────┼──────────────────┤ +│ discard │ 一時的な雑談・重複情報・ │ compaction 時に │ +│ (破棄対象) │ 古い全文ログ │ 削除対象 │ +└─────────────┴──────────────────────────┴──────────────────┘ + +ライフサイクル: -No conversation schema, retention policy, search behavior, or storage engine is -implied by the current scaffold. +write_message (memory_tier=short) + │ + │ セッション終了・要約トリガー + ▼ +要約生成 → write_message (memory_tier=mid) + │ 元の short メッセージを delete_message で論理削除 + ▼ +compaction 時に short の deleted レコードが物理削除される + │ + │ 長期的に重要と判断されたもの + ▼ +write_message (memory_tier=long) + │ snapshot 優先で永続化 + ▼ +discard レコードは compaction 時に消える +9. MCP Tool とストレージ層の対応表(最終版) +Tool L2処理 L3処理 関連コンポーネント +write_message Index更新 + Queue追加 flush時に active.log へ追記 Write Queue, In-Memory Index +read_messages session_id で Index 検索 pread() で該当レコード読み出し In-Memory Index, active.log +search_messages キーワードハッシュで Index 検索 pread() で該当レコード読み出し In-Memory Index, active.log +delete_message Index の type を DEL に更新 DEL レコードを active.log へ追記 Write Queue, In-Memory Index +rebuild_index 全有効データを再列挙 snapshot 作成 + compaction 実行 Snapshot, Compaction Worker +get_system_health 各種カウンタ収集 ログファイルサイズ・整合性チェック 全コンポーネント |
