Implement Layer 3.5: Integrated Profile system
Layer 3.5 provides a unified, essential summary of the user by integrating data from Layers 1-3. This addresses the product design gap where individual layers work correctly but lack a cohesive, simple output. Design Philosophy: - "Internal complexity, external simplicity" - AI references get_profile() as primary tool (efficient) - Detailed data still accessible when needed (flexible) - Auto-caching with smart update triggers (performant) Implementation: - UserProfile struct: dominant traits, core interests, core values - Automatic data aggregation from Layer 1-3 - Frequency analysis for topics/values extraction - SQLite caching (user_profiles table, single row) - Update triggers: 10+ new memories, new analysis, or 7+ days old - MCP tool: get_profile (primary), others available for details Data Structure: - dominant_traits: Top 3 Big Five traits - core_interests: Top 5 frequent topics from memories - core_values: Top 5 values from high-priority memories - key_memory_ids: Top 10 priority memories as evidence - data_quality: 0.0-1.0 confidence score Usage Pattern: - Normal: AI calls get_profile() only when needed - Deep dive: AI calls list_memories(), get_memory(id) for details - Efficient: Profile cached, regenerates only when necessary
This commit is contained in:
@@ -82,6 +82,16 @@ impl MemoryStore {
|
||||
[],
|
||||
)?;
|
||||
|
||||
// Create user_profiles table (Layer 3.5 - integrated profile cache)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS user_profiles (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
data TEXT NOT NULL,
|
||||
last_updated TEXT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
@@ -366,6 +376,60 @@ impl MemoryStore {
|
||||
|
||||
Ok(analyses)
|
||||
}
|
||||
|
||||
// === Layer 3.5: Integrated Profile ===
|
||||
|
||||
/// Save integrated profile to cache
|
||||
pub fn save_profile(&self, profile: &super::profile::UserProfile) -> Result<()> {
|
||||
let profile_json = serde_json::to_string(profile)?;
|
||||
|
||||
self.conn.execute(
|
||||
"INSERT OR REPLACE INTO user_profiles (id, data, last_updated) VALUES (1, ?1, ?2)",
|
||||
params![profile_json, profile.last_updated.to_rfc3339()],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get cached profile if exists
|
||||
pub fn get_cached_profile(&self) -> Result<Option<super::profile::UserProfile>> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT data FROM user_profiles WHERE id = 1")?;
|
||||
|
||||
let result = stmt.query_row([], |row| {
|
||||
let json: String = row.get(0)?;
|
||||
Ok(json)
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(json) => {
|
||||
let profile: super::profile::UserProfile = serde_json::from_str(&json)?;
|
||||
Ok(Some(profile))
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get or generate profile (with automatic caching)
|
||||
pub fn get_profile(&self) -> Result<super::profile::UserProfile> {
|
||||
// Check cache first
|
||||
if let Some(cached) = self.get_cached_profile()? {
|
||||
// Check if needs update
|
||||
if !cached.needs_update(self)? {
|
||||
return Ok(cached);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new profile
|
||||
let profile = super::profile::UserProfile::generate(self)?;
|
||||
|
||||
// Cache it
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user