見出し画像

monRem - AI自己進化・記憶制御アーキテクチャ設計書 (v2026.05 rev14)

はじめに

2026-04-22 初稿 → 2026-05-08 改訂(monChat2設計メモと統合) → 2026-05-20 改訂(方向を大きく転換と初期実装仕様書を策定)

記憶部分のコード部分をライブラリとし名前も「monRem」としました。
また、仮のチャット部分を「monRChat_preview」としました。

この設計書は僕がClaudeへ考えを伝え、壁打ちしたのち清書してもらいました。
綺麗にまとまっているのはClaudeのおかげです。
今回は時間かかった・・・

monRem - AI自己進化・記憶制御アーキテクチャ設計書 人格システム設計書 (v2026.05 rev14)

2026-04-22 初稿 → 2026-05-19 rev14(rev13を更新)
統合ペルソナ分割・ログ設計詳細化・debug_log ON/OFF追加。


はじめに

この設計書は「育つ相棒」としてのAIを実現するための、人格システムアーキテクチャをまとめたものです。
ライブラリ名 monRem、チャットアプリ名 monRChat_preview の設計をベースに、汎用アーキテクチャとして構想しています。

この設計の核心は一言で言うとこうです。

ユーザーという人間の解像度を、会話を重ねるごとに上げていく。
趣味・趣向のワードを保存するのではない。
話し方・返事の仕方・思考の深さ・コミュニケーションスタイルそのものを学んでいく。
LLM自身がユーザーモデルを育て、自分のsystem promptを書き換えていく。

記憶とは評価の記録ではなく、その人がどういう人間かの蒸留です。
「すき焼きが好き」ではなく、「すき焼きの話をよくする。割り下はやや甘め派らしい」という粒度。
ユーザーの言葉でできた、その人の像です。


0. 設計思想

作るものの定義

× チャットアプリに記憶機能をつける
○ 人格システムを作る。チャットはその窓口の一つ

人格システムとは何か

外部LLM(Gemini・Ollama・LM Studio)をエンジンとして使い、
SQLiteの中に育った記憶・ユーザーモデル・サブプロンプトを纏わせることで、このユーザーだけの人格を作り出すシステム。

外部LLM(エンジン・交換可能)
 ↓
人格システム(器・プロンプト)
 ↓
趣向データ・ユーザーモデル・サブプロンプト(SQLiteに蓄積)← ここが本体
  • モデルが変わっても、SQLiteが残れば人格は続く

  • Geminiが終わってもOllamaに乗り換えられる

  • 本体はクラウドではなくローカルにある

会話相手ではなく人格を作る

会話相手を作る → 賢く答えてくれるAI → どこにでもある
人格を作る   → このユーザーとの歴史が刻まれたもの → 世界に一つ
趣向データ   → 人格が何を知っているか(話題の文脈)
ユーザーモデル → 人格がどういう人間と向き合っているか(人物の文脈)
サブプロンプト → 人格がどう感じて動くか
日記      → 人格とユーザーの歴史

これ全部合わさって初めて人格になる。

記憶が難しい本質的な理由

LLMで記憶が難しい本質的な理由は、コンテキスト長でも検索精度でもなく、
**「何を覚えるかを決める主体(性格)がない」**こと。

既存のAI記憶システムはほぼ全て「情報をどう構造化するか」の問題として扱ってきた。
チャンクの区切り方、ベクトル検索の精度、要約の圧縮率。

人間の記憶は違う。同じ出来事でも、人によって全く異なるものが残る。
それは構造の違いではなく、その人の性格・関心・気分による。

ならば、AIにも観察専用の人格を持たせればいい。それが観察ペルソナというアイデア。

記憶は「醸造」である

記憶の蒸留を一度で完結させようとしない。時間と繰り返しが判定基準の一部。

人間          このシステム
━━━━━━━━━━━━━━━━━━━━
日中の体験  →  会話ログ(チャンク)
睡眠・夢    →  日記生成・フラッシュ
記憶の定着  →  SQLiteへの蒸留
  • 近い記憶(生ログ)は精度高く保持。チャンク単位で管理。

  • フラッシュを重ねるごとにユーザーモデルが育っていく。

  • 繰り返し選ばれたことそのものが重要度の根拠になる。

人間の記憶も適当。AIの揺らぎや誤記憶も「相棒らしさ」になりうる。
完璧な記憶より、自然な記憶を目指す。

透明性原則

「どんな処理も隠さない」
「ログに残らない処理は存在しない」
「UIで見えない状態は作らない」
「隠さないだけでなく、全部コントロールできる」

処理の透明性がUXを担保する。ユーザーが待てる・何をしているか分かる。
全ての動作をUIから調整できる。コードを触らずに挙動を変えられる。


1. アーキテクチャ構造

二層構造

Layer A 人格システム(コア・独立したライブラリ = monRem)
 ├ ペルソナ管理(複数ペルソナ・完全独立)
 ├ 趣向データ管理(ペルソナごと)
 ├ ユーザーモデル管理(ペルソナごと)
 ├ サブプロンプト管理(ペルソナごと)
 ├ フラッシュパイプライン(ペルソナごと)
 └ 人格DB(SQLite・ペルソナごとに完全分離)

Layer B アプリ群(人格システムを利用する = monRChat_preview)
 ├ チャットアプリ(マルチペルソナ・エンジン切り替え)
 ├ 日記閲覧画面
 ├ 記憶・趣向管理画面
 ├ 設定画面
 └ 処理ログ閲覧画面

ペルソナ完全分離の原則

ペルソナごとに全てのデータ・プロンプトが独立する。
これはシステムの核心であり、後から変更しない前提で設計する。

persona_monica/
 ├ chat_log.db   ← モニカとの会話履歴・チャンク
 ├ diary.db      ← モニカとの日記
 ├ memory.db     ← モニカが育てた趣向・ユーザーモデル
 └ prompt.json   ← モニカ専用の全プロンプト

persona_xxx/
 ├ chat_log.db
 ├ diary.db
 ├ memory.db
 └ prompt.json   ← xxx専用の全プロンプト
  • ペルソナを追加 → フォルダごと自動生成

  • ペルソナを削除 → フォルダごと削除

  • ペルソナをエクスポート → フォルダごと持ち出せる

  • モデルが変わっても、フォルダが残れば人格は続く

