6 Commits

Author SHA1 Message Date
syui
8951544ad3 Merge pull request #4 from syui/claude/ai-memory-system-011CUps6H1mBNe6zxKdkcyUj
Fix compilation errors in Layer 4 caching
2025-11-06 19:04:05 +09:00
Claude
49f41c1b44 Fix compilation errors in Layer 4 caching
- Remove unused RelationshipInference import from base.rs
- Add Parse error variant to MemoryError enum
- Replace num_minutes() with num_seconds() / 60 (chrono compatibility)

Fixes compatibility with different chrono versions.
2025-11-06 10:02:27 +00:00
syui
46249fb10c Merge pull request #3 from syui/claude/ai-memory-system-011CUps6H1mBNe6zxKdkcyUj
Add Layer 4 caching to reduce AI load
2025-11-06 18:59:16 +09:00
Claude
2579312029 Add Layer 4 caching to reduce AI load
Implemented 5-minute short-term caching for relationship inference:

**store.rs**:
- Added relationship_cache SQLite table
- save_relationship_cache(), get_cached_relationship()
- save_all_relationships_cache(), get_cached_all_relationships()
- clear_relationship_cache() - called on memory create/update/delete
- Cache duration: 5 minutes (configurable constant)

**relationship.rs**:
- Modified infer_all_relationships() to use cache
- Added get_relationship() function with caching support
- Cache hit: return immediately
- Cache miss: compute, save to cache, return

**base.rs**:
- Updated tool_get_relationship() to use cached version
- Reduced load from O(n) scan to O(1) cache lookup

**Benefits**:
- Reduces AI load when frequently querying relationships
- Automatic cache invalidation on data changes
- Scales better with growing memory count
- No user-facing changes

**Documentation**:
- Updated ARCHITECTURE.md with caching strategy details

This addresses scalability concerns for Layer 4 as memory data grows.
2025-11-06 09:33:42 +00:00
syui
2abcc957ef Merge pull request #2 from syui/claude/ai-memory-system-011CUps6H1mBNe6zxKdkcyUj
Design Layer 5: Knowledge Sharing platform
2025-11-06 18:05:45 +09:00
Claude
8037477104 Design Layer 5: Knowledge Sharing platform
Layer 5 focuses on sharing AI interactions as "information + personality":
- SharedInteraction: Problem, approach, result with author profile
- ShareableProfile: User essence from Layer 3.5 + Layer 4
- Privacy-first: Share patterns, not raw data
- Use cases: AI-era GitHub Gist, knowledge SNS, persona showcase

Philosophy: People seek both useful information and authentic personality.
Like SNS/streaming, the combination creates value. Information alone is
sterile; personality alone is hollow.

Updated:
- docs/ARCHITECTURE.md: Comprehensive Layer 5 design with data models
- README.md: Added Layer 5 feature overview
- Layer overview diagram updated
2025-11-06 09:03:24 +00:00
7 changed files with 312 additions and 51 deletions

View File

