Initial scaffold: GyanDoot v2.0 in Rust

Full Cargo workspace with 11 crates:
- gyandoot-core: Shared types, config, error types
- gyandoot-agent: Agent loop, tool execution, context compression
- gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama)
- gyandoot-memory: MemPalace persistent memory system
- gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI)
- gyandoot-security: Credential vault, sanitizer, threat detector
- gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics
- gyandoot-subagents: Sub-agent spawning and orchestration
- gyandoot-skills: Skills discovery, loading, preprocessing, bundles
- gyandoot-profile: Multi-agent profile manager
- gyandoot-gateway: Gateway sessions, routing, access control

Plus: CLI entry point with clap, cross-platform installer script,
KANBAN.md, .gnpl/AGENTS.md

Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
2026-06-11 20:04:12 +05:30
commit 851c838ca5
53 changed files with 8037 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
//! MemPalace — The core persistent memory store.
//!
//! Stores conversation entries with embeddings for semantic retrieval,
//! handles memory compaction, and provides query capabilities.
use std::path::PathBuf;
use chrono::{DateTime, Utc};
use gyandoot_core::MemoryResult;
use serde::{Deserialize, Serialize};
use tracing::debug;
use crate::identity::IdentityStore;
use crate::transcript::TranscriptStore;
/// A single memory entry stored in the palace.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub content: String,
pub agent_id: String,
pub session_id: String,
pub entry_type: EntryType,
pub embedding: Option<Vec<f64>>,
pub created_at: DateTime<Utc>,
pub metadata: std::collections::HashMap<String, String>,
}
/// The type of a memory entry.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EntryType {
Conversation,
Fact,
Identity,
Task,
Skill,
}
/// A memory query result with relevance score.
#[derive(Debug, Clone)]
pub struct MemoryQueryResult {
pub content: String,
pub relevance: f64,
pub entry_type: EntryType,
pub created_at: DateTime<Utc>,
}
/// Status information for the memory palace.
#[derive(Debug, Clone)]
pub struct MemoryStatus {
pub entry_count: usize,
pub backend: String,
pub storage_size: u64,
}
/// The MemPalace — persistent, searchable agent memory.
///
/// Entries are stored as JSON files in the data directory, organized by
/// agent and session. Future versions will include vector embeddings
/// for semantic search.
pub struct MemPalace {
entries: Vec<MemoryEntry>,
storage_path: PathBuf,
transcript_store: TranscriptStore,
identity_store: IdentityStore,
}
impl MemPalace {
/// Create a new MemPalace with memory directory in the user's data dir.
pub fn new() -> Self {
let data_dir = gyandoot_core::data_dir().join("memory");
std::fs::create_dir_all(&data_dir).ok();
Self {
entries: Vec::new(),
storage_path: data_dir,
transcript_store: TranscriptStore::new(),
identity_store: IdentityStore::new(),
}
}
/// Create a MemPalace with a custom storage path.
pub fn with_path(path: PathBuf) -> Self {
std::fs::create_dir_all(&path).ok();
Self {
entries: Vec::new(),
storage_path: path,
transcript_store: TranscriptStore::new(),
identity_store: IdentityStore::new(),
}
}
/// Store a memory entry in the palace.
pub fn store(&mut self, entry: MemoryEntry) -> MemoryResult<()> {
debug!("Storing memory entry: {}", entry.id);
self.entries.push(entry);
Ok(())
}
/// Store a conversation turn (the last user + assistant pair) as memory.
pub async fn store_conversation_turn(
&mut self,
messages: &[gyandoot_core::Message],
) -> MemoryResult<()> {
let recent: Vec<&gyandoot_core::Message> = messages.iter().rev().take(4).collect();
for msg in recent.iter().rev() {
let entry = MemoryEntry {
id: uuid::Uuid::new_v4().to_string(),
content: msg.content.clone(),
agent_id: "default".to_string(),
session_id: String::new(),
entry_type: EntryType::Conversation,
embedding: None,
created_at: Utc::now(),
metadata: std::collections::HashMap::new(),
};
self.store(entry)?;
}
Ok(())
}
/// Query memory by simple text matching.
///
/// Future versions will use embedding-based semantic search.
pub fn query(&self, query: &str, limit: usize) -> MemoryResult<Vec<MemoryQueryResult>> {
let query_lower = query.to_lowercase();
let mut results: Vec<MemoryQueryResult> = self
.entries
.iter()
.filter(|e| e.content.to_lowercase().contains(&query_lower))
.map(|e| MemoryQueryResult {
content: e.content.clone(),
relevance: 1.0, // placeholder; real relevance from embedding cosine sim
entry_type: e.entry_type.clone(),
created_at: e.created_at,
})
.collect();
results.sort_by(|a, b| b.relevance.partial_cmp(&a.relevance).unwrap_or(std::cmp::Ordering::Equal));
results.truncate(limit);
Ok(results)
}
/// Export all memory to a JSON file at the given path.
pub fn export_to(&self, path: &PathBuf) -> MemoryResult<()> {
let json = serde_json::to_string_pretty(&self.entries)
.map_err(|e| gyandoot_core::MemoryError::Storage(e.to_string()))?;
std::fs::write(path, &json)
.map_err(|e| gyandoot_core::MemoryError::Storage(e.to_string()))?;
Ok(())
}
/// Get memory status information.
pub fn status() -> MemoryResult<MemoryStatus> {
let data_dir = gyandoot_core::data_dir().join("memory");
let size = std::fs::read_dir(&data_dir)
.map(|entries| {
entries
.filter_map(|e| e.ok())
.filter_map(|e| e.metadata().ok())
.map(|m| m.len())
.sum()
})
.unwrap_or(0);
let entry_count = std::fs::read_dir(&data_dir)
.map(|entries| entries.filter_map(|e| e.ok()).count())
.unwrap_or(0);
Ok(MemoryStatus {
entry_count,
backend: "local-json".to_string(),
storage_size: size,
})
}
/// Get a reference to the transcript store.
pub fn transcript_store(&self) -> &TranscriptStore {
&self.transcript_store
}
/// Get a reference to the identity store.
pub fn identity_store(&self) -> &IdentityStore {
&self.identity_store
}
}
impl Default for MemPalace {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_store_and_query() {
let mut palace = MemPalace::new();
palace
.store(MemoryEntry {
id: "1".to_string(),
content: "The user likes Python".to_string(),
agent_id: "default".to_string(),
session_id: "s1".to_string(),
entry_type: EntryType::Fact,
embedding: None,
created_at: Utc::now(),
metadata: std::collections::HashMap::new(),
})
.unwrap();
let results = palace.query("python", 10).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].content.contains("Python"));
}
#[test]
fn test_export() {
let palace = MemPalace::new();
let tmp = std::env::temp_dir().join("gyandoot_test_export.json");
palace.export_to(&tmp).unwrap();
assert!(tmp.exists());
std::fs::remove_file(tmp).ok();
}
}