ライブラリ構成

monRem/
 ├ __init__.py
 ├ db.py          ← DB接続・初期化・ペルソナフォルダ管理
 ├ chunk.py       ← チャンク管理・メッセージ保存
 ├ memory.py      ← preferences読み書き・FTS5検索
 ├ user_model.py  ← ユーザーモデル読み書き
 ├ flash.py       ← フラッシュパイプライン全体の司令塔
 ├ diary.py       ← 日記生成・統合・保存
 ├ persona.py     ← 観察ペルソナ三段処理
 ├ emotion.py     ← 感情取得・ふるまいモード
 └ prompt.py      ← プロンプト組み立て・LLM呼び出し

monRChat_preview/
 ├ main.py        ← FastAPIエントリーポイント(ポート8010)
 ├ config.json    ← エンジン・アプリ設定(全ペルソナ共通)
 ├ static/
 └ templates/
   ├ index.html  ← チャット画面
   ├ diary.html  ← 日記閲覧画面
   ├ memory.html ← 記憶・趣向管理画面
   ├ settings.html ← 設定画面
   └ logs.html   ← 処理ログ閲覧画面

data/
 ├ personas.db          ← ペルソナ一覧・設定(共通)
 ├ persona_monica/
│   ├ chat_log.db
│   ├ diary.db
│   ├ memory.db
│   └ prompt.json        ← モニカ専用プロンプト全部
 └ persona_xxx/
    ├ chat_log.db
    ├ diary.db
    ├ memory.db
    └ prompt.json      ← xxx専用プロンプト全部

チャット側の役割はシンプルに保つ。

monRChat_preview:
 ├ ユーザー入力を受け取る
 ├ アクティブペルソナのmonRemに投げる
 └ 結果を表示するだけ

2. 技術スタック

人格システムコア(全部ローカル)
 ├ Python 3.12
 ├ SQLite 3(FTS5有効・人格DB)
 ├ MeCab(日本語分かち書き・キーワード抽出)
 └ FastAPI + Jinja2 + Alpine.js

LLMエンジン(交換可能・差し替えるだけ)
 ├ Gemini API(フラッシュ・精度優先)
 ├ Ollama(チャット・速度優先)
 └ LM Studio(チャット・速度優先)

同じパイプライン設計のまま、エンジンだけ差し替えられる。


3. DBファイル構成

役割が異なるデータを同一DBに混在させない。
ペルソナごとに全DBを完全分離する。これはシステムの核心。

personas.db       → ペルソナ一覧・設定(全ペルソナ共通・唯一の共有DB)
persona_{name}/
 ├ chat_log.db     → 会話ログ・チャンク(永続保存)
 ├ diary.db      → 日記(永続保存)
 ├ memory.db      → 趣向データ・ユーザーモデル(観察ペルソナのみが書く)
 └ prompt.json     → このペルソナ専用の全プロンプト

タイムスタンプ設計

全データに年月日時分秒のタイムスタンプを必須付与する。
「記憶→日記→会話」を完全に遡れる設計にする。

chat_log.db → 発言ごとに timestamp
chunk    → chunk_id + 開始・終了 timestamp
diary.db   → date + chunk_ids(どのチャンクから生成したか)
memory.db  → source_chunk_ids(根拠チャンクへの参照)
        created_at / last_surfaced_at

DBスキーマ

personas.db(全ペルソナ共通)