@@ -4,6 +4,8 @@ AI memory system with psychological analysis for Claude via MCP.
**Current: Layers 1-4 Complete** - Memory storage, AI interpretation, personality analysis, integrated profile, and relationship inference. **Current: Layers 1-4 Complete** - Memory storage, AI interpretation, personality analysis, integrated profile, and relationship inference.
**Planned: Layer 5** - Knowledge sharing platform combining useful insights with author personality.
## Features ## Features
### Layer 1: Pure Memory Storage ### Layer 1: Pure Memory Storage
@@ -34,6 +36,12 @@ AI memory system with psychological analysis for Claude via MCP.
- 🎮 **Game Ready**: Foundation for companion apps, games, VTubers - 🎮 **Game Ready**: Foundation for companion apps, games, VTubers
- 🔒 **Opt-in**: Enable only when needed with `--enable-layer4` flag - 🔒 **Opt-in**: Enable only when needed with `--enable-layer4` flag
### Layer 5: Knowledge Sharing (Planned)
- 💡 **Information + Personality**: Share AI interactions with context
- 🌐 **SNS for AI Era**: Useful insights combined with author's unique perspective
- 🔒 **Privacy-First**: Share essence, not raw data
- 📊 **Showcase**: Display how AI understands you
### General ### General
- 🛠️ **MCP Integration**: Works seamlessly with Claude Code - 🛠️ **MCP Integration**: Works seamlessly with Claude Code
- 🧪 **Well-tested**: Comprehensive test coverage - 🧪 **Well-tested**: Comprehensive test coverage
@@ -223,8 +231,8 @@ Multi-layer system design:
- **Layer 3** ✅ Complete: Big Five personality analysis - **Layer 3** ✅ Complete: Big Five personality analysis
- **Layer 3.5** ✅ Complete: Integrated profile (unified summary) - **Layer 3.5** ✅ Complete: Integrated profile (unified summary)
- **Layer 4** ✅ Complete: Relationship inference (optional, `--enable-layer4`) - **Layer 4** ✅ Complete: Relationship inference (optional, `--enable-layer4`)
- **Layer 4+** 🔵 Future: Extended game/companion features - **Layer 4+** 🔵 Planned: Extended game/companion features
- **Layer 5** 🔵 Future: Distribution and sharing - **Layer 5** 🔵 Planned: Knowledge sharing (information + personality)
**Design Philosophy**: **Design Philosophy**:
- **"Internal complexity, external simplicity"**: Simple API, complex internals - **"Internal complexity, external simplicity"**: Simple API, complex internals

View File

