Skip to content
Merged

V2.5 #48

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ cortex://resources/{resource_name}/
- <strong>Session Management:</strong> Track conversation timelines, participants, and message history with automatic indexing and event-driven processing.
- <strong>Multi-Tenancy Support:</strong> Isolated memory spaces for different users and agents within a single deployment via tenant-aware collection naming.
- <strong>Event-Driven Automation:</strong> File watchers and auto-indexers for background processing, synchronization, and profile enrichment.
- <strong>LLM Result Caching:</strong> Intelligent caching with LRU eviction and TTL expiration reduces redundant LLM API calls by 50-75%, with cascade layer debouncing for 70-90% reduction in layer updates.
- <strong>Agent Framework Integration:</strong> Built-in support for Rig framework and Model Context Protocol (MCP).
- <strong>Web Dashboard:</strong> Svelte 5 SPA (Insights) for monitoring, tenant management, and semantic search visualization.

Expand Down
1 change: 1 addition & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ cortex://resources/{resource_name}/
- <strong>会话管理:</strong> 跟踪对话时间线、参与者和消息历史,具有自动索引和事件驱动处理。
- <strong>多租户支持:</strong> 通过租户感知集合命名,在单个部署中为不同用户和代理提供隔离的内存空间。
- <strong>事件驱动自动化:</strong> 文件监视器和自动索引器用于后台处理、同步和配置文件丰富。
- <strong>LLM结果缓存:</strong> 智能缓存采用LRU淘汰和TTL过期机制,减少50-75%的冗余LLM API调用,级联层防抖可减少70-90%的层更新调用。
- <strong>代理框架集成:</strong> 内置支持Rig框架和模型上下文协议(MCP)。
- <strong>Web仪表板:</strong> Svelte 5 SPA(Insights)用于监控、租户管理和语义搜索可视化。