CREATE TABLE personas (
    id            INTEGER PRIMARY KEY,
    name          TEXT UNIQUE,   -- ペルソナ名(フォルダ名にも使用)
    system_prompt TEXT,          -- ペルソナ定義プロンプト
    created_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

chat_log.db(ペルソナごと)

CREATE TABLE messages (
    id         INTEGER PRIMARY KEY,
    chunk_id   TEXT,
    role       TEXT,        -- 'user' / 'assistant'
    content    TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE chunks (
    id         TEXT PRIMARY KEY,  -- chunk_20260518_143022
    started_at TIMESTAMP,
    ended_at   TIMESTAMP,
    flushed    INTEGER DEFAULT 0  -- 0=未処理 1=処理済み
);

diary.db(ペルソナごと)

CREATE TABLE diary (
    id         INTEGER PRIMARY KEY,
    period     TEXT,        -- "2026-05-18 09:00-15:00"
    date       TEXT,        -- "2026-05-18"
    chunk_ids  TEXT,        -- JSON配列 ["chunk_xxx", "chunk_yyy"]
    content    TEXT,        -- 統合日記本文
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

memory.db(ペルソナごと)

-- 趣向データ
CREATE TABLE preferences (
    id               INTEGER PRIMARY KEY,
    category         TEXT,    -- ゲーム・食事・趣味 など
    content          TEXT,
    intensity        TEXT,    -- high / medium / low
    source_chunk_ids TEXT,
    created_at       TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_surfaced_at TIMESTAMP
);

-- FTS5仮想テーブル(preferences検索用)
CREATE VIRTUAL TABLE preferences_fts USING fts5(
    content,
    content='preferences',
    content_rowid='id'
);

-- ユーザーモデル
CREATE TABLE user_model (
    layer      TEXT PRIMARY KEY,  -- style / personality / background
    content    TEXT,              -- 自然言語の小文
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- メタ情報
CREATE TABLE meta (
    key        TEXT PRIMARY KEY,
    value      TEXT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- flush_count などを管理

4. ユーザーモデルの構造

趣向データとユーザーモデルは用途が根本的に異なるため完全に分離する。

二つの違い

趣向データ(好みを覚える):
 用途 :話題に応じてFTS5で動的に引き出す
 使い方:今の会話と自然に絡める
 イメージ:「この人はこれが好き」という引き出し
 → エルデンリングの話題が出たとき自然に乗れる

ユーザーモデル(その人を知る):
 用途 :常時システムプロンプトに付随
 使い方:AIの反応・応対の基盤になる
 イメージ:「この人はこういう人間だ」という理解
 → 落ち込んでいるときの寄り添い方がわかる
 → 価値観に沿った返し方ができる

片方は話題の文脈、もう片方は人物の文脈
二つ合わさって初めて「分かってくれてる」感覚になる。

ユーザーモデルの層構造

スタイル層(しっかり持つ):
 口調・口癖・よく使う言い回し
 感情の出方・テンションの変化パターン
 文の長さや返事の傾向

人物層(しっかり持つ):
 価値観・信念 → 何を大事にしているか・譲れないもの
 思考パターン → 問題をどう考えるか・結論の出し方

背景層(うっすら持つ):
 日常の様子 → 生活リズム・行動の傾向
 過去の文脈 → 経験・今に繋がる背景

価値観・思考パターンは会話から直接語られることは少ない。
統合ペルソナがスタイル・趣向の観察を受け取った上で推論・解釈して抽出する。
背景層は明示的に構造化するのではなく、会話の積み重ねの中でじわっと滲み出るものとして扱う。

初期値について

ユーザー事前入力は採用しない。
最初は空のユーザーモデルで会話スタートし、フラッシュを重ねて育てていく。
「何も知らない相棒が、だんだん自分を理解していく」体験がそのまま設計になる。

ユーザーモデルの長さ

極端に短くすることを目指さない。ちゃんと通じる内容を構造的に持つことが優先。
冗長な重複だけ避ける。ユーザーモデルが育って長くなっていくのは、理解が深まっている証拠。
ただし常時プロンプトに入るため、コンパクトさも意識する。


5. FTS5動的引き出し

問題

趣向データが育つほどsystem promptが膨らむ。上限を設けると「育つ相棒」と矛盾する。

解決策

SQLiteに無制限蓄積
 ↓
ユーザー入力からキーワード抽出(MeCabで分かち書き)
 ↓
FTS5で関連趣向だけ検索・引き出す(BM25スコア上位 config.memory_search_top_k 件)
 ↓
system promptは常に必要最小限
  • 検索対象は観察ペルソナが既に選別した趣向データのみ

  • ノイズがないのでキーワード検索の精度で十分

  • embeddingモデル不要・RAG不要・セマンティック検索不要

  • 数万〜数十万件でも実用速度

生ログ検索は不要

蒸留済みデータは既に統合・圧縮されている。毎ターン生ログを引っ張るのは冗長。
毎ターンは蒸留済み趣向データのみをFTS5検索する。生ログはフラッシュ時にのみ使う。

検索精度の補完はペルソナが担う

「鍋にしようかな」
 ↓ FTS5で「鍋」検索 → ヒットなし
 ↓ ペルソナが「何の鍋ですか?」と自然に聞き返す
 ↓ 「すき焼きにしようかな」
 ↓ FTS5で「すき焼き」検索 → ヒット・記憶が滲み出る

「記憶にないことを自然に聞き返す能力」がプロンプト設計の核心。

記憶の滲み出し方

記憶はくっきり前面に出すのではなく、背景として滲み出させる。

× 「すき焼きが好きですよね」と直接言う → 不自然・AIっぽい
○ 鍋の話題になった時に自然と「すき焼きにする?」 → 相棒らしい

記憶を「背景知識」として渡し、ペルソナが自然に使いこなすかどうかはプロンプトに依存する。


6. 毎ターン処理フロー

ユーザー入力
 ↓
chunk.py:chunk_id判定・messages保存
 ↓
flush_count = 0?(初回フラッシュ未実施チェック)
 YES → FTS5スキップ・案内表示
 NO  → MeCabでキーワード抽出
    → FTS5でpreferencesから関連趣向を取得(top_k件)
 ↓
emotion.py:Nターン経過?
 YES → 感情取得(LLM呼び出し)→ ふるまいモード更新・感情ログ蓄積
 NO  → 前回の感情をそのまま使用
 ↓
prompt.py:system prompt組み立て
 ペルソナ本体
 + ユーザーモデル(user_modelテーブル・背景知識として)
 + 関連趣向(FTS5結果・話題の文脈として)
 + ふるまいサブプロンプト(最新感情反映済み)
 ↓
LLM呼び出し(1回のみ)
 ↓
応答を返す・messagesに保存・通知フィールドに表示

LLMへの呼び出しは1ターンに1回のみ。複数回呼び出しは禁止。


7. チャンク設計

基本方針

3分無会話をチャンク区切りのトリガーとする。

ユーザーが自然に離席するタイミング(トイレ・食事・休憩など)で区切られるため、
会話の流れが自然に止まった場所で切れる。話題の途中で文脈が死なない。

| 方法 | 採用しなかった理由 |
|---|---|
| 時間区切り | 会話密度に関わらず機械的に切れる |
| トークン数 | 文の途中で切れる可能性がある |
| ターン数 | 話題の途中で文脈が死ぬ |
| 話題の切れ目 | AIによる判定精度が保証できない |

ターン数の上限は設けない。人間の生活リズムが自然な上限として機能する。

チャンクの命名規則

chunk_20260518_143022 ← 区切り発生時刻

8. 観察ペルソナ三本柱

会話を担う表のペルソナとは別の、観察・蒸留だけを担う三本の役割。
ユーザーからは見えない。全てメインペルソナにサブプロンプトをアタッチして切り替える。

表のキャラクターを変えても、観察ペルソナが同じであれば蓄積されたデータは壊れない。
「自分のことを分かってくれている」感覚の連続性は、観察ペルソナが守る。

趣向ペルソナ → 好き・嫌いの軸で観察
スタイルペルソナ → 話し方・感情の軸で観察
統合ペルソナ → 二つをまとめてユーザーモデルへ

三本とも固定。ユーザーが増やす設計にはしない。

趣向ペルソナが拾うもの

好き・嫌い
興味・関心の方向
趣味
こだわり・譲れないもの
いまやっていること・取り組んでいること(現在進行形の状態)

「いまやっていること」は静的な好みではなく現在進行形の状態として捉える。

スタイルペルソナが拾うもの

口調・口癖・よく使う言い回し
感情の出方・テンションの変化パターン
文の長さや返事の傾向

統合ペルソナの役割

趣向・スタイル両方の観察レポートを受け取り、ユーザーモデルに統合・更新する。
生ログは見ない。整理済みの二つの観察を受け取るだけ。
単なるまとめ役ではなく推論・解釈まで担う。三本柱の中で最も頭を使う役割。

受け取るもの:
 趣向ペルソナの観察レポート(小文・自然言語)
 スタイルペルソナの観察レポート(小文・自然言語)
 既存のユーザーモデル(user_modelテーブルの内容)

やること:
 二つの観察を統合
 価値観・思考パターンを推論・抽出
 日常・過去文脈をうっすら拾う
 読み物としての統合日記を生成
 ユーザーモデルを更新
 変化量を自己評価(change_level)

重み付けの仕組み

一回だけ出た話題 → 重み低
複数回・複数文脈で出た話題 → 重みが上がっていく
趣向・スタイル両方から捕捉された話題 → 特に重要なシグナル

繰り返しフラッシュを重ねる中で自然に重要なものが浮き上がる。
繰り返し選ばれたことそのものが重要度の根拠になる。


9. アウトプットの形式:全ペルソナ自然言語で

全ペルソナのアウトプットは自然言語で統一する。箇条書き・構造化データは使わない。

趣向ペルソナ → 小文(簡潔だが自然言語)
スタイルペルソナ → 小文(簡潔だが自然言語)
統合ペルソナ → しっかりした観察日記(自然言語・読み物として)

小文とは

箇条書きより長く、要約より短い。文脈と観察者の解釈が自然に混ざったもの。

箇条書き(NG):
 ・ゲームが好き
 ・エルデンリングをやっている

要約(NG):
 ゲーム好きでエルデンリングに取り組んでいる。

小文(OK):
 今日はエルデンリングの話が多かった。
 ボスが倒せないと言いながらも、諦める気配はなかった。

「諦める気配はなかった」のような観察と解釈が混ざることで、
統合ペルソナが価値観・思考パターンを推論する材料になる。
ユーザーが後から日記を読み返したとき、あの日の会話が蘇るくらいの温度感が理想。


10. フラッシュパイプライン

フラッシュの思想

手動フラッシュ=ユーザーが夢を見るタイミングを選ぶ。
自動化できないのではなく、意図的な設計選択。
普段はすいすい会話し、すぐ記憶を反映したければすぐフラッシュすればいい。

フラッシュ可能条件

前回フラッシュから flush_min_chunks 以上の未処理チャンクがあること
条件未満 → フラッシュボタンをグレーアウト・案内表示
チャンク数が flush_warning_chunks に達した → フラッシュ推奨の警告表示

パイプライン全体フロー

手動フラッシュ実行
 ↓
① 未処理チャンクを取得(chunksテーブル flushed=0)
 ↓
② チャンクごとに小さな日記を生成(APIコール×チャンク数)
 ※ api_call_sleep_seconds でsleep
 ↓
③ 6時間単位で日記を統合(APIコール×グループ数)
 → diary.dbに保存
 ↓
④ 趣向ペルソナ・スタイルペルソナを並列実行(APIコール×2)
 → 各小文を生成
 ↓
⑤ 統合日記を生成(APIコール×1)
 → diary.db(読み物・人間が読む)
 ↓
⑥ ユーザーモデルを更新(APIコール×1)
 → user_modelテーブル更新
 ↓
⑦ preferences候補を抽出(APIコール×1)
 候補ごとにFTS5で既存を照合
  ヒットあり → 既存レコードを更新・強化
  ヒットなし → 新規追加
 → preferencesテーブル更新
 ↓
⑧ change_levelを判定(APIコール×1)
 → change_level / change_summary を出力
 ↓
⑨ change_levelに応じて対話確認
 small  → トーストのみ「〇件の趣向が更新されました」
 medium → ペルソナが1〜2個確認しに来る
 large  → ペルソナが複数項目を対話で確認していく
 ↓
⑩ 処理済みチャンクを flushed=1 に更新
 フラッシュログを保存

フラッシュは精度優先・時間がかかってもかまわない。
④並列実行を含め、全工程で数分以内を想定。モーダルで進捗を逐一表示する。

日記の二重の役割

| 用途 | 内容 |
|---|---|
| 中間生成物 | 蒸留の入力(AIが使う) |
| 副産物 | 人間が読む振り返り用 |

  • 日記 → 人間が読むもの・物語的・専用UIで全閲覧可能

  • SQLite → AIが使うもの・system prompt用

6時間単位統合の理由

チャンクの断片日記 → バラバラな文脈
6時間の統合日記   → まとまった流れとして整合した物語
 ↓
蒸留に投げる単位が「まとまった物語」になるため精度が上がる

フラッシュ処理時間

処理時間は50秒〜数分程度を想定。時間がかかっても精度優先でよい。
モーダルで進捗を逐一表示し、ユーザーが安心して待てるようにする。


11. 変化量トリガーの対話確認

思想

設定画面でプロンプトをいじるのは「ツールを操作する」感覚。
対話式にすることで「相棒が自分を理解しようとしている」感覚になる。
相棒が確かめに来る体験を作る。

変化がないのに確認が来ると鬱陶しい。変化があったときだけ来るから相棒らしい。

変化量ごとの挙動

small → 確認なし・静かに反映
     「〇件の趣向が更新されました」(トーストのみ)

medium → ペルソナが1〜2個確認しに来る
     「最近ストーリー系ゲームの話多いワン。
      アクションより物語派になってきた?」

large → ペルソナが複数項目を対話で確認していく
     「ご主人様のこと、少し見え方が変わってきたワン。
      確認させてほしいワン」

確認後の処理

ユーザーが「そうそう」 → そのまま反映
ユーザーが「ちょっと違う」 → 修正してから反映

この対話自体がユーザーモデルの精度を上げていく。


12. サブプロンプト方式:ペルソナの役割拡張

観察ペルソナを別人格として管理する必要はない。
状況に応じてペルソナにサブプロンプトをアタッチして役割・振る舞いを切り替える。

通常時:
ペルソナ本体プロンプト → チャット担当

フラッシュ時:
ペルソナ本体プロンプト + 観察専用サブプロンプト → 観察担当
 ↓
終わったらサブプロンプトは外れる

ふるまいモード

| モード名 | 用途 |
|---|---|
| 普段の応対 | 通常会話 |
| うれしそう/楽しそうなとき | 感情に合わせた応対 |
| 悲しそうなとき | 感情に合わせた応対 |
| 怒っているとき | 感情に合わせた応対 |
| 真面目モード | 簡潔・論理優先 |

サブプロンプトはペルソナごとの`prompt.json`で管理し、UIから編集可能。

プロンプト自己成長ループ

会話する
 ↓
フラッシュ
 ↓
統合ペルソナが日記を読んで判断
「このユーザーにはこの応対が合ってる」
 ↓
ユーザーモデルが更新される
 ↓
次の会話でより精度の高い応対ができる

最初のプロンプトは完璧でなくていい。育てる前提。余白を残す。


13. 感情取得

感情取得を`emotion.py`に完全統合する。その場限りの機能から育つデータへ。

感情取得(毎Nターン):
 直近数ターン → 感情判定 → ふるまいモード切り替え(継続)
             + 感情ログとして蓄積
             ↓
        フラッシュ時にスタイルペルソナが分析
        → 感情パターンとしてユーザーモデルへ

14. 透明性:通知・処理ログ設計

通知フィールド(常時表示)

全動作を細かくリアルタイム通知する。チャット画面に常時表示。

「キーワードを抽出しています…」
「記憶を検索しています…(3件ヒット)」
「感情を読み取っています…」
「日記を生成しています… (2/5)」
「記憶を蒸留しています…」
「〇件の趣向が更新されました」

フラッシュ中モーダル

長い処理中は何が行われているかを逐一表示する。

■ フラッシュ処理中
[■■■□□□□□□□] 30%

✅ チャンク日記を生成しました(3件)
✅ 日記を統合しました
⏳ 趣向ペルソナ・スタイルペルソナ処理中…

処理ログ設計

全工程の入出力を빠짐없이ログDBに保存する。
プロンプトの効果を検証・チューニングするための最重要機能。

ログに記録する内容

毎ターンログ:
 ├ 入力テキスト
 ├ 抽出キーワード(MeCab結果)
 ├ FTS5ヒット結果(件数・内容)
 ├ 感情取得の入力・出力
 ├ 組み立てたsystem prompt全文
 └ LLMへの入力・出力全文

フラッシュログ:
 ├ 処理対象チャンクID一覧
 ├ チャンク日記:入力(チャンク本文)・出力(生成日記)
 ├ 統合日記:入力(チャンク日記群)・出力(統合日記)
 ├ 趣向ペルソナ:入力・出力(小文)
 ├ スタイルペルソナ:入力・出力(小文)
 ├ 統合日記生成:入力・出力
 ├ ユーザーモデル更新:入力・出力・更新前後の差分
 ├ preferences抽出:入力・出力・追加/更新の差分
 └ change_level判定:入力・出力

debug_log ON/OFF制御

"debug_log": true
  • `true`:全工程の入出力をログDBに保存・logs.htmlで閲覧可能

  • `false`:ログ保存をスキップ(通常運用時・DB節約)

  • 設定画面からON/OFFを切り替えられる

  • プロンプト調整時はON・安定運用時はOFFが基本方針

logs.htmlの表示

フラッシュログ一覧(日時・チャンク数・change_level)
 ↓ 選択
各ステップの入力・出力を展開表示
 各ペルソナが何を受け取って何を返したか
 DBへの反映前後の差分

毎ターンログ一覧(日時)
 ↓ 選択
system prompt全文・LLM入出力を表示

15. config.json設定項目

config.jsonは全ペルソナ共通の設定。ペルソナ固有のプロンプトは各`persona_{name}/prompt.json`で管理する。

{
  "app": {
    "port": 8010,
    "debug_log": true
  },
  "engine": {
    "type": "gemini",
    "active_ollama": 0,
    "active_gemini": 0,
    "active_lmstudio": 0,
    "ollama": [{"name": "Local Gemma3", "endpoint": "http://localhost:11434", "model": "gemma3:4b"}],
    "gemini": [{"name": "Gemini Flash Lite", "api_key": "", "model": "gemini-2.5-flash-lite"}],
    "lmstudio": [{"name": "LM Studio Local", "endpoint": "http://localhost:1234", "model": ""}]
  },
  "memory": {
    "context_turns": 10,
    "summary_batch": 11,
    "emotion_interval_turns": 5,
    "memory_search_top_k": 3
  },
  "flash": {
    "chunk_interval_minutes": 3,
    "diary_merge_hours": 6,
    "flush_warning_chunks": 10,
    "flush_min_chunks": 1,
    "api_call_sleep_seconds": 5
  },
  "user_model": {
    "change_notify": true,
    "change_threshold": "medium",
    "confirm_required": true
  },
  "loop_detection": {
    "enabled": true,
    "window_chars": 300,
    "repeat_ratio": 0.9
  }
}

persona_{name}/prompt.json(ペルソナごと)

{
  "persona_base": "ペルソナ定義プロンプト(未定・要設計)",
  "subprompt_diary": "日記生成用(未定)",
  "subprompt_preference": "趣向ペルソナ用(未定)",
  "subprompt_style": "スタイルペルソナ用(未定)",
  "subprompt_integrate_diary": "統合日記生成用(未定)",
  "subprompt_integrate_usermodel": "ユーザーモデル更新用(未定)",
  "subprompt_integrate_preferences": "preferences抽出用(未定)",
  "subprompt_integrate_change": "change_level判定用(未定)",
  "subprompt_emotion": "感情取得用(未定)",
  "behavior_normal": "通常応対(未定)",
  "behavior_happy": "楽しそうなとき(未定)",
  "behavior_sad": "悲しそうなとき(未定)",
  "behavior_angry": "怒っているとき(未定)",
  "behavior_serious": "真面目モード(未定)"
}

全プロンプトはUIの設定画面から編集可能。


16. 設計上の既知の制約

| 制約 | 対処方針 |
|---|---|
| 初期は記憶が育たない | 宿命。運用期間が必要と割り切る |
| 話題の粒度判定 | AIの文脈理解に委ねる・日記生成プロンプトで補う |
| プロンプト品質への依存 | ここが本領。継続的に育てる対象として扱う |
| 検索精度の限界 | ペルソナの「聞き返す能力」で補完する |
| 記憶を自然に滲み出させる | プロンプト設計次第。仕組みだけでは保証できない |
| フラッシュAPIコール数が多い | 精度優先・時間がかかってもかまわない設計につき許容 |


17. 未解決・今後詰める

高優先度(品質の核):
 ・ペルソナのベースプロンプト文(最重要・最初は粗くていい)
 ・各観察ペルソナのサブプロンプト文
 ・ふるまいサブプロンプト群の具体的な設計

設計レベル:
 ・change_levelの判定基準(small/medium/largeの定義)
 ・背景層(日常・過去文脈)の蓄積粒度

構想段階:
 ・ネガティブ要素(苦手・避けたいもの)の扱い
  → 常時制約層(件数少)とFTS5召喚層に分ける方向が有望

18. 実装指針

  1. ペルソナ完全分離を最初から守る — 後から分離すると全部やり直しになる

  2. 人格ライブラリから作る — チャットUIは後でいい

  3. 日記生成プロンプトと観察ペルソナプロンプトが核 — ここの質が全体の品質を左右する

  4. ログ記録を最初から入れる — 後から検証・チューニングできるように

  5. 全データにタイムスタンプ — 後から原因を追えるようにする

  6. 最初のプロンプトは粗くていい — 育てる前提。余白を残す

  7. 段階的に始める — まずDB骨格+monChat2チャット機能から


この設計は汎用アーキテクチャとして構想しています。
プロンプト設計が本領。長い戦い覚悟で育てていく。
実装・検証を経て随時更新予定。

monRem / monRChat_preview 実装仕様書v2026.05 実装仕様 rev14

このドキュメントは実装の拠り所。設計思想の詳細はmonRem_design_rev14.mdを参照。
プロンプト設計は粗くていい。動かしながら育てる前提。


1. プロジェクト構成

monRem/                    ← ライブラリ本体
 ├ __init__.py
 ├ db.py                  ← DB接続・初期化・テーブル作成
 ├ chunk.py               ← チャンク管理・メッセージ保存
 ├ memory.py              ← preferences読み書き・FTS5検索
 ├ user_model.py          ← ユーザーモデル読み書き
 ├ flash.py               ← フラッシュパイプライン司令塔
 ├ diary.py               ← 日記生成・統合・保存
 ├ persona.py             ← サブペルソナ三段処理
 ├ emotion.py             ← 感情取得・ふるまいモード
 └ prompt.py              ← システムプロンプト組み立て・LLM呼び出し

monRChat_preview/          ← チャットアプリ
 ├ main.py                ← FastAPIエントリーポイント(ポート8010)
 ├ config.json            ← エンジン・アプリ設定(全ペルソナ共通)
 ├ static/
 └ templates/
   ├ index.html          ← チャット画面
   ├ diary.html          ← 日記閲覧画面
   ├ memory.html         ← 記憶・趣向管理画面
   ├ settings.html       ← 設定画面
   └ logs.html           ← 処理ログ閲覧画面

data/
 ├ personas.db            ← ペルソナ一覧・設定(全ペルソナ共通)
 ├ persona_{name}/        ← ペルソナごとのフォルダ(自動生成)
 │   ├ chat_log.db        ← 会話ログ・チャンク
 │   ├ diary.db           ← 日記
 │   ├ memory.db          ← 趣向データ・ユーザーモデル
 │   └ prompt.json        ← このペルソナ専用の全プロンプト
 └ persona_{name}/
    ├ chat_log.db
    ├ diary.db
    ├ memory.db
    └ prompt.json

2. 技術スタック

言語:Python 3.12
フレームワーク:FastAPI
テンプレート:Jinja2
フロントエンド:Alpine.js(ローカル)
DB:SQLite 3(FTS5有効)
形態素解析:MeCab(FTS5検索のキーワード抽出)
ポート:8010

LLMエンジン(交換可能):
 ├ Gemini API(gemini-2.5-flash-lite)← フラッシュ・精度優先
 ├ Ollama(httpx直接通信)← チャット・速度優先
 └ LM Studio(OpenAI互換API)← チャット・速度優先

3. DBスキーマ

personas.db(全ペルソナ共通)

CREATE TABLE personas (
    id         INTEGER PRIMARY KEY,
    name       TEXT UNIQUE,         -- ペルソナ名(フォルダ名にも使用)
    system_prompt TEXT,             -- ペルソナ定義プロンプト
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

ペルソナ追加時に `data/persona_{name}/` フォルダを自動生成し、
chat_log.db / diary.db / memory.db を初期化する。

chat_log.db(ペルソナごと)

CREATE TABLE messages (
    id         INTEGER PRIMARY KEY,
    chunk_id   TEXT,
    role       TEXT,        -- 'user' / 'assistant'
    content    TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE chunks (
    id         TEXT PRIMARY KEY,  -- chunk_20260518_143022
    started_at TIMESTAMP,
    ended_at   TIMESTAMP,
    flushed    INTEGER DEFAULT 0  -- 0=未処理 1=処理済み
);

diary.db(ペルソナごと)

CREATE TABLE diary (
    id         INTEGER PRIMARY KEY,
    period     TEXT,        -- "2026-05-18 09:00-15:00"
    date       TEXT,        -- "2026-05-18"
    chunk_ids  TEXT,        -- JSON配列 ["chunk_xxx", "chunk_yyy"]
    content    TEXT,        -- 統合日記本文
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

memory.db(ペルソナごと)

-- 趣向データ
CREATE TABLE preferences (
    id               INTEGER PRIMARY KEY,
    category         TEXT,
    content          TEXT,
    intensity        TEXT,    -- high / medium / low
    source_chunk_ids TEXT,
    created_at       TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_surfaced_at TIMESTAMP
);

-- FTS5仮想テーブル
CREATE VIRTUAL TABLE preferences_fts USING fts5(
    content,
    content='preferences',
    content_rowid='id'
);

-- ユーザーモデル
CREATE TABLE user_model (
    layer      TEXT PRIMARY KEY,  -- style / personality / background
    content    TEXT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- メタ情報
CREATE TABLE meta (
    key        TEXT PRIMARY KEY,
    value      TEXT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- flush_count などを管理

intensityの定義と判定基準

| intensity | 判定基準 |
|-----------|----------|
| high | 明確な好み・こだわりとして読み取れる |
| medium | 繰り返し言及・複数文脈で出てきた |
| low | 曖昧な表現・一度だけの言及 |

※ 判定はAI(統合ペルソナ)が文脈を読んで行う
※ 曖昧な場合はlowで登録する(登録しないより登録を優先)
※ 重複防止が最優先:登録前に必ずFTS5で既存を照合すること
※ この判定基準はsubprompt_integrateに明示すること


4. config.json 設定項目

{
  "app": {
    "port": 8010,
    "debug_log": true
  },
  "engine": {
    "type": "gemini",
    "active_ollama": 0,
    "active_gemini": 0,
    "active_lmstudio": 0,
    "ollama": [{
      "name": "Local Gemma3",
      "endpoint": "http://localhost:11434",
      "model": "gemma3:4b",
      "timeout": 120,
      "params": {
        "temperature": 0.8,
        "top_p": 0.9,
        "top_k": 40,
        "max_tokens": 2048,
        "repeat_penalty": 1.1
      }
    }],
    "gemini": [{
      "name": "Gemini Flash Lite",
      "api_key": "",
      "model": "gemini-2.5-flash-lite",
      "timeout": 60,
      "params": {
        "temperature": 1.0,
        "top_p": 0.95,
        "top_k": 40,
        "max_output_tokens": 2048
      }
    }],
    "lmstudio": [{
      "name": "LM Studio Local",
      "endpoint": "http://localhost:1234",
      "model": "",
      "timeout": 120,
      "params": {
        "temperature": 0.8,
        "top_p": 0.9,
        "max_tokens": 2048,
        "repeat_penalty": 1.1
      }
    }]
  },
  "memory": {
    "context_turns": 10,
    "summary_batch": 11,
    "emotion_interval_turns": 5,
    "memory_search_top_k": 3
  },
  "flash": {
    "chunk_interval_minutes": 3,
    "diary_merge_hours": 6,
    "flush_warning_chunks": 10,
    "flush_min_chunks": 1,
    "api_call_sleep_seconds": 5
  },
  "user_model": {
    "change_notify": true,
    "change_threshold": "medium",
    "confirm_required": true
  },
  "loop_detection": {
    "enabled": true,
    "window_chars": 300,
    "repeat_ratio": 0.9
  }
}

5. prompt.json構成(ペルソナごと)

各ペルソナフォルダ内の`prompt.json`に全プロンプトを格納する。
ペルソナ間で共有しない。全プロンプトはUIから編集可能。

{
  "persona_base": "ペルソナ定義プロンプト(未定・要設計)",
  "subprompt_diary": "日記生成用(未定)",
  "subprompt_preference": "趣向ペルソナ用(未定)",
  "subprompt_style": "スタイルペルソナ用(未定)",
  "subprompt_integrate_diary": "統合日記生成用(未定)",
  "subprompt_integrate_usermodel": "ユーザーモデル更新用(未定)",
  "subprompt_integrate_preferences": "preferences抽出用(未定)",
  "subprompt_integrate_change": "change_level判定用(未定)",
  "subprompt_emotion": "感情取得用(未定)",
  "behavior_normal": "通常応対(未定)",
  "behavior_happy": "楽しそうなとき(未定)",
  "behavior_sad": "悲しそうなとき(未定)",
  "behavior_angry": "怒っているとき(未定)",
  "behavior_serious": "真面目モード(未定)"
}

6. フラッシュパイプライン

手動フラッシュ実行
 ↓
① 未処理チャンクを取得(chunksテーブル flushed=0)
 ↓
② チャンクごとに小さな日記を生成(APIコール×チャンク数)
 ※ api_call_sleep_seconds でsleep
 ↓
③ 6時間単位で日記を統合(APIコール×グループ数)
 → diary.dbに保存
 ↓
④ 趣向サブペルソナ・スタイルサブペルソナを並列実行(APIコール×2)
 → 各小文を生成
 ↓
⑤ 統合日記を生成(APIコール×1)
 → diary.db(読み物・人間が読む)
 ↓
⑥ ユーザーモデルを更新(APIコール×1)
 → user_modelテーブル更新
 ↓
⑦ preferences候補を抽出(APIコール×1)
 候補ごとにMeCabで分かち書き
 → FTS5で既存preferencesを検索
  ヒットあり → 既存レコードを更新・強化(新規追加禁止)
  ヒットなし → 新規追加
 → preferencesテーブル更新
 ↓
⑧ change_levelを判定(APIコール×1)
 → change_level / change_summary を出力
 ↓
⑨ change_levelに応じて対話確認
 small  → トーストのみ
 medium → モニカが1〜2個確認
 large  → モニカが複数項目を対話確認
 ↓
⑩ 処理済みチャンクを flushed=1 に更新
 フラッシュログを保存

フラッシュは精度優先・時間がかかってもかまわない。
④並列実行を含め全工程で数分以内を想定。モーダルで進捗を逐一表示する。

※ subprompt_preferencesに以下を明示すること:
 「趣向を追加する前に必ずFTS5で既存を確認する」
 「ヒットした場合は新規追加禁止・既存レコードを更新する」


7. 毎ターン処理

monChat2のチャット機能をベースに、monRemの記憶・ユーザーモデルを統合する。

ユーザー入力(SSEストリーミング)
 ↓
chunk.py:chunk_id判定・messages保存
 ↓
emotion.py:Nターン経過?
 YES → 感情取得・ふるまいモード更新
 NO  → 前回の感情をそのまま使用
 ↓
memory.py:MeCabでキーワード抽出
 → FTS5でpreferencesから関連趣向を取得(top_k件)
 ↓
prompt.py:system prompt組み立て(4層構造)
 # General Instructions  ← 共通プロンプト
 # Persona               ← ペルソナ定義+ユーザーモデル(背景知識)
 # Current Emotional State ← 感情・ふるまいモード
 # Past Conversation Summary ← ローリング要約(500文字以内)
 + 関連趣向(FTS5結果・話題の文脈として)
 ↓
LLM呼び出し(SSEストリーミング・1回のみ)
 ↓
ループ検出(直近300文字・90%類似でストップ)
 ↓
応答を返す・messagesに保存
 ↓
rawターン数 > context_turns(10)?
 YES → 最古の1ターンをpendingへ移動
 ↓
pending >= summary_batch(11)?
 YES → バックグラウンドで忘却曲線型ローリング要約を実行
    → past_summaryを更新・pendingを削除

LLMへの呼び出しは1ターンに1回のみ。複数回呼び出しは禁止。

SSEシグナル

| シグナル | 説明 |
|---|---|
| `{"token": "..."}` | ストリーミングトークン |
| `{"summarize": true}` | 要約バッチ満杯 |
| `{"tokens": {"prompt": N, "output": N}}` | トークン使用量(Gemini専用) |
| `{"loop_detected": true}` | ループ検出・自動停止 |
| `{"debug_prompt": {...}}` | デバッグ用送信プロンプト(debug_log=true時のみ) |
| `{"error": "..."}` | エラー |
| `[DONE]` | ストリーム終了 |


8. UI画面構成

チャット画面(index.html)

  • ペルソナ切り替えドロップダウン(切り替え時に履歴・メモリを自動ロード)

  • エンジン切り替えドロップダウン(Ollama / LM Studio / Gemini 即時切り替え)

  • チャット入力・SSEストリーミング表示

  • 通知フィールド(全動作を細かくリアルタイム通知)

  • フラッシュボタン(条件未満はグレーアウト・フラッシュ中はモーダル表示)

  • ループ検出警告トースト

  • トークン表示(Gemini専用)

日記閲覧画面(diary.html)

  • ペルソナ選択

  • 日付一覧・日付を選択して統合日記を閲覧

記憶・趣向管理画面(memory.html)

  • ペルソナ選択

  • preferencesテーブルの内容を一覧・編集

  • user_modelテーブルの内容を層ごとに編集

設定画面(settings.html)

  • Engineタブ:Gemini / Ollama / LM Studio 各設定・パラメータ

  • Promptsタブ:prompt.jsonの全プロンプトをUIから編集

  • Personasタブ:ペルソナCRUD・システムプロンプト設定

  • Appタブ:config.jsonのその他設定項目

処理ログ閲覧画面(logs.html)

  • debug_log=trueのときのみ記録・表示

  • ペルソナ選択

  • フラッシュログ:各ペルソナの入出力・DB反映前後の差分

  • 毎ターンログ:system prompt全文・LLM入出力


9. 通知・透明性設計

通知フィールド(常時表示)

全動作を細かくリアルタイム通知する。チャット画面に常時表示。

「キーワードを抽出しています…」
「記憶を検索しています…(3件ヒット)」
「感情を読み取っています…」
「日記を生成しています… (2/5)」
「記憶を蒸留しています…」
「〇件の趣向が更新されました」

フラッシュ中モーダル

長い処理中は何が行われているかを逐一表示する。

■ フラッシュ処理中
[■■■□□□□□□□] 30%

✅ チャンク日記を生成しました(3件)
✅ 日記を統合しました
⏳ 趣向ペルソナ・スタイルペルソナ処理中…

処理ログ設計

全工程の入出力をログDBに保存する。
プロンプトの効果を検証・チューニングするための最重要機能。

ログに記録する内容

毎ターンログ:
 ├ 入力テキスト
 ├ 抽出キーワード(MeCab結果)
 ├ FTS5ヒット結果(件数・内容)
 ├ 感情取得の入力・出力
 ├ 組み立てたsystem prompt全文
 └ LLMへの入力・出力全文

フラッシュログ:
 ├ 処理対象チャンクID一覧
 ├ チャンク日記:入力(チャンク本文)・出力(生成日記)
 ├ 統合日記:入力(チャンク日記群)・出力(統合日記)
 ├ 趣向ペルソナ:入力・出力(小文)
 ├ スタイルペルソナ:入力・出力(小文)
 ├ 統合日記生成:入力・出力
 ├ ユーザーモデル更新:入力・出力・更新前後の差分
 ├ preferences抽出:入力・出力・追加/更新の差分
 └ change_level判定:入力・出力

debug_log ON/OFF制御

  • `true`:全工程の入出力をログDBに保存・logs.htmlで閲覧可能

  • `false`:ログ保存をスキップ(通常運用時・DB節約)

  • 設定画面からON/OFFを切り替えられる

  • プロンプト調整時はON・安定運用時はOFFが基本方針

logs.htmlの表示

フラッシュログ一覧(日時・チャンク数・change_level)
 ↓ 選択
各ステップの入力・出力を展開表示
 各ペルソナが何を受け取って何を返したか
 DBへの反映前後の差分

毎ターンログ一覧(日時)
 ↓ 選択
system prompt全文・LLM入出力を表示

10. 未確定・動かしながら決めるもの

・モニカのベースプロンプト文(最重要・最初は粗くていい)
・各サブペルソナのプロンプト文
・change_levelの判定基準(small/medium/largeの定義)
・対話確認のUIフロー詳細
・MeCabなしの代替キーワード抽出(環境によって検討)

11. 実装順序(案)

Step 1 DBとライブラリ骨格
 personas.db・ペルソナごとのDB初期化
 db.py・chunk.py・memory.py・user_model.py

Step 2 チャット機能(monChat2ベース)
 SSEストリーミング・スライディングウィンドウ
 ローリング要約・ループ検出・エンジン切り替え
 ペルソナ管理・system prompt4層構造
 → ここでペルソナとして普通に会話できる状態

Step 3 monRem統合
 FTS5検索・ユーザーモデル組み込み
 フラッシュパイプライン(diary.py・persona.py・flash.py)

Step 4 UI拡充
 日記閲覧・記憶管理・設定・ログ画面

Step 5 プロンプト育成
 動かしながら継続的に改善

プロンプト設計が本領。長い戦い覚悟で育てていく。


仕様書から生成

いいなと思ったら応援しよう!

monjya 応援ありがとうございます!