@@ -12,8 +12,8 @@ aigptは、独立したレイヤーを積み重ねる設計です。各レイヤ
``` ```
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ Layer 5: Distribution & Sharing │ 🔵 Future │ Layer 5: Knowledge Sharing │ 🔵 Planned
│ (Game streaming, public/private) │ (Information + Personality sharing)
├─────────────────────────────────────────┤ ├─────────────────────────────────────────┤
│ Layer 4+: Extended Features │ 🔵 Planned │ Layer 4+: Extended Features │ 🔵 Planned
│ (Advanced game/companion systems) │ │ (Advanced game/companion systems) │
@@ -362,10 +362,16 @@ if user.extraversion < 0.5 {
### Design Philosophy ### Design Philosophy
**推測のみ、保存なし**: **推測ベース + 短期キャッシング**:
- 毎回Layer 1-3.5から計算 - 毎回Layer 1-3.5から計算
- キャッシュなし(シンプルさ優先) - 5分間の短期キャッシュで負荷軽減
- 後でキャッシング追加可能 - メモリ更新時にキャッシュ無効化
**キャッシング戦略**:
- SQLiteテーブル`relationship_cache`)に保存
- 個別エンティティ: `get_relationship(entity_id)`
- 全体リスト: `list_relationships()`
- メモリ作成/更新/削除時に自動クリア
**独立性**: **独立性**:
- Layer 1-3.5に依存 - Layer 1-3.5に依存
@@ -460,18 +466,104 @@ pub struct Companion {
--- ---
## Layer 5: Distribution (Future) ## Layer 5: Knowledge Sharing (Planned)
**Status**: 🔵 **Future Consideration** **Status**: 🔵 **Planned**
### Purpose ### Purpose
ゲーム配信や共有機能 AIとのやり取りを「情報 + 個性」として共有する。SNSや配信のように、**有用な知見**と**作者の個性**を両立させたコンテンツプラットフォーム
### Ideas ### Design Philosophy
- Share memory rankings
- Export as shareable format 人々が求めるもの:
- Public/private memory modes 1. **情報価値**: 「このプロンプトでこんな結果が得られた」「この問題をAIでこう解決した」
- Integration with streaming platforms 2. **個性・共感**: 「この人はこういう人だ」という親近感、信頼
SNSや配信と同じく、**情報のみは無機質**、**個性のみは空虚**。両方を組み合わせることで価値が生まれる。
### Data Model
```rust
pub struct SharedInteraction {
pub id: String,
// 情報価値
pub problem: String, // 何を解決しようとしたか
pub approach: String, // AIとどうやり取りしたか
pub result: String, // 何を得たか
pub usefulness_score: f32, // 有用性 (0.0-1.0, priority_score由来)
pub tags: Vec<String>, // 検索用タグ
// 個性
pub author_profile: ShareableProfile, // 作者の本質
pub why_this_matters: String, // なぜこの人がこれに取り組んだか
// メタデータ
pub views: u32,
pub useful_count: u32, // 「役に立った」カウント
pub created_at: DateTime<Utc>,
}
pub struct ShareableProfile {
// ユーザーの本質Layer 3.5から抽出)
pub personality_essence: Vec<TraitScore>, // Top 3 traits
pub core_interests: Vec<String>, // 5個
pub core_values: Vec<String>, // 5個
// AIの解釈
pub ai_perspective: String, // AIがこのユーザーをどう理解しているか
pub confidence: f32, // データ品質 (0.0-1.0)
// 関係性スタイルLayer 4から推測、匿名化
pub relationship_style: String, // 例: "深く狭い繋がりを好む"
}
```
### Privacy Design
**共有するもの:**
- ✅ 本質Layer 3.5の統合プロファイル)
- ✅ パターン(関係性スタイル、思考パターン)
- ✅ 有用な知見(問題解決のアプローチ)
**共有しないもの:**
- ❌ 生の会話内容Layer 1-2
- ❌ 個人を特定できる情報
- ❌ メモリID、タイムスタンプ等の生データ
### Use Cases
**1. AI時代のGitHub Gist**
- 有用なプロンプトとその結果を共有
- 作者の個性とアプローチが見える
- 「この人の考え方が参考になる」
**2. 知見のSNS**
- 情報を発信しながら、個性も伝わる
- フォロー、「役に立った」機能
- 関心領域でフィルタリング
**3. AIペルソナのショーケース**
- 「AIは私をこう理解している」を共有
- 性格分析の精度を比較
- コミュニティでの自己表現
### Implementation Ideas
```rust
// Layer 5のMCPツール
- create_shareable_interaction() -
- get_shareable_profile() -
- export_interaction() - JSON/Markdown形式でエクスポート
- anonymize_data() -
```
### Future Platforms
- Web UI: 知見を閲覧・検索・共有
- API: 外部サービスと連携
- RSS/Atom: フィード配信
- Markdown Export: ブログ投稿用
--- ---

View File

@@ -19,6 +19,9 @@ pub enum MemoryError {
#[error("Configuration error: {0}")] #[error("Configuration error: {0}")]
Config(String), Config(String),
#[error("Parse error: {0}")]
Parse(String),
} }
pub type Result<T> = std::result::Result<T, MemoryError>; pub type Result<T> = std::result::Result<T, MemoryError>;

View File

@@ -9,5 +9,5 @@ pub use analysis::UserAnalysis;
pub use error::{MemoryError, Result}; pub use error::{MemoryError, Result};
pub use memory::Memory; pub use memory::Memory;
pub use profile::{UserProfile, TraitScore}; pub use profile::{UserProfile, TraitScore};
pub use relationship::{RelationshipInference, infer_all_relationships}; pub use relationship::{RelationshipInference, infer_all_relationships, get_relationship};
pub use store::MemoryStore; pub use store::MemoryStore;

View File