Expand Down
12 changes: 11 additions & 1 deletion cortex-mem-cli/src/commands/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ pub async fn execute(
operations: Arc<MemoryOperations>,
uri: &str,
abstract_only: bool,
overview: bool,
) -> Result<()> {
println!("{} Getting memory: {}", "🔍".bold(), uri.cyan());

if abstract_only {
if overview {
// Get overview (L1 layer)
let overview_result = operations.get_overview(uri).await?;

println!("\n{}", "─".repeat(80).dimmed());
println!("{} Overview (L1)", "📝".bold());
println!("{}\n", "─".repeat(80).dimmed());
println!("{}", overview_result.overview_text);
println!("{}\n", "─".repeat(80).dimmed());
} else if abstract_only {
// Get abstract (L0 layer)
let abstract_result = operations.get_abstract(uri).await?;

Expand Down
4 changes: 2 additions & 2 deletions cortex-mem-cli/src/commands/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ pub async fn execute(
recursive: true,
};

// Perform vector search
// Perform layered vector search (L0/L1/L2 hierarchical search)
let results = operations.vector_engine()
.semantic_search(query, &options)
.layered_semantic_search(query, &options)
.await?;

println!("\n{} Found {} results\n", "✓".green().bold(), results.len());
Expand Down
25 changes: 25 additions & 0 deletions cortex-mem-cli/src/commands/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,30 @@ pub async fn create(
println!(" {}: {}", "Title".cyan(), t);
}

Ok(())
}

/// Close a session and trigger memory extraction, layer generation, and indexing
pub async fn close(operations: Arc<MemoryOperations>, thread: &str) -> Result<()> {
println!("{} Closing session: {}", "🔒".bold(), thread.cyan());

// Close the session (triggers SessionClosed event → MemoryEventCoordinator)
operations.close_session(thread).await?;

println!("{} Session closed successfully", "✓".green().bold());
println!(" {}: {}", "Thread ID".cyan(), thread);
println!();
println!("{} Waiting for memory extraction, L0/L1 generation, and indexing to complete...", "⏳".yellow().bold());

// Wait for background tasks to complete (max 60 seconds)
// This ensures memory extraction, layer generation, and vector indexing finish before CLI exits
let completed = operations.flush_and_wait(Some(1)).await;

if completed {
println!("{} All background tasks completed successfully", "✓".green().bold());
} else {
println!("{} Background tasks timed out (some may still be processing)", "⚠".yellow().bold());
}

Ok(())
}
17 changes: 15 additions & 2 deletions cortex-mem-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ enum Commands {
/// Show abstract (L0) instead of full content
#[arg(short, long)]
abstract_only: bool,

/// Show overview (L1) instead of full content
#[arg(short, long)]
overview: bool,
},

/// Delete a memory
Expand Down Expand Up @@ -131,6 +135,12 @@ enum SessionAction {
#[arg(short, long)]
title: Option<String>,
},

/// Close a session and trigger memory extraction, layer generation, and indexing
Close {
/// Thread ID to close
thread: String,
},
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -252,8 +262,8 @@ async fn main() -> Result<()> {
} => {
list::execute(operations, uri.as_deref(), include_abstracts).await?;
}
Commands::Get { uri, abstract_only } => {
get::execute(operations, &uri, abstract_only).await?;
Commands::Get { uri, abstract_only, overview } => {
get::execute(operations, &uri, abstract_only, overview).await?;
}
Commands::Delete { uri } => {
delete::execute(operations, &uri).await?;
Expand All @@ -265,6 +275,9 @@ async fn main() -> Result<()> {
SessionAction::Create { thread, title } => {
session::create(operations, &thread, title.as_deref()).await?;
}
SessionAction::Close { thread } => {
session::close(operations, &thread).await?;
}
},
Commands::Stats => {
stats::execute(operations).await?;
Expand Down
3 changes: 2 additions & 1 deletion cortex-mem-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tracing-subscriber = { workspace = true }
walkdir = { workspace = true }
rig-core = { workspace = true }
reqwest = { workspace = true }
log = "0.4"

# Additional dependencies
regex = "1.10"
Expand All @@ -31,7 +32,7 @@ cortex-mem-config = { path = "../cortex-mem-config" }
schemars = "0.8"

# Vector search dependencies (mandatory)
qdrant-client = "1.11"
qdrant-client = "1.17"
dyn-clone = "1.0"

[features]
Expand Down
71 changes: 13 additions & 58 deletions cortex-mem-core/src/automation/auto_extract.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use crate::{
Result,
extraction::MemoryExtractor,
filesystem::CortexFilesystem,
llm::LLMClient,
session::SessionManager,
};
use std::sync::Arc;
use tracing::{info, warn};
use tracing::info;

/// 会话自动提取配置
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -38,18 +36,15 @@ pub struct AutoExtractStats {

/// 会话自动提取器
///
/// 🔧 简化版本:移除了profile.json相关代码
/// 现在所有记忆统一由SessionManager的MemoryExtractor处理
/// v2.5: 此结构体已被简化,记忆提取现在由 SessionManager 通过 MemoryEventCoordinator 处理。
/// 保留此结构体仅用于向后兼容。
pub struct AutoExtractor {
#[allow(dead_code)]
filesystem: Arc<CortexFilesystem>,
#[allow(dead_code)]
llm: Arc<dyn LLMClient>,
#[allow(dead_code)]
extractor: MemoryExtractor,
#[allow(dead_code)]
config: AutoExtractConfig,
/// 用户ID(保留用于兼容性)
user_id: String,
}

Expand All @@ -60,13 +55,9 @@ impl AutoExtractor {
llm: Arc<dyn LLMClient>,
config: AutoExtractConfig,
) -> Self {
let extraction_config = crate::extraction::ExtractionConfig::default();
let extractor = MemoryExtractor::new(filesystem.clone(), llm.clone(), extraction_config);

Self {
filesystem,
llm,
extractor,
config,
user_id: "default".to_string(),
}
Expand All @@ -79,13 +70,9 @@ impl AutoExtractor {
config: AutoExtractConfig,
user_id: impl Into<String>,
) -> Self {
let extraction_config = crate::extraction::ExtractionConfig::default();
let extractor = MemoryExtractor::new(filesystem.clone(), llm.clone(), extraction_config);

Self {
filesystem,
llm,
extractor,
config,
user_id: user_id.into(),
}
Expand All @@ -96,51 +83,19 @@ impl AutoExtractor {
self.user_id = user_id.into();
}

/// 🔧 简化:extract_session现在只需要直接使用SessionManager处理即可
/// AutoExtractor不再负责用户记忆提取(由MemoryExtractor统一处理)
/// 提取会话记忆
///
/// v2.5: 此方法已被废弃。记忆提取现在由 SessionManager::close_session 通过
/// MemoryEventCoordinator 异步处理。此方法返回空统计用于向后兼容。
pub async fn extract_session(&self, _thread_id: &str) -> Result<AutoExtractStats> {
info!("AutoExtractor::extract_session is deprecated - all memory extraction is now handled by SessionManager::close_session");
warn!("Use SessionManager::close_session instead. This method returns empty stats for compatibility.");

info!(
"AutoExtractor::extract_session is deprecated - memory extraction is handled by MemoryEventCoordinator"
);
Ok(AutoExtractStats::default())
}
}

/// 增强SessionManager支持自动提取
pub struct AutoSessionManager {
session_manager: SessionManager,
#[allow(dead_code)]
auto_extractor: AutoExtractor,
}

impl AutoSessionManager {
/// 创建新的自动会话管理器
pub fn new(
session_manager: SessionManager,
auto_extractor: AutoExtractor,
) -> Self {
Self {
session_manager,
auto_extractor,
}
}

/// 获取内部的 SessionManager
pub fn session_manager(&self) -> &SessionManager {
&self.session_manager
}

/// 获取可变的 SessionManager
pub fn session_manager_mut(&mut self) -> &mut SessionManager {
&mut self.session_manager
}

/// 关闭会话并自动提取(增强版)
pub async fn close_session(&mut self, thread_id: &str) -> Result<()> {
// 先通过SessionManager关闭会话(触发timeline和记忆提取)
self.session_manager.close_session(thread_id).await?;

info!("Session {} closed with automatic memory extraction via SessionManager", thread_id);
Ok(())
/// 获取用户ID
pub fn user_id(&self) -> &str {
&self.user_id
}
}
10 changes: 7 additions & 3 deletions cortex-mem-core/src/automation/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl AutoIndexer {
run_id: Some(thread_id.to_string()),
actor_id: None,
role: Some(format!("{:?}", message.role)),
memory_type: crate::types::MemoryType::Conversational,
layer: "L2".to_string(),
hash: self.calculate_hash(&message.content),
importance_score: 0.5,
entities: vec![],
Expand Down Expand Up @@ -189,7 +189,7 @@ impl AutoIndexer {
run_id: Some(thread_id.to_string()),
actor_id: None,
role: Some(format!("{:?}", message.role)),
memory_type: crate::types::MemoryType::Conversational,
layer: "L2".to_string(),
hash: self.calculate_hash(&message.content),
importance_score: 0.5,
entities: vec![],
Expand Down Expand Up @@ -592,7 +592,11 @@ impl AutoIndexer {
run_id: None,
actor_id: None,
role: None,
memory_type: crate::types::MemoryType::Conversational,
layer: match layer {
ContextLayer::L0Abstract => "L0",
ContextLayer::L1Overview => "L1",
ContextLayer::L2Detail => "L2",
}.to_string(),
hash: self.calculate_hash(content),
importance_score: 0.5,
entities: vec![],
Expand Down
Loading