@@ -184,6 +184,11 @@ impl RelationshipInference {
pub fn infer_all_relationships( pub fn infer_all_relationships(
store: &MemoryStore, store: &MemoryStore,
) -> Result<Vec<RelationshipInference>> { ) -> Result<Vec<RelationshipInference>> {
// Check cache first
if let Some(cached) = store.get_cached_all_relationships()? {
return Ok(cached);
}
// Get all memories // Get all memories
let memories = store.list()?; let memories = store.list()?;
@@ -219,9 +224,41 @@ pub fn infer_all_relationships(
.unwrap_or(std::cmp::Ordering::Equal) .unwrap_or(std::cmp::Ordering::Equal)
}); });
// Cache the result
store.save_all_relationships_cache(&relationships)?;
Ok(relationships) Ok(relationships)
} }
/// Get relationship inference for a specific entity (with caching)
pub fn get_relationship(
store: &MemoryStore,
entity_id: &str,
) -> Result<RelationshipInference> {
// Check cache first
if let Some(cached) = store.get_cached_relationship(entity_id)? {
return Ok(cached);
}
// Get all memories
let memories = store.list()?;
// Get user profile
let user_profile = store.get_profile()?;
// Infer relationship
let relationship = RelationshipInference::infer(
entity_id.to_string(),
&memories,
&user_profile,
);
// Cache it
store.save_relationship_cache(entity_id, &relationship)?;
Ok(relationship)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -102,6 +102,17 @@ impl MemoryStore {
[], [],
)?; )?;
// Create relationship_cache table (Layer 4 - relationship inference cache)
// entity_id = "" for all_relationships cache
conn.execute(
"CREATE TABLE IF NOT EXISTS relationship_cache (
entity_id TEXT PRIMARY KEY,
data TEXT NOT NULL,
cached_at TEXT NOT NULL
)",
[],
)?;
Ok(Self { conn }) Ok(Self { conn })
} }
@@ -137,6 +148,10 @@ impl MemoryStore {
memory.updated_at.to_rfc3339(), memory.updated_at.to_rfc3339(),
], ],
)?; )?;
// Clear relationship cache since memory data changed
self.clear_relationship_cache()?;
Ok(()) Ok(())
} }
@@ -204,6 +219,9 @@ impl MemoryStore {
return Err(MemoryError::NotFound(memory.id.clone())); return Err(MemoryError::NotFound(memory.id.clone()));
} }
// Clear relationship cache since memory data changed
self.clear_relationship_cache()?;
Ok(()) Ok(())
} }
@@ -217,6 +235,9 @@ impl MemoryStore {
return Err(MemoryError::NotFound(id.to_string())); return Err(MemoryError::NotFound(id.to_string()));
} }
// Clear relationship cache since memory data changed
self.clear_relationship_cache()?;
Ok(()) Ok(())
} }
@@ -464,6 +485,123 @@ impl MemoryStore {
Ok(profile) Ok(profile)
} }
// ========== Layer 4: Relationship Cache Methods ==========
/// Cache duration in minutes
const RELATIONSHIP_CACHE_DURATION_MINUTES: i64 = 5;
/// Save relationship inference to cache
pub fn save_relationship_cache(
&self,
entity_id: &str,
relationship: &super::relationship::RelationshipInference,
) -> Result<()> {
let data = serde_json::to_string(relationship)?;
let cached_at = Utc::now().to_rfc3339();
self.conn.execute(
"INSERT OR REPLACE INTO relationship_cache (entity_id, data, cached_at) VALUES (?1, ?2, ?3)",
params![entity_id, data, cached_at],
)?;
Ok(())
}
/// Get cached relationship inference
pub fn get_cached_relationship(
&self,
entity_id: &str,
) -> Result<Option<super::relationship::RelationshipInference>> {
let mut stmt = self
.conn
.prepare("SELECT data, cached_at FROM relationship_cache WHERE entity_id = ?1")?;
let result = stmt.query_row([entity_id], |row| {
let data: String = row.get(0)?;
let cached_at: String = row.get(1)?;
Ok((data, cached_at))
});
match result {
Ok((data, cached_at_str)) => {
// Check if cache is still valid (within 5 minutes)
let cached_at = DateTime::parse_from_rfc3339(&cached_at_str)
.map_err(|e| MemoryError::Parse(e.to_string()))?
.with_timezone(&Utc);
let age_minutes = (Utc::now() - cached_at).num_seconds() / 60;
if age_minutes < Self::RELATIONSHIP_CACHE_DURATION_MINUTES {
let relationship: super::relationship::RelationshipInference =
serde_json::from_str(&data)?;
Ok(Some(relationship))
} else {
// Cache expired
Ok(None)
}
}
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e.into()),
}
}
/// Save all relationships list to cache (use empty string as entity_id)
pub fn save_all_relationships_cache(
&self,
relationships: &[super::relationship::RelationshipInference],
) -> Result<()> {
let data = serde_json::to_string(relationships)?;
let cached_at = Utc::now().to_rfc3339();
self.conn.execute(
"INSERT OR REPLACE INTO relationship_cache (entity_id, data, cached_at) VALUES ('', ?1, ?2)",
params![data, cached_at],
)?;
Ok(())
}
/// Get cached all relationships list
pub fn get_cached_all_relationships(
&self,
) -> Result<Option<Vec<super::relationship::RelationshipInference>>> {
let mut stmt = self
.conn
.prepare("SELECT data, cached_at FROM relationship_cache WHERE entity_id = ''")?;
let result = stmt.query_row([], |row| {
let data: String = row.get(0)?;
let cached_at: String = row.get(1)?;
Ok((data, cached_at))
});
match result {
Ok((data, cached_at_str)) => {
let cached_at = DateTime::parse_from_rfc3339(&cached_at_str)
.map_err(|e| MemoryError::Parse(e.to_string()))?
.with_timezone(&Utc);
let age_minutes = (Utc::now() - cached_at).num_seconds() / 60;
if age_minutes < Self::RELATIONSHIP_CACHE_DURATION_MINUTES {
let relationships: Vec<super::relationship::RelationshipInference> =
serde_json::from_str(&data)?;
Ok(Some(relationships))
} else {
Ok(None)
}
}
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e.into()),
}
}
/// Clear all relationship caches (call when memories are modified)
pub fn clear_relationship_cache(&self) -> Result<()> {
self.conn.execute("DELETE FROM relationship_cache", [])?;
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -2,7 +2,7 @@ use anyhow::Result;
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::io::{self, BufRead, Write}; use std::io::{self, BufRead, Write};
use crate::core::{Memory, MemoryStore, UserAnalysis, RelationshipInference, infer_all_relationships}; use crate::core::{Memory, MemoryStore, UserAnalysis, infer_all_relationships, get_relationship};
pub struct BaseMCPServer { pub struct BaseMCPServer {
store: MemoryStore, store: MemoryStore,
@@ -581,31 +581,9 @@ impl BaseMCPServer {
}); });
} }
// Get memories and user profile // Get relationship (with caching)
let memories = match self.store.list() { match get_relationship(&self.store, entity_id) {
Ok(m) => m, Ok(relationship) => json!({
Err(e) => return json!({
"success": false,
"error": format!("Failed to get memories: {}", e)
}),
};
let user_profile = match self.store.get_profile() {
Ok(p) => p,
Err(e) => return json!({
"success": false,
"error": format!("Failed to get profile: {}", e)
}),
};
// Infer relationship
let relationship = RelationshipInference::infer(
entity_id.to_string(),
&memories,
&user_profile,
);
json!({
"success": true, "success": true,
"relationship": { "relationship": {
"entity_id": relationship.entity_id, "entity_id": relationship.entity_id,
@@ -617,7 +595,12 @@ impl BaseMCPServer {
"confidence": relationship.confidence, "confidence": relationship.confidence,
"inferred_at": relationship.inferred_at "inferred_at": relationship.inferred_at
} }
}) }),
Err(e) => json!({
"success": false,
"error": format!("Failed to get relationship: {}", e)
}),
}
} }
fn tool_list_relationships(&self, arguments: &Value) -> Value { fn tool_list_relationships(&self, arguments: &Value) -> Value {