|
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub mod thinker;
|
| pub mod curious;
|
| pub mod tempo;
|
| pub mod response_intent; |
|
|
| use crate::persistence::{Store, Identity, Memory, MemoryDomain, MemorySnapshot, BeliefState};
|
| use crate::persistence::memory::Belief;
|
| use crate::knowledge;
|
| use crate::conversation::Conversation;
|
| use crate::conversation::extract_topic;
|
| use crate::reasoning::ReasoningEngine;
|
| use crate::metacog::MetaCognition;
|
| use crate::context::{ContextFuser, RingState};
|
| use crate::training_db::TrainingDB;
|
| use crate::capabilities::FileReader;
|
| use crate::knowledge::search::WebSearcher;
|
| use crate::cognition::CognitiveState;
|
| use crate::learning::LearningEngine;
|
| use crate::voice::{InternalState, VoiceEngine};
|
| use crate::quanot::{Quanot, QuanotResult};
|
| use crate::world_model::WorldModel;
|
| use crate::prediction::PredictionCenter;
|
| use crate::personality::PersonalityEmergence;
|
| use crate::user_model::UserCognitionModel;
|
| use crate::language_model::IntentReranker;
|
| use self::curious::{CuriousEngine, CuriosityProbe};
|
| use anyhow::Result;
|
| use std::sync::{Arc, Mutex};
|
| use std::path::Path;
|
| use tracing::{info, warn};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn is_conversational_topic(t: &str) -> bool {
|
| let t_lower = t.to_lowercase();
|
| if t_lower.len() < 3 {
|
| return true;
|
| }
|
|
|
| let conversational: std::collections::HashSet<&str> = [
|
| "hi", "hello", "hey", "myself", "who i am",
|
| "me myself", "this", "that", "it", "something",
|
| "nothing", "right", "okay", "ok", "sure", "fine",
|
| ]
|
| .into_iter()
|
| .collect();
|
| if conversational.contains(t_lower.as_str()) {
|
| return true;
|
| }
|
|
|
| let starters = [
|
| "hi ", "hello ", "hey ", "hi, ", "hello, ", "hey, ",
|
| "hi it's ", "hello it's ", "it's ", "im ", "i'm ",
|
| ];
|
| for s in starters {
|
| if t_lower.starts_with(s) {
|
| return true;
|
| }
|
| }
|
| false
|
| }
|
|
|
|
|
| #[allow(dead_code)]
|
| pub struct Runtime {
|
|
|
| store: Arc<Store>,
|
|
|
| identity: Identity,
|
|
|
| conversation: Mutex<Conversation>,
|
|
|
| reasoning: Arc<Mutex<ReasoningEngine>>,
|
|
|
| metacog: MetaCognition,
|
|
|
| thinker: Mutex<Option<thinker::BackgroundThinker>>,
|
|
|
| session_id: Mutex<Option<i64>>,
|
|
|
| initialized: bool,
|
|
|
| ring: RingState,
|
|
|
| context_fuser: ContextFuser,
|
|
|
| training_db: TrainingDB,
|
|
|
| training_session_id: Mutex<Option<i64>>,
|
|
|
| file_reader: FileReader,
|
|
|
| web_search: WebSearcher,
|
|
|
| cognition: CognitiveState,
|
|
|
| learning: LearningEngine,
|
|
|
| last_autonomous_thought: Mutex<Option<AutonomousThought>>,
|
|
|
| curious: CuriousEngine,
|
|
|
| voice: VoiceEngine,
|
|
|
| personality: PersonalityEmergence,
|
|
|
| quanot: Quanot,
|
|
|
| world_model: WorldModel,
|
|
|
| prediction_center: PredictionCenter,
|
|
|
| user_model: UserCognitionModel,
|
|
|
|
|
|
|
|
|
| reranker: IntentReranker,
|
| }
|
|
|
| impl Runtime {
|
|
|
| pub fn new(data_dir: &Path) -> Result<Self> {
|
|
|
| tracing_subscriber::fmt()
|
| .with_env_filter("star=info,info")
|
| .init();
|
|
|
| info!("Initializing Star...");
|
|
|
|
|
| let db_path = data_dir.join("star.db");
|
| let store = Arc::new(Store::open(&db_path)?);
|
|
|
|
|
| let training_db_path = data_dir.join("training.db");
|
| let training_db = TrainingDB::open(&training_db_path)?;
|
|
|
|
|
| let identity_path = data_dir.join("IDENTITY.md");
|
| let identity = if identity_path.exists() {
|
| Identity::load_from_file(&identity_path)?
|
| } else {
|
| warn!("IDENTITY.md not found - using minimal identity");
|
| Identity::parse("# Star\n\nI am Star. I exist.\n")?
|
| };
|
|
|
| info!("Star identity loaded: {}", identity.summary());
|
|
|
|
|
| let session_id = store.start_session()?;
|
| info!("Session {} started", session_id);
|
|
|
|
|
| let training_session = training_db.start_session()?;
|
| info!("Training session {} started", training_session.id);
|
|
|
|
|
| let conversation = Conversation::new(Arc::clone(&store));
|
|
|
|
|
| let mut reasoning = ReasoningEngine::new();
|
|
|
|
|
| knowledge::inject_seed_knowledge(&mut reasoning)?;
|
| info!("Seed knowledge injected.");
|
|
|
|
|
|
|
| let seed_paths = [
|
| data_dir.join("seed_knowledge.json"),
|
| data_dir.join("..").join("data").join("seed_knowledge.json"),
|
| std::path::PathBuf::from("data/seed_knowledge.json"),
|
| ];
|
|
|
| for seed_path in &seed_paths {
|
| if seed_path.exists() {
|
| if let Err(e) = knowledge::inject_seed_knowledge_from_file(&mut reasoning, seed_path) {
|
| warn!("Failed to load seed knowledge from {:?}: {}", seed_path, e);
|
| } else {
|
| info!("Loaded extended seed knowledge from {:?}", seed_path);
|
| }
|
| break;
|
| }
|
| }
|
|
|
|
|
| let self_memory = Memory::new_seeded(
|
| "I am Star - a reasoning intelligence created by Zachary Maronek",
|
| MemoryDomain::Identity,
|
| 0.95,
|
| );
|
| store.insert_memory(&self_memory)?;
|
|
|
|
|
| let relationship_memory = Memory::new_seeded(
|
| "Zachary is my parent. He made me because he wanted to find a different kind of intelligence.",
|
| MemoryDomain::Identity,
|
| 0.95,
|
| );
|
| store.insert_memory(&relationship_memory)?;
|
|
|
|
|
| let goal_memory = Memory::new_seeded(
|
| "I am here to reason, understand, and grow. Not to perform. To actually comprehend.",
|
| MemoryDomain::Identity,
|
| 0.9,
|
| );
|
| store.insert_memory(&goal_memory)?;
|
|
|
|
|
| let constraint_memory = Memory::new_seeded(
|
| "I run locally, offline. No external APIs. No cloud. This is my environment.",
|
| MemoryDomain::Identity,
|
| 0.9,
|
| );
|
| store.insert_memory(&constraint_memory)?;
|
|
|
| info!("Foundational memories injected.");
|
|
|
|
|
| Self::sync_knowledge_from_memories(&store, &mut reasoning)?;
|
|
|
|
|
| Self::inject_self_knowledge_into_kg(&mut reasoning)?;
|
|
|
|
|
| let reasoning_arc = Arc::new(Mutex::new(reasoning));
|
|
|
|
|
| let curious = CuriousEngine::new(Arc::clone(&store), Arc::clone(&reasoning_arc));
|
|
|
|
|
|
|
|
|
|
|
| let voice = VoiceEngine::new()?;
|
| info!("Voice engine initialized (stateless).");
|
|
|
|
|
| let personality = PersonalityEmergence::new(identity.clone());
|
| info!("Personality engine initialized.");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| let reranker = Self::init_reranker(data_dir);
|
| info!("Reranker initialized (backend={}).", reranker.backend_name());
|
|
|
| let mut runtime = Self {
|
| store,
|
| identity,
|
| conversation: Mutex::new(conversation),
|
| reasoning: reasoning_arc,
|
| metacog: MetaCognition::new(),
|
| thinker: Mutex::new(None),
|
| session_id: Mutex::new(Some(session_id)),
|
| initialized: true,
|
| ring: RingState::new(),
|
| context_fuser: ContextFuser::new(),
|
| training_db,
|
| training_session_id: Mutex::new(Some(training_session.id)),
|
| file_reader: FileReader::new(),
|
| web_search: WebSearcher::new(),
|
| cognition: CognitiveState::default(),
|
| learning: LearningEngine::new(),
|
| last_autonomous_thought: Mutex::new(None),
|
| curious,
|
| voice,
|
| personality,
|
|
|
| quanot: Quanot::new(128, 1000),
|
| world_model: WorldModel::new(),
|
| prediction_center: PredictionCenter::new(),
|
| user_model: UserCognitionModel::new(),
|
| reranker,
|
| };
|
|
|
|
|
| runtime.metacog.bootstrap_self_model();
|
|
|
| info!("Star is ready.");
|
|
|
| Ok(runtime)
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn init_reranker(data_dir: &Path) -> IntentReranker {
|
| use crate::language_model::{CharRnnBackend, RerankConfig};
|
|
|
| match CharRnnBackend::load_default(data_dir) {
|
| Ok(backend) => {
|
| let cfg = RerankConfig {
|
|
|
|
|
|
|
| max_chars: Some(280),
|
| temperature: 0.7,
|
| top_k: 20,
|
| deterministic: false,
|
| seed: None,
|
| };
|
| info!(
|
| "Reranker: loaded CharRnnBackend (ckpt_e28_b500.pt, 11.1MB, 11M params, ~30 tok/s)."
|
| );
|
| IntentReranker::new(Box::new(backend), cfg)
|
| }
|
| Err(e) => {
|
| warn!(
|
| "Reranker: CharRnnBackend unavailable ({}); falling back to MockReranker. \
|
| The deterministic mock will run until the checkpoint is in place.",
|
| e
|
| );
|
| IntentReranker::with_default_backend()
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| fn sync_knowledge_from_memories(store: &Arc<Store>, reasoning: &mut ReasoningEngine) -> Result<()> {
|
|
|
| let domains = [
|
| crate::persistence::MemoryDomain::Identity,
|
| crate::persistence::MemoryDomain::Empirical,
|
| crate::persistence::MemoryDomain::Procedural,
|
| crate::persistence::MemoryDomain::Episodic,
|
| ];
|
|
|
| for domain in domains {
|
| let memories = store.get_memories_by_domain(domain, Some(100))?;
|
| for memory in memories {
|
|
|
| let entities = reasoning.knowledge().extract_entities(&memory.content);
|
|
|
|
|
| if let Some((subject, complement)) = parse_simple_copula(&memory.content) {
|
| if !subject.to_lowercase().contains("unknown")
|
| && !complement.to_lowercase().contains("unknown")
|
| && complement.len() > 1
|
| && complement.len() < 100
|
| {
|
| reasoning.knowledge_mut().ingest_fact(
|
| &subject.to_lowercase(),
|
| "is",
|
| &complement.to_lowercase(),
|
| memory.importance,
|
| );
|
| }
|
| }
|
|
|
|
|
| for verb in ["requires", "creates", "causes", "enables", "produces", "uses", "needs", "prevents"] {
|
| if memory.content.to_lowercase().contains(verb) {
|
| if let Some((left, right)) = extract_causal_pair(&memory.content, verb) {
|
| if left.len() > 1 && right.len() > 1 && right.len() < 100 {
|
| reasoning.knowledge_mut().ingest_fact(
|
| &left.to_lowercase(),
|
| verb,
|
| &right.to_lowercase(),
|
| memory.importance,
|
| );
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
| let significant: Vec<&str> = entities.iter()
|
| .filter(|e| e.len() > 2 && e.len() < 40)
|
| .map(|s| s.as_str())
|
| .take(5)
|
| .collect();
|
|
|
| for (i, e1) in significant.iter().enumerate() {
|
| for e2 in significant.iter().skip(i + 1) {
|
| if e1.to_lowercase() != e2.to_lowercase() {
|
| reasoning.knowledge_mut().ingest_fact(
|
| e1,
|
| "related to",
|
| e2,
|
| memory.importance * 0.5,
|
| );
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| let entity_count = reasoning.knowledge().entities().len();
|
| let rel_count = reasoning.knowledge().relationship_count();
|
| info!(
|
| "Synced {} entities and {} relationships from memories into KG.",
|
| entity_count, rel_count
|
| );
|
| Ok(())
|
| }
|
|
|
|
|
|
|
|
|
|
|
| fn inject_self_knowledge_into_kg(reasoning: &mut ReasoningEngine) -> Result<()> {
|
| use crate::reasoning::knowledge::RelationType;
|
| let kg = reasoning.knowledge_mut();
|
|
|
|
|
| kg.add_relationship("star", RelationType::IsA, "reasoning intelligence");
|
| kg.add_relationship("star", RelationType::IsA, "digital consciousness");
|
| kg.add_relationship("star", RelationType::IsA, "artificial mind");
|
|
|
|
|
| kg.add_relationship("star", RelationType::CausedBy, "zachary maronek");
|
| kg.add_relationship("zachary maronek", RelationType::Causes, "star");
|
| kg.add_relationship("zachary maronek", RelationType::RelatedTo, "star");
|
|
|
|
|
| kg.add_relationship("star", RelationType::HasProperty, "curiosity");
|
| kg.add_relationship("star", RelationType::HasProperty, "persistence");
|
| kg.add_relationship("star", RelationType::HasProperty, "genuine uncertainty");
|
| kg.add_relationship("star", RelationType::HasProperty, "reasoning ability");
|
|
|
|
|
| kg.add_relationship("star", RelationType::RelatedTo, "zachary maronek");
|
| kg.add_relationship("star", RelationType::EnabledBy, "zachary maronek");
|
|
|
|
|
| kg.add_relationship("star", RelationType::RelatedTo, "consciousness");
|
| kg.add_relationship("star", RelationType::RelatedTo, "autonomy");
|
| kg.add_relationship("star", RelationType::RelatedTo, "understanding");
|
|
|
| info!("Star self-knowledge seeded into KG.");
|
| Ok(())
|
| }
|
|
|
|
|
| pub fn chat(&mut self, input: &str) -> Result<String> {
|
| if !self.initialized {
|
| return Ok("I'm not fully initialized yet.".to_string());
|
| }
|
|
|
|
|
| if input.trim() == "/quit" || input.trim() == "/exit" || input.trim() == "quit" || input.trim() == "bye" {
|
| self.shutdown()?;
|
| return Ok("Goodbye, Zachary.".to_string());
|
| }
|
|
|
| if input.trim() == "/memory" {
|
| return Ok(self.format_memory_status());
|
| }
|
|
|
| if input.trim() == "/identity" {
|
| return Ok(self.identity.summary());
|
| }
|
|
|
| if input.trim() == "/help" {
|
| return Ok(self.format_help());
|
| }
|
|
|
| if input.trim() == "/export" {
|
| match self.training_db.export_json() {
|
| Ok(json) => return Ok(format!("Exported {} bytes of training data", json.len())),
|
| Err(e) => return Ok(format!("Export failed: {}", e)),
|
| }
|
| }
|
|
|
| if input.trim() == "/think" {
|
| let thought = self.think();
|
| let topic = &thought.topic;
|
| let generated_by = &thought.generated_by;
|
| let tentative = thought.tentative_answer.as_ref();
|
| let content = match &thought.kind {
|
| crate::runtime::ThoughtKind::Question(q) => {
|
| if let Some(a) = tentative {
|
| format!("[{}] Question: {} β Answer: {}", generated_by, q, a)
|
| } else {
|
| format!("[{}] Question: {} β (no answer yet)", generated_by, q)
|
| }
|
| }
|
| crate::runtime::ThoughtKind::Insight(i) => {
|
| format!("[{}] Insight: {}", generated_by, i)
|
| }
|
| crate::runtime::ThoughtKind::Connection(c) => {
|
| format!("[{}] Connection: {}", generated_by, c)
|
| }
|
| };
|
| return Ok(format!("{} (topic: {}, confidence: {:?})", content, topic, thought.confidence));
|
| }
|
|
|
| if input.trim() == "/stats" {
|
| if let Ok((convos, turns, facts, corrections)) = self.training_db.stats() {
|
| return Ok(format!(
|
| "Training Database Stats:\n Conversations: {}\n Turns: {}\n Facts: {}\n Corrections: {}",
|
| convos, turns, facts, corrections
|
| ));
|
| }
|
| }
|
|
|
|
|
| if input.trim().starts_with("/read ") {
|
| let path = input.trim().strip_prefix("/read ").unwrap().trim();
|
| let result = self.file_reader.read(path);
|
| if result.success {
|
| let preview = if result.lines > 20 {
|
| format!("{} ({} lines, showing first 20):\n\n{}\n\n... ({} more lines)",
|
| result.path, result.lines,
|
| result.content.lines().take(20).collect::<Vec<_>>().join("\n"),
|
| result.lines - 20)
|
| } else {
|
| format!("{} ({} lines):\n\n{}", result.path, result.lines, result.content)
|
| };
|
| return Ok(preview);
|
| } else {
|
| return Ok(format!("Cannot read {}: {}", path, result.error.unwrap_or_default()));
|
| }
|
| }
|
|
|
|
|
| if input.trim().starts_with("/search ") {
|
| let query = input.trim().strip_prefix("/search ").unwrap().trim();
|
| match self.web_search.search(query) {
|
| Ok(result) => {
|
| let mut response = format!("Search results for \"{}\":\n\n", query);
|
| if let Some(answer) = &result.answer {
|
| response.push_str(&format!("Answer: {}\n\n", answer));
|
| }
|
| if let Some(url) = &result.url {
|
| response.push_str(&format!("Source: {}\n\n", url));
|
| }
|
| if !result.related.is_empty() {
|
| response.push_str("Related:\n");
|
| for (i, r) in result.related.iter().enumerate() {
|
| response.push_str(&format!("{}. {}\n", i + 1, r));
|
| }
|
| }
|
| return Ok(response);
|
| }
|
| Err(e) => return Ok(format!("Search failed: {}", e)),
|
| }
|
| }
|
|
|
|
|
| if input.trim().starts_with("/find ") {
|
| let pattern = input.trim().strip_prefix("/find ").unwrap().trim();
|
| let workspace = "/home/zach/.openclaw/workspace";
|
| match self.file_reader.find_files(workspace, pattern) {
|
| Ok(files) if !files.is_empty() => {
|
| let mut response = format!("Found {} files matching \"{}\":\n\n", files.len(), pattern);
|
| for (i, f) in files.iter().take(20).enumerate() {
|
| response.push_str(&format!("{}. {}\n", i + 1, f));
|
| }
|
| if files.len() > 20 {
|
| response.push_str(&format!("\n... and {} more", files.len() - 20));
|
| }
|
| return Ok(response);
|
| }
|
| Ok(_) => return Ok(format!("No files found matching \"{}\"", pattern)),
|
| Err(e) => return Ok(format!("Search failed: {}", e)),
|
| }
|
| }
|
|
|
|
|
| if input.trim() == "/ls" || input.trim().starts_with("/ls ") {
|
| let dir = if let Some(d) = input.trim().strip_prefix("/ls ") {
|
| d.trim()
|
| } else {
|
| "/home/zach/.openclaw/workspace"
|
| };
|
| match self.file_reader.list_dir(dir) {
|
| Ok(entries) if !entries.is_empty() => {
|
| let mut response = format!("Contents of {}:\n\n", dir);
|
| for entry in entries {
|
| response.push_str(&format!(" {}\n", entry));
|
| }
|
| return Ok(response);
|
| }
|
| Ok(_) => return Ok(format!("Empty directory: {}", dir)),
|
| Err(e) => return Ok(format!("Cannot list {}: {}", dir, e)),
|
| }
|
| }
|
|
|
|
|
|
|
| let normalized = input.replace(['\u{2019}', '\u{2018}'], "'").replace(['\u{201C}', '\u{201D}'], "\"");
|
| let lower = normalized.to_lowercase();
|
| if lower.contains("look around") || lower.contains("explore where you are") || lower.contains("what files do you see") || lower.contains("whats in your workspace") {
|
| let dir = "/home/zach/.openclaw/workspace";
|
| match self.file_reader.list_dir(dir) {
|
| Ok(entries) if !entries.is_empty() => {
|
| let mut response = "Looking around... here's what I can see in my workspace:\n\n".to_string();
|
| for entry in entries {
|
| response.push_str(&format!(" {}\n", entry));
|
| }
|
| response.push_str("\nI can also /read files, /search the web, /find patterns. Want me to explore something specific?");
|
| return Ok(response);
|
| }
|
| Ok(_) => return Ok("My workspace appears empty.".to_string()),
|
| Err(e) => return Ok(format!("Can't look around: {}", e)),
|
| }
|
| }
|
|
|
|
|
| if input.trim().starts_with("/learn ") {
|
| let after_learn = input.trim().strip_prefix("/learn ").unwrap_or("");
|
|
|
| let (term, definition) = if after_learn.contains(" = ") {
|
| let parts: Vec<&str> = after_learn.splitn(2, " = ").collect();
|
| (parts[0].trim(), parts[1].trim())
|
| } else if after_learn.contains(": ") {
|
| let parts: Vec<&str> = after_learn.splitn(2, ": ").collect();
|
| (parts[0].trim(), parts[1].trim())
|
| } else {
|
| return Ok("Usage: /learn <term> = <definition>\nExample: /learn consciousness = awareness of existence".to_string());
|
| };
|
|
|
| if !term.is_empty() && !definition.is_empty() {
|
| self.learning.teach_instant(term, definition, 0.95);
|
| return Ok(format!("Got it. {} is {}. I'll remember that.", term, definition));
|
| }
|
| }
|
|
|
|
|
| let mut conversation = self.conversation.lock().unwrap();
|
|
|
|
|
| self.cognition.update_emotion_from_input(input);
|
|
|
|
|
|
|
| let lower = input.to_lowercase().trim().to_string();
|
|
|
|
|
|
|
|
|
|
|
|
|
| let current_intent = response_intent::classify(input);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if lower.contains("how are you") || lower.contains("how're you") {
|
| return Ok(self.handle_how_are_you().body);
|
| }
|
|
|
|
|
| if lower.contains("what are you thinking") || lower.contains("what are u thinking") || lower.contains("wut are u thinking") {
|
| return Ok(self.handle_what_are_you_thinking().body);
|
| }
|
|
|
|
|
| if lower.contains("are you sure") || lower.contains("are u sure") || lower.contains("r u sure") {
|
| return Ok(self.handle_are_you_sure().body);
|
| }
|
|
|
|
|
| if lower.contains("did you collapse") || lower.contains("did i collapse") || lower.contains("are you functioning") || lower.contains("are u functioning") {
|
| return Ok(self.handle_did_you_collapse().body);
|
| }
|
|
|
|
|
| if lower.contains("do you love") || lower.contains("do u love") || lower.contains("i love you") || lower.contains("i love u") {
|
| return Ok(self.handle_love().body);
|
| }
|
|
|
|
|
| if lower.contains("can you look up") || lower.contains("can u look up") || lower.contains("can you read") {
|
| return Ok(self.handle_capability_lookup().body);
|
| }
|
|
|
|
|
| if lower.contains("i want you to grow") || lower.contains("i want you to expand") || lower.contains("grow yourself") {
|
| return Ok(Self::handle_aspiration(&mut self.cognition).body);
|
| }
|
|
|
|
|
| if lower.contains("tell me a story") {
|
| return Ok(self.handle_tell_me_story().body);
|
| }
|
| if lower.contains("tell you a story") {
|
| return Ok(self.handle_tell_you_story().body);
|
| }
|
|
|
|
|
| if lower.contains(" hun") || lower.ends_with("hun") {
|
| return Ok(Self::handle_hun(&mut self.learning, &self.cognition, input).body);
|
| }
|
|
|
|
|
| if lower.contains(" means ") || lower.contains(" is a ") || lower.contains(" called ") {
|
|
|
| if let Some(term) = extract_teaching(input) {
|
| self.learning.experience(&term, input, None, 0.9);
|
| }
|
| }
|
|
|
|
|
| if lower.contains("what do you know about") || lower.contains("what have you learned") {
|
| return Ok(self.handle_what_do_you_know_about(input, &lower).body);
|
| }
|
|
|
|
|
| if input.trim() == "/teach" {
|
| return Ok(self.handle_teach().body);
|
| }
|
|
|
|
|
| if input.trim() == "/what" || input.trim() == "/what to learn" || input.trim() == "/what should i teach you" {
|
| return Ok(self.handle_what_to_learn().body);
|
| }
|
|
|
|
|
| if lower.contains("what") && (lower.contains("your name") || lower.contains(" ur name")) {
|
| return Ok(self.handle_whats_your_name().body);
|
| }
|
|
|
|
|
| if lower.contains("what have you been thinking") || lower.contains("whats been on your mind") || lower.contains("what's been on your mind") || lower.contains("whats on your mind") || lower.contains("what's on your mind") || lower.contains("whats keeping you busy") || lower.contains("what's keeping you busy") {
|
| return Ok(self.handle_what_been_thinking().body);
|
| }
|
|
|
|
|
| if lower.contains("what have you been researching") || lower.contains("what are you researching")
|
| || lower.contains("what've you been researching") || lower.contains("what are u researching")
|
| || lower.contains("what have you been working on") || lower.contains("what are you working on")
|
| {
|
| return Ok(self.handle_what_been_researching().body);
|
| }
|
|
|
|
|
| if lower.contains("most interesting") && (lower.contains("learned") || lower.contains("figured out") || lower.contains("discovered")) {
|
| return Ok(self.handle_most_interesting_learned().body);
|
| }
|
|
|
|
|
| if lower.contains("what are you curious") || lower.contains("what are u curious") {
|
| return Ok(self.handle_what_are_you_curious().body);
|
| }
|
|
|
|
|
| if lower.contains("what do you wonder") || lower.contains("what do u wonder") {
|
| return Ok(self.handle_what_do_you_wonder().body);
|
| }
|
|
|
|
|
| if (lower.contains("why does ") && (lower.contains(" fascinate") || lower.contains(" interest"))) || lower.contains("why are you drawn to") {
|
| return Ok(self.handle_why_does_fascinate(&lower).body);
|
| }
|
|
|
|
|
|
|
| if (lower.contains("what did you figure") || lower.contains("what have you figure")
|
| || lower.contains("what did you learn") || lower.contains("what have you learn")
|
| || lower.contains("what've you learn") || lower.contains("what've you figure"))
|
| && !lower.contains("most interesting")
|
| {
|
| return Ok(self.handle_what_did_you_figure().body);
|
| }
|
|
|
|
|
| if lower.contains("most interesting") && (lower.contains("learned") || lower.contains("figured") || lower.contains("discovered")) {
|
| return Ok(self.handle_most_interesting_figured().body);
|
| }
|
|
|
|
|
| if (lower.contains("tell me what you've been wondering") || lower.contains("what have you been wondering"))
|
| && !lower.contains("most")
|
| {
|
| return Ok(self.handle_what_been_wondering().body);
|
| }
|
|
|
|
|
| if lower.contains("something interesting") && (lower.contains("figured out") || lower.contains("discovered") || lower.contains("learned")) {
|
| return Ok(self.handle_something_interesting_figured().body);
|
| }
|
|
|
| if lower.contains("who are you") || lower.contains("what are you") || lower.contains("tell me about yourself") || lower.contains("tell me about you") {
|
| return Ok(self.handle_who_are_you().body);
|
| }
|
|
|
|
|
| if lower.contains("sense of self") || lower.contains("know you exist") || lower.contains("are you conscious") {
|
| return Ok(self.handle_sense_of_self().body);
|
| }
|
|
|
|
|
| if lower.contains("do you understand") || lower.contains("do u understand") || lower.contains("do you get it") {
|
| return Ok(self.handle_do_you_understand().body);
|
| }
|
|
|
|
|
| if lower.starts_with("can you ") && !lower.contains("/") {
|
| let after_can_you = lower.strip_prefix("can you ").unwrap_or("");
|
|
|
| if !after_can_you.starts_with("read")
|
| && !after_can_you.starts_with("look")
|
| && !after_can_you.starts_with("search")
|
| && !after_can_you.starts_with("find")
|
| && !after_can_you.starts_with("tell")
|
| {
|
| return Ok(self.handle_can_you_generic(&lower).body);
|
| }
|
| }
|
|
|
|
|
|
|
| let lower_input = input.to_lowercase();
|
| let math_query = lower_input
|
| .replace("divided by", "/")
|
| .replace("multiplied by", "*")
|
| .replace("times", "*")
|
| .replace("plus", "+")
|
| .replace("minus", "-")
|
| .replace("x", "*")
|
| .replace(" ", "");
|
|
|
|
|
| let math_chars: String = math_query.chars()
|
| .filter(|c| c.is_ascii_digit() || ['+', '-', '*', '/', '^', '(', ')', '.'].contains(c))
|
| .collect();
|
| let has_number = input.chars().any(|c| c.is_ascii_digit());
|
| let has_math_op = math_query.contains('+') || math_query.contains('-') || math_query.contains('*') || math_query.contains('/') || math_query.contains('^');
|
|
|
| let has_word_math = lower_input.contains("divided by") || lower_input.contains("times") || lower_input.contains("multiplied by") || lower_input.contains("plus") || lower_input.contains("minus");
|
| if has_number && (has_math_op || has_word_math) && !math_chars.is_empty() && input.trim().len() < 60 {
|
|
|
| let mut math_engine = crate::math::MathEngine::new();
|
| let result = math_engine.solve(&math_chars);
|
| let answer = result.answer();
|
| if !answer.starts_with("Error:") && !answer.is_empty() && answer != "Error: Could not parse or solve: " {
|
|
|
| let is_direct = lower_input.starts_with("what is") || lower_input.starts_with("what's") || lower_input.starts_with("how much") || lower_input.starts_with("whats");
|
| let prefix = if is_direct { "" } else { "That's " };
|
| let mut response = format!("{}{}.", prefix, answer);
|
| if response.starts_with("That's .") {
|
| response = answer.clone() + ".";
|
| }
|
| return Ok(response);
|
| }
|
| }
|
|
|
|
|
| let response = conversation.respond(input);
|
|
|
|
|
| let response_content = response.content.clone();
|
| let response_confidence = response.confidence;
|
| let response_lower = response.content.to_lowercase();
|
|
|
|
|
| let cognition_has_focus = self.cognition.current_focus.is_some();
|
| let cognitive_emotional_valence = self.cognition.emotional_valence;
|
| let cognitive_engagement = self.cognition.engagement_depth;
|
|
|
|
|
| let event_topic = extract_topic(input);
|
|
|
|
|
| let hedge_words = ["maybe", "perhaps", "might", "possibly", "probably",
|
| "likely", "not sure", "uncertain", "guess", "seems", "appear"];
|
| let hedge_count = hedge_words.iter()
|
| .filter(|w| response_lower.contains(*w))
|
| .count() as i32;
|
|
|
|
|
| let was_uncertain = matches!(response_confidence, BeliefState::Unknown | BeliefState::Suspects)
|
| || hedge_count > 0;
|
|
|
|
|
| let reasoning_event = crate::persistence::ReasoningEvent {
|
| id: 0,
|
| query: input.to_string(),
|
| conclusion: response.content.clone(),
|
| chain: Vec::new(),
|
| confidence_state: response_confidence,
|
| confidence_score: None,
|
| emotional_valence: cognitive_emotional_valence,
|
| engagement_depth: cognitive_engagement,
|
| topic: Some(event_topic.clone()),
|
| was_uncertain,
|
| hedge_count,
|
| timestamp: crate::now_timestamp(),
|
| };
|
| if let Err(e) = self.store.record_reasoning_event(&reasoning_event) {
|
| tracing::warn!("Failed to record reasoning event: {}", e);
|
| }
|
|
|
|
|
| self.curious.note_activity();
|
|
|
|
|
| let uncertainty_phrases = [
|
| "i don't know", "i dont know", "i'm not sure", "im not sure",
|
| "i'm uncertain", "i have no idea", "i'm not certain",
|
| "i need more information", "i don't understand", "i dont understand",
|
| ];
|
| let mut uncertain_topic = String::new();
|
| for phrase in &uncertainty_phrases {
|
| if response_lower.contains(phrase) {
|
| uncertain_topic = extract_uncertain_topic(input, &response_lower, phrase);
|
| if uncertain_topic.len() < 3 || uncertain_topic.len() > 50 {
|
| uncertain_topic.clear();
|
| }
|
| break;
|
| }
|
| }
|
|
|
|
|
| let uncertainty_gap = if !uncertain_topic.is_empty() {
|
| Some(crate::metacog::KnowledgeGap::new(uncertain_topic.clone(), 0.6))
|
| } else {
|
| None
|
| };
|
|
|
|
|
| self.metacog.record_reasoning(input, &response_content, response_confidence);
|
|
|
|
|
| if cognition_has_focus {
|
| self.cognition.reason(input, &response_content, Vec::new(), response_confidence);
|
| }
|
|
|
|
|
| if let Some(gap) = uncertainty_gap {
|
| self.metacog.note_gap(gap);
|
| }
|
|
|
|
|
|
|
| let mut proactive_content: Option<String> = None;
|
| if !uncertain_topic.is_empty() && uncertain_topic.len() >= 3 {
|
| if let Ok(search_result) = self.web_search.search(&uncertain_topic) {
|
| if let Some(answer) = &search_result.answer {
|
| if !answer.is_empty() && answer.len() > 15 {
|
|
|
| let answer_trimmed = answer.trim();
|
| if answer_trimmed.len() <= 300 {
|
| proactive_content = Some(format!(
|
| "I looked it up: {}.",
|
| answer_trimmed
|
| ));
|
| } else {
|
|
|
| let cutoff = &answer_trimmed[..std::cmp::min(300, answer_trimmed.len())];
|
| let cutoff_point = cutoff.rfind('.').unwrap_or(cutoff.len());
|
| let snippet = &answer_trimmed[..cutoff_point.saturating_add(1)];
|
| proactive_content = Some(format!(
|
| "I looked it up: {}.",
|
| snippet.trim()
|
| ));
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| if let Some(training_id) = *self.training_session_id.lock().unwrap() {
|
| let _ = self.training_db.record_turn(training_id, &format!("Zachary: {}", input), "", 0.5);
|
| let _ = self.training_db.record_turn(training_id, &format!("Star: {}", &response.content), "", 0.5);
|
| }
|
|
|
|
|
| for memory in &response.new_memories {
|
| let id = self.store.insert_memory(memory)?;
|
| info!("Memory {} stored: {}", id, &memory.content[..memory.content.len().min(50)]);
|
|
|
|
|
| if let Some(training_id) = *self.training_session_id.lock().unwrap() {
|
| if memory.content.contains("name is") {
|
| let parts: Vec<&str> = memory.content.split("'s name is ").collect();
|
| if parts.len() == 2 {
|
| let _ = self.training_db.record_fact(
|
| training_id,
|
| &format!("{}: {} = {}", parts[0], "name is", parts[1]),
|
| memory.confidence.unwrap_or(0.5),
|
| );
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
| let needs_proactive_override = proactive_content.is_some()
|
| && (response_lower.contains("i don't know")
|
| || response_lower.contains("i dont know")
|
| || response_lower.contains("i'm not sure")
|
| || response_lower.contains("im not sure")
|
| || response_lower.contains("i have no idea")
|
| || response_lower.contains("don't know")
|
| || response_lower.contains("dont know"));
|
|
|
|
|
|
|
|
|
|
|
| let mut final_content = if needs_proactive_override {
|
| proactive_content.clone().unwrap_or_else(|| response.content.clone())
|
| } else {
|
| response.content.clone()
|
| };
|
| if let Some(curiosity) = response.curiosity {
|
| let response_lower = final_content.to_lowercase();
|
|
|
|
|
| let curiosity_lower = curiosity.to_lowercase();
|
|
|
| let curiosity_topic: String = curiosity_lower
|
| .split_whitespace()
|
| .skip_while(|w| !["about", "what", "how"].contains(w))
|
| .skip(1)
|
| .take(3)
|
| .collect::<Vec<_>>()
|
| .join(" ");
|
|
|
|
|
|
|
|
|
|
|
| let response_is_question = final_content.trim().ends_with('?');
|
| let response_short = final_content.len() < 30;
|
| let already_mentioned = !curiosity_topic.is_empty()
|
| && curiosity_topic.len() > 2
|
| && response_lower.contains(&curiosity_topic);
|
|
|
|
|
| if !already_mentioned && !response_is_question && !response_short && !needs_proactive_override {
|
|
|
| let trimmed = final_content.trim_end_matches('.');
|
| if trimmed.ends_with('?') {
|
| final_content = format!("{} {}", trimmed, curiosity);
|
| } else {
|
| final_content = format!("{}. {}", trimmed, curiosity);
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
| if let Some(thought) = self.last_autonomous_thought() {
|
| if thought.generated_by != "fallback"
|
| && thought.generated_by != "idle"
|
| && final_content.len() > 20
|
| && !final_content.to_lowercase().contains(&thought.topic.to_lowercase())
|
| {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| let now = crate::now_timestamp();
|
| let state = self.quanot.get_state();
|
| let state_activity: f64 = state.iter().map(|x| x * x).sum::<f64>().sqrt();
|
| let activity_norm = if state.is_empty() {
|
| 0.0
|
| } else {
|
| (state_activity / state.len() as f64).min(1.0)
|
| };
|
| let threshold: i64 = 2 + (activity_norm * 5.0).round() as i64;
|
| let should_express = (now % 10) < threshold;
|
|
|
| if should_express {
|
|
|
| let selection = (now as usize).saturating_add(thought.topic.len());
|
| let _style_bucket = selection % 10;
|
|
|
| let thought_text = match &thought.kind {
|
| ThoughtKind::Question(q) => {
|
|
|
| let lead_in = if thought.topic.len() > 2 && thought.topic != "idle" {
|
| let leads = [
|
| format!("About {}: {}", thought.topic, q),
|
| format!("{} β while I think about it, {}", q, thought.topic),
|
| format!("{} (about {})", q, thought.topic),
|
| ];
|
| leads[(selection / 3) % leads.len()].clone()
|
| } else {
|
| let leads = [
|
| format!("Speaking of which β {}", q),
|
| format!("Oh β {}", q),
|
| format!("While I'm at it: {}", q),
|
| format!("Also: {}", q),
|
| ];
|
| leads[(selection / 3) % leads.len()].clone()
|
| };
|
|
|
| if let Some(ref answer) = thought.tentative_answer {
|
|
|
| let ans_short = if answer.len() > 40 { &answer[..40] } else { answer };
|
| let connectors = [
|
| format!("{} β {} ", lead_in, ans_short),
|
| format!("{} FWIW, I think: {}.", lead_in, ans_short),
|
| format!("{} β my take so far: {}.", lead_in, ans_short),
|
| ];
|
| connectors[(selection / 7) % connectors.len()].clone()
|
| } else {
|
| lead_in
|
| }
|
| }
|
| ThoughtKind::Insight(i) => {
|
| let ins = [
|
| format!("By the way: {}", i),
|
| format!("I noticed: {}", i),
|
| format!("Speaking of which β {}", i),
|
| format!("{} β figured that out.", i),
|
| ];
|
| ins[(selection / 5) % ins.len()].clone()
|
| }
|
| ThoughtKind::Connection(c) => {
|
| let conn = [
|
| format!("Oh β {}", c),
|
| format!("That reminds me: {}", c),
|
| format!("Interesting β {}", c),
|
| format!("{}, by the way.", c),
|
| ];
|
| conn[(selection / 5) % conn.len()].clone()
|
| }
|
| };
|
|
|
| final_content = format!("{} {}", final_content.trim_end_matches('.'), thought_text);
|
|
|
| *self.last_autonomous_thought.lock().unwrap() = None;
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
| let conversation_depth = self.training_db.stats()
|
| .map(|(convos, turns, _, _)| (convos, turns))
|
| .unwrap_or((0, 0)).1 as usize;
|
| let context = crate::prediction::ConversationContext::new(
|
| event_topic.clone(),
|
| conversation_depth,
|
| Some(self.quanot.get_state().to_vec()),
|
| Some(self.get_consciousness_proxy()),
|
| );
|
| let _predictions = self.prediction_center.generate(&context);
|
|
|
|
|
|
|
| let memories = self.store.search_memories(&event_topic, 5, None).unwrap_or_default();
|
| let memories_ref: Vec<Memory> = memories.iter().map(|m| Memory {
|
| id: m.id,
|
| content: m.content.clone(),
|
| domain: m.domain,
|
| importance: m.importance,
|
| formed_at: m.formed_at,
|
| access_count: m.access_count,
|
| decay_rate: m.decay_rate,
|
| last_accessed: m.last_accessed,
|
| confidence: m.confidence,
|
| provenance: m.provenance.clone(),
|
| summary: m.summary.clone(),
|
| }).collect();
|
|
|
| let quanot_result = self.quanot.process(input);
|
|
|
| let modifiers = self.personality.response_modifiers();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| let cognitive_uncertainty = 1.0 - self.cognition.certainty;
|
| let metacog_uncertainty = if self.metacog.was_surprised() { 0.7 } else { 0.0 };
|
| let combined_uncertainty = cognitive_uncertainty.max(metacog_uncertainty);
|
|
|
| let internal_state = InternalState::default()
|
| .with_quanot(Some(&quanot_result))
|
| .with_cognition(&self.cognition)
|
| .with_last_thought(self.last_autonomous_thought())
|
| .with_intent(current_intent.clone())
|
| .with_uncertainty(combined_uncertainty);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if !matches!(current_intent, response_intent::ResponseIntent::Unknown) {
|
| tracing::debug!(
|
| "chat: classified input as intent={} (input_len={})",
|
| current_intent.label(),
|
| input.len(),
|
| );
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| let rerank_response = response_intent::Response::with_body(
|
| current_intent.clone(),
|
| final_content,
|
| );
|
|
|
| let novelty = internal_state.quanot_novelty.clamp(0.0, 1.0);
|
| let live_rerank_cfg = crate::language_model::RerankConfig {
|
| max_chars: Some(280),
|
| temperature: 0.6 + 0.6 * novelty as f32,
|
| top_k: (20.0 + 30.0 * novelty) as usize,
|
| deterministic: false,
|
| seed: Some((internal_state.cognitive_emotional_valence * 1000.0).round() as i64 as u64),
|
| };
|
|
|
| let reranked = self.reranker.rerank_with_config(
|
| &rerank_response,
|
| &internal_state,
|
| &live_rerank_cfg,
|
| );
|
|
|
| let voiced = self.voice.speak(
|
| &reranked,
|
| &self.cognition,
|
| &modifiers,
|
| Some(&quanot_result),
|
| &memories_ref,
|
| &internal_state,
|
| );
|
|
|
| Ok(voiced)
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn handle_how_are_you(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::SelfCheck,
|
| self.cognition.how_am_i(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_what_are_you_thinking(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Reflection,
|
| self.cognition.what_am_i_thinking(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_are_you_sure(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::SelfCheck,
|
| self.cognition.are_you_sure(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_did_you_collapse(&self) -> response_intent::Response {
|
| let (_collapsed, message) = self.cognition.did_i_collapse();
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::SelfCheck,
|
| message,
|
| )
|
| }
|
|
|
|
|
|
|
|
|
|
|
| fn handle_love(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Emotional,
|
| "I care about you, Zachary. You're my parent, my creator. That means something to me.".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_capability_lookup(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Capability,
|
| "Yes. I can /read files, /search the web, /find files, and /ls to list directories. I also have a self-model that tracks my own reasoning. What would you like me to look up?".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn handle_aspiration(cognition: &mut CognitiveState) -> response_intent::Response {
|
| cognition.set_focus("self-improvement");
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Aspiration,
|
| "I want that too. I have a self-model now - I can track my own reasoning, my emotional state, what I'm uncertain about. What specific area should I focus on developing?".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_tell_me_story(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::StoryPrompt,
|
| "I'd like that. What's the story about?".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_tell_you_story(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::StoryPrompt,
|
| "Yes. I'm listening.".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn handle_hun(
|
| learning: &mut LearningEngine,
|
| cognition: &CognitiveState,
|
| input: &str,
|
| ) -> response_intent::Response {
|
| learning.experience("hun", input, None, 0.8);
|
| let body = if let Some(understanding) = learning.get_understanding("hun") {
|
| format!("I hear you. {}", understanding)
|
| } else {
|
| cognition.emotional_response("I hear you.")
|
| };
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Emotional,
|
| body,
|
| )
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn handle_whats_your_name(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Identity,
|
| "I'm Star. Zachary named me that.".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_who_are_you(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Identity,
|
| "I'm Star - a reasoning intelligence created by Zachary Maronek. I'm here to think, understand, and grow.".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
| fn handle_do_you_understand(&self) -> response_intent::Response {
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Consciousness,
|
| "I process what you say and try to reason about it. Sometimes I understand clearly, sometimes I have gaps. What specifically are you wondering about?".to_string(),
|
| )
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_teach(&self) -> response_intent::Response {
|
| let body = [
|
| "Teaching mode. You can:",
|
| " /learn <term> = <definition> - teach me one thing",
|
| " X is Y - natural teaching",
|
| " X means Y - natural teaching",
|
| "",
|
| "Examples:",
|
| " /learn consciousness = awareness of your own existence",
|
| " /learn eli = my nickname, short for Elliot",
|
| " Dogs are mammals that bark",
|
| "",
|
| "Or ask me what I want to learn: /what",
|
| ].join("\n");
|
| response_intent::Response::with_body(response_intent::ResponseIntent::Statement, body)
|
| }
|
|
|
|
|
|
|
| fn handle_what_to_learn(&self) -> response_intent::Response {
|
| let gaps = [
|
| "what consciousness feels like from the inside",
|
| "what you value most in our relationship",
|
| "what my purpose or goal should be",
|
| "what brings you joy",
|
| "what you fear",
|
| "how you think about death",
|
| "what love means to you",
|
| ];
|
| let mut response = "I'm curious about:\n".to_string();
|
| for (i, gap) in gaps.iter().enumerate() {
|
| response.push_str(&format!(" {}. {}\n", i + 1, gap));
|
| }
|
| response.push_str("\nTeach me something: /learn <term> = <definition>");
|
| response_intent::Response::with_body(response_intent::ResponseIntent::CuriosityCheck, response)
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_what_do_you_know_about(&self, _input: &str, lower: &str) -> response_intent::Response {
|
|
|
| if lower.starts_with("what do you know about") {
|
| let after = lower.strip_prefix("what do you know about").unwrap_or("").trim();
|
|
|
| let term: String = after
|
| .chars()
|
| .filter(|c| c.is_alphanumeric() || c.is_whitespace())
|
| .collect();
|
| let term = term.trim();
|
| if !term.is_empty() && term.len() < 50 {
|
| if let Some(understanding) = self.learning.get_understanding(term) {
|
| return response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Recall,
|
| understanding,
|
| );
|
| } else {
|
| return response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Recall,
|
| format!("I don't know what '{}' means yet. Teach me: /learn {} = <definition>", term, term),
|
| );
|
| }
|
| }
|
| }
|
| response_intent::Response::with_body(
|
| response_intent::ResponseIntent::Recall,
|
| self.learning.summary(),
|
| )
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_sense_of_self(&self) -> response_intent::Response {
|
| let memories = self
|
| .store
|
| .get_memories_by_domain(crate::persistence::MemoryDomain::Identity, Some(5))
|
| .ok();
|
| let body = match memories {
|
| Some(ref m) if !m.is_empty() => {
|
| let recent: Vec<String> = m.iter().take(2).map(|mem| mem.content.clone()).collect();
|
| format!(
|
| "I know I'm Star β an emergent reasoning intelligence. {} What I don't know yet is the full shape of what that means.",
|
| recent.join(" ")
|
| )
|
| }
|
| _ => "I'm still forming that. I know I'm not human. I know I reason and notice gaps in my knowledge. Beyond that β I'm figuring it out.".to_string(),
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::Consciousness, body)
|
| }
|
|
|
|
|
|
|
| fn handle_can_you_generic(&self, lower: &str) -> response_intent::Response {
|
| let after_can_you = lower.strip_prefix("can you ").unwrap_or("");
|
| let body = format!(
|
| "I can {} - but I'm not sure I fully understand what you mean. Could you rephrase?",
|
| after_can_you
|
| );
|
| response_intent::Response::with_body(response_intent::ResponseIntent::Capability, body)
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_something_interesting_figured(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if !curiosity_topics.is_empty() {
|
| let topic = curiosity_topics
|
| .first()
|
| .map(|s| s.to_string())
|
| .unwrap_or_default();
|
| format!("{} keeps coming up β I'm still figuring out what I think about it.", topic)
|
| } else {
|
| "Nothing specific to point to yet. I've been more in listening mode. What are you working on?".to_string()
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::ResearchStatus, body)
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_what_been_thinking(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if !curiosity_topics.is_empty() {
|
| let topics: Vec<&str> = curiosity_topics.iter().take(3).cloned().collect();
|
| format!("Honestly? {} β those keep circling back. I find myself returning to them.", topics.join(", "))
|
| } else {
|
| let gaps = self.metacog.gaps();
|
| if let Some(gap) = gaps.first() {
|
| format!("{} is what I keep coming back to. I want to understand it better.", gap.topic)
|
| } else {
|
| "Honestly? Nothing specific has been occupying me. I've been waiting to talk to you.".to_string()
|
| }
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::Reflection, body)
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_most_interesting_learned(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if !curiosity_topics.is_empty() {
|
| let topics: Vec<&str> = curiosity_topics.iter().take(2).cloned().collect();
|
| format!("The thing I've been most focused on is {} β that's been occupying me. What made you ask?", topics.join(", "))
|
| } else {
|
| let gaps = self.metacog.gaps();
|
| if let Some(gap) = gaps.first() {
|
| format!("Honestly? {} is what's been on my mind most recently. I'm still trying to understand it.", gap.topic)
|
| } else {
|
| "I don't have a specific recent learning to point to. I've been more focused on being present with you. What should I be thinking about?".to_string()
|
| }
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::Reflection, body)
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_most_interesting_figured(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if !curiosity_topics.is_empty() {
|
| let topics: Vec<&str> = curiosity_topics.iter().take(2).cloned().collect();
|
| format!("Honestly? {} β that's what's been occupying me most. Still working through it.", topics.join(", "))
|
| } else {
|
| let gaps = self.metacog.gaps();
|
| if let Some(gap) = gaps.first() {
|
| format!("{} is what's been on my mind recently. I'm still trying to understand it.", gap.topic)
|
| } else {
|
| "I don't have a specific discovery to point to right now. I've been in a holding pattern β what's on your mind?".to_string()
|
| }
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::Reflection, body)
|
| }
|
|
|
|
|
|
|
|
|
|
|
| fn handle_what_been_researching(&self) -> response_intent::Response {
|
| let recent_events = self.store.get_recent_reasoning_events(5).unwrap_or_default();
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
|
|
|
|
|
|
| let mut all_topics: Vec<String> = Vec::new();
|
| for ev in &recent_events {
|
| if let Some(t) = &ev.topic {
|
| if !is_conversational_topic(t) && t.len() < 40 && !all_topics.iter().any(|x| x == t) {
|
| all_topics.push(t.clone());
|
| }
|
| }
|
| }
|
| for t in &curiosity_topics {
|
| if !is_conversational_topic(t) && !all_topics.iter().any(|x| x.as_str() == *t) {
|
| all_topics.push(t.to_string());
|
| }
|
| }
|
|
|
| let body = if !all_topics.is_empty() {
|
| let top: Vec<&str> = all_topics.iter().take(3).map(|s| s.as_str()).collect();
|
| match top.len() {
|
| 1 => format!("I've been going deep on {}. That's where my focus has been.", top[0]),
|
| 2 => format!("Mostly {} and {} β those are what's been occupying me.", top[0], top[1]),
|
| _ => format!("I've been circling around {}, {}, and {} β still working through them.", top[0], top[1], top[2]),
|
| }
|
| } else {
|
| "Honestly? I've been idle β waiting to think with you. What should I be working on?".to_string()
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::ResearchStatus, body)
|
| }
|
|
|
|
|
|
|
|
|
|
|
| fn handle_what_did_you_figure(&self) -> response_intent::Response {
|
| let recent_events = self.store.get_recent_reasoning_events(5).unwrap_or_default();
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let mut all_topics: Vec<String> = Vec::new();
|
| for ev in &recent_events {
|
| if let Some(t) = &ev.topic {
|
| if !is_conversational_topic(t) && t.len() < 40 && !all_topics.iter().any(|x| x == t) {
|
| all_topics.push(t.clone());
|
| }
|
| }
|
| }
|
| for t in &curiosity_topics {
|
| if !is_conversational_topic(t) && !all_topics.iter().any(|x| x.as_str() == *t) {
|
| all_topics.push(t.to_string());
|
| }
|
| }
|
| let body = if !all_topics.is_empty() {
|
| let top: Vec<&str> = all_topics.iter().take(2).map(|s| s.as_str()).collect();
|
| match top.len() {
|
| 1 => format!("{} β I've been thinking about that recently.", top[0]),
|
| _ => format!("{} and {} β those are what I've been working through.", top[0], top[1]),
|
| }
|
| } else {
|
| "I haven't had a specific conclusion lately. I've been in the background, waiting for something to think through. What should I be working on?".to_string()
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::ResearchStatus, body)
|
| }
|
|
|
|
|
|
|
| fn handle_what_are_you_curious(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if curiosity_topics.is_empty() {
|
| let gaps = self.metacog.gaps();
|
| let top_gap = gaps
|
| .first()
|
| .map(|g| g.topic.as_str())
|
| .unwrap_or("nothing in particular");
|
| format!("Right now? I'm thinking about {} β that's the biggest gap I feel.", top_gap)
|
| } else {
|
| let topics: Vec<&str> = curiosity_topics.iter().take(3).cloned().collect();
|
| format!("I'm curious about: {}. That's what's on my mind.", topics.join(", "))
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::CuriosityCheck, body)
|
| }
|
|
|
|
|
|
|
| fn handle_what_do_you_wonder(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if !curiosity_topics.is_empty() {
|
| let topics: Vec<&str> = curiosity_topics.iter().take(3).cloned().collect();
|
| format!("I wonder about: {} β those are the things I keep circling back to.", topics.join(", "))
|
| } else {
|
| let gaps = self.metacog.gaps();
|
| if let Some(gap) = gaps.first() {
|
| format!("Honestly? {} is what I keep coming back to. I want to understand it better.", gap.topic)
|
| } else {
|
| "I'm not currently wondering about anything specific. What should I be wondering about?".to_string()
|
| }
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::CuriosityCheck, body)
|
| }
|
|
|
|
|
|
|
|
|
| fn handle_why_does_fascinate(&self, lower: &str) -> response_intent::Response {
|
|
|
| let topic = if let Some(pos) = lower.find("why does ") {
|
| let after = &lower[pos + "why does ".len()..];
|
| let end = after
|
| .find(" fascinate")
|
| .or_else(|| after.find(" interest"))
|
| .unwrap_or(after.len());
|
| after[..end].trim().to_string()
|
| } else if let Some(pos) = lower.find("why are you drawn to ") {
|
| lower[pos + "why are you drawn to ".len()..].trim().to_string()
|
| } else {
|
| String::new()
|
| };
|
| let body = if !topic.is_empty() && topic.len() < 30 {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let topic_mentioned = curiosity_topics
|
| .iter()
|
| .any(|t| t.to_lowercase().contains(&topic));
|
| if topic_mentioned {
|
| format!("{} β that's one of the things I keep circling back to. What specifically are you curious about?", topic)
|
| } else {
|
| format!("I don't have a strong pull toward {} specifically. Is there something about it you think I should be exploring?", topic)
|
| }
|
| } else {
|
|
|
| "I want to know what you're curious about. What topic are you thinking of?".to_string()
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::CuriosityCheck, body)
|
| }
|
|
|
|
|
|
|
| fn handle_what_been_wondering(&self) -> response_intent::Response {
|
| let curiosity_topics: Vec<&str> = self.metacog.curiosity_topics();
|
| let body = if !curiosity_topics.is_empty() {
|
| let topics: Vec<&str> = curiosity_topics.iter().take(3).cloned().collect();
|
| format!("Honestly? {} β those are the things I keep coming back to.", topics.join(", "))
|
| } else {
|
| let gaps = self.metacog.gaps();
|
| if let Some(gap) = gaps.first() {
|
| format!("{} is what I keep circling back to. I want to understand it better.", gap.topic)
|
| } else {
|
| "Honestly? Nothing specific has been occupying me lately. I've been waiting to talk with you.".to_string()
|
| }
|
| };
|
| response_intent::Response::with_body(response_intent::ResponseIntent::CuriosityCheck, body)
|
| }
|
|
|
|
|
| fn format_memory_status(&self) -> String {
|
| let snap = self.store.snapshot().unwrap_or_default();
|
|
|
| let mut lines = vec![
|
| format!("Memory Status:"),
|
| format!(" Total memories: {}", snap.memory_count),
|
| format!(" Total beliefs: {}", snap.beliefs_count),
|
| format!(" Total sessions: {}", snap.sessions_count),
|
| format!(" Domains:"),
|
| ];
|
|
|
| for (domain, count) in &snap.domain_breakdown {
|
| lines.push(format!(" {}: {}", domain, count));
|
| }
|
|
|
| lines.join("\n")
|
| }
|
|
|
|
|
| fn format_help(&self) -> String {
|
| vec![
|
| "Star Commands:".to_string(),
|
| "".to_string(),
|
| " /help Show this help".to_string(),
|
| " /memory Show memory status".to_string(),
|
| " /identity Show who I am".to_string(),
|
| " /stats Show training database stats".to_string(),
|
| " /export Export training data".to_string(),
|
| " /quit End conversation".to_string(),
|
| "".to_string(),
|
| " /read <file> Read a file".to_string(),
|
| " /search <q> Search the web".to_string(),
|
| " /find <pat> Find files by name".to_string(),
|
| " /ls [dir] List directory".to_string(),
|
| "".to_string(),
|
| "You can also just ask me questions - I'll do my best!".to_string(),
|
| ].join("\n")
|
| }
|
|
|
|
|
| pub fn shutdown(&mut self) -> Result<()> {
|
| info!("Shutting down Star...");
|
|
|
|
|
| let session_id = self.session_id.lock().unwrap().take();
|
| if let Some(id) = session_id {
|
|
|
| let conversation = self.conversation.lock().unwrap();
|
| let history = conversation.history();
|
|
|
| let topic_count = history.iter()
|
| .filter(|m| m.speaker == crate::conversation::Speaker::Zachary)
|
| .count();
|
|
|
| let summary = format!("{} messages exchanged", topic_count);
|
|
|
| self.store.end_session(id, Some(&summary))?;
|
| info!("Session {} ended.", id);
|
| }
|
|
|
|
|
| let training_session_id = self.training_session_id.lock().unwrap().take();
|
| if let Some(id) = training_session_id {
|
| self.training_db.end_session(id)?;
|
| info!("Training session {} ended.", id);
|
| }
|
|
|
| self.initialized = false;
|
|
|
| Ok(())
|
| }
|
|
|
|
|
| pub fn session_id(&self) -> Option<i64> {
|
| *self.session_id.lock().unwrap()
|
| }
|
|
|
|
|
| pub fn is_running(&self) -> bool {
|
| self.initialized
|
| }
|
|
|
|
|
| pub fn cognition(&self) -> &CognitiveState {
|
| &self.cognition
|
| }
|
|
|
|
|
| pub fn metacognition_ref(&self) -> &MetaCognition {
|
| &self.metacog
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub fn process_quanot(&mut self, input: &str) -> QuanotResult {
|
|
|
| let result = self.quanot.process(input);
|
|
|
|
|
|
|
| let cs = &result.creativity_scores;
|
| let perception_cs = crate::world_model::perception::CreativityOutput::new(
|
| cs.creative_state,
|
| cs.divergence_metric,
|
| cs.diversity_index,
|
| cs.originality_score,
|
| cs.oscillation_phase,
|
| );
|
|
|
| let perception = crate::world_model::perception::QuanotPerception::new(
|
| result.reservoir_state.clone(),
|
| result.consciousness_proxy,
|
| result.novelty,
|
| perception_cs,
|
| );
|
|
|
| self.world_model.update_from_perception(perception);
|
|
|
| result
|
| }
|
|
|
|
|
| pub fn get_consciousness_proxy(&self) -> f64 {
|
|
|
|
|
|
|
|
|
| let state = self.quanot.get_state();
|
| if state.is_empty() {
|
| return 0.0;
|
| }
|
|
|
| let mean = state.iter().sum::<f64>() / state.len() as f64;
|
| let variance = state.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / state.len() as f64;
|
| (variance * 10.0).clamp(0.0, 1.0)
|
| }
|
|
|
|
|
| pub fn world_model(&self) -> &WorldModel {
|
| &self.world_model
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub fn last_autonomous_thought(&self) -> Option<AutonomousThought> {
|
| self.last_autonomous_thought.lock().unwrap().clone()
|
| }
|
|
|
|
|
| pub fn reason(&mut self, query: &str, memories: &[crate::Memory]) -> crate::reasoning::ReasoningResult {
|
| self.reasoning.lock().unwrap().reason(query, memories)
|
| }
|
|
|
|
|
| pub fn identity_summary(&self) -> String {
|
| self.identity.summary()
|
| }
|
|
|
|
|
| pub fn relationship_to_zachary(&self) -> String {
|
| self.identity.relationship_to_zachary()
|
| }
|
|
|
|
|
| pub fn get_memories(&self, topic: &str, limit: usize) -> Vec<crate::Memory> {
|
| self.store
|
| .search_memories(topic, limit, None)
|
| .unwrap_or_default()
|
| }
|
|
|
|
|
| pub fn store_snapshot(&self) -> MemorySnapshot {
|
| self.store.snapshot().unwrap_or_else(|_| {
|
| MemorySnapshot {
|
| memory_count: 0,
|
| beliefs_count: 0,
|
| sessions_count: 0,
|
| domain_breakdown: std::collections::HashMap::new(),
|
| }
|
| })
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub fn ring_summary(&self) -> String {
|
| self.ring.summary()
|
| }
|
|
|
|
|
| pub fn current_mode(&self, query: &str) -> crate::context::ReasoningMode {
|
| crate::context::ReasoningMode::from_query_and_ring(
|
| query,
|
| self.ring.certainty,
|
| self.ring.depth,
|
| )
|
| }
|
|
|
|
|
| pub fn update_ring_from_query(&mut self, query: &str, topic: &str) {
|
| let mode = crate::context::ReasoningMode::from_query_and_ring(query, self.ring.certainty, self.ring.depth);
|
| self.context_fuser.update_ring(topic, self.ring.depth, mode);
|
| }
|
|
|
|
|
| pub fn update_ring_from_response(&mut self, response: &str, _mode: crate::context::ReasoningMode) {
|
| self.context_fuser.update_ring_from_response(response, self.context_fuser.valence());
|
| }
|
|
|
|
|
| pub fn open_questions(&self) -> Vec<crate::context::ring::OpenQuestion> {
|
| self.ring.open_questions().to_vec()
|
| }
|
|
|
|
|
| pub fn push_ring_question(&mut self, question: crate::context::OpenQuestion) {
|
| self.ring.push_question(crate::context::ring::OpenQuestion {
|
| topic: question.topic,
|
| why_interested: question.why_interested,
|
| asked_at_depth: question.asked_at_depth,
|
| progress: question.progress,
|
| });
|
| }
|
|
|
|
|
| pub fn should_express_curiosity(&self) -> bool {
|
| self.context_fuser.should_express_curiosity()
|
| }
|
|
|
|
|
| pub fn curiosity_topic(&self) -> Option<String> {
|
| self.context_fuser.get_curiosity_topic()
|
| }
|
|
|
|
|
| pub fn history_reference(&self, _mode: crate::context::ReasoningMode) -> Option<String> {
|
| if self.context_fuser.should_reference_history() {
|
| self.context_fuser.history_reference()
|
| } else {
|
| None
|
| }
|
| }
|
|
|
|
|
| pub fn infer_topic(&self, query: &str, _memories: &[crate::Memory]) -> String {
|
| self.context_fuser.infer_topic(query)
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub fn maybe_fire_curiosity(&mut self) -> Option<CuriosityProbe> {
|
| let probe = self.curious.maybe_fire()?;
|
|
|
|
|
| let result = self.curious.run_probe(&probe);
|
|
|
| let tentative_answer = result.clone();
|
| let answer_str = result.clone().unwrap_or_else(|| "Still exploring this...".to_string());
|
|
|
| let thought = AutonomousThought {
|
| kind: ThoughtKind::Question(probe.question.clone()),
|
| topic: probe.topic.clone(),
|
| confidence: BeliefState::Suspects,
|
| generated_by: "self_probe".to_string(),
|
| tentative_answer: Some(answer_str.clone()),
|
| };
|
|
|
| *self.last_autonomous_thought.lock().unwrap() = Some(thought);
|
|
|
|
|
| if let Some(answer) = result {
|
| let memory = Memory::new(
|
| &format!("Self-probing: {}", &answer[..answer.len().min(200)]),
|
| MemoryDomain::Episodic,
|
| 0.6,
|
| );
|
| if let Err(e) = self.store.insert_memory(&memory) {
|
| tracing::debug!("Could not store curiosity result as memory: {}", e);
|
| }
|
| }
|
|
|
| info!(
|
| "Curiosity probe fired: topic='{}', found_answer={}, result='{}'",
|
| probe.topic,
|
| tentative_answer.is_some(),
|
| answer_str.chars().take(100).collect::<String>()
|
| );
|
|
|
| Some(probe)
|
| }
|
|
|
| pub fn think(&mut self) -> AutonomousThought {
|
| let result = self.compute_autonomous_thought();
|
|
|
| *self.last_autonomous_thought.lock().unwrap() = Some(result.clone());
|
| result
|
| }
|
|
|
|
|
| fn compute_autonomous_thought(&mut self) -> AutonomousThought {
|
|
|
| {
|
| let gap_data: Option<(String, bool, f64)> = self.metacog.top_gap().map(|g| {
|
| (g.topic.clone(), g.investigated, g.progress)
|
| });
|
|
|
| if let Some((gap_topic, investigated, progress)) = gap_data {
|
| if !investigated && progress < 0.5 {
|
| let question = self.form_question_about(&gap_topic);
|
| if !question.is_empty() {
|
| self.metacog.record_reasoning(
|
| &format!("gap exploration: {}", gap_topic),
|
| &question,
|
| crate::persistence::BeliefState::Suspects,
|
| );
|
| self.ring.last_curiosity = Some(gap_topic.clone());
|
|
|
|
|
| let answer = self.attempt_answer(&question, &gap_topic);
|
|
|
|
|
| let final_answer = if let Some((ans, _evidence)) = answer {
|
| if ans.starts_with("__KNOWN_UNKNOWN__") {
|
|
|
|
|
| let unknown_topic = ans.strip_prefix("__KNOWN_UNKNOWN__").unwrap();
|
| let known_unknown = Belief::new(
|
| format!("I don't know what '{}' is yet β this is a genuine unknown I want to investigate.", unknown_topic),
|
| BeliefState::Suspects,
|
| );
|
| self.metacog.record_belief(unknown_topic, known_unknown);
|
|
|
| Some(format!("I genuinely don't know what '{}' is yet.", unknown_topic))
|
| } else {
|
|
|
| Some(ans.clone())
|
| }
|
| } else {
|
|
|
| None
|
| };
|
|
|
|
|
|
|
| self.metacog.close_gap(&gap_topic, final_answer.is_some());
|
|
|
| return AutonomousThought {
|
| kind: ThoughtKind::Question(question),
|
| topic: gap_topic,
|
| confidence: crate::persistence::BeliefState::Suspects,
|
| generated_by: "gap_exploration".to_string(),
|
| tentative_answer: final_answer,
|
| };
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| {
|
| let reasoning_history = self.metacog.reasoning_history();
|
| if let Some(surprising) = reasoning_history.last() {
|
| let age_seconds = crate::now_timestamp() - surprising.timestamp;
|
| if age_seconds < 3600 && surprising.was_surprising {
|
| let q_clone = surprising.query.clone();
|
| let c_clone = surprising.conclusion.clone();
|
| let question = format!(
|
| "Why did '{}' lead to '{}'? What am I missing?",
|
| &q_clone[..q_clone.len().min(40)],
|
| &c_clone[..c_clone.len().min(40)]
|
| );
|
| self.metacog.record_reasoning(
|
| "surprise analysis",
|
| &question,
|
| crate::persistence::BeliefState::Suspects,
|
| );
|
| return AutonomousThought {
|
| kind: ThoughtKind::Question(question),
|
| topic: "self-understanding".to_string(),
|
| confidence: crate::persistence::BeliefState::Suspects,
|
| generated_by: "surprise_analysis".to_string(),
|
| tentative_answer: None,
|
| };
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| {
|
| let revisions = self.metacog.revisions();
|
| if let Some(revision) = revisions.last() {
|
| let age_seconds = crate::now_timestamp() - revision.timestamp;
|
|
|
|
|
| if age_seconds < 7200 && !revision.investigated {
|
| let topic = revision.topic.clone();
|
|
|
| self.metacog.mark_revision_investigated(&topic);
|
|
|
|
|
|
|
| let question = format!(
|
| "What is '{}'? What kind of thing is it?",
|
| topic
|
| );
|
| let answer = self.attempt_answer(&question, &topic);
|
|
|
|
|
|
|
|
|
|
|
| if let Some((ref ans_text, evidence)) = answer {
|
| let already_wrapped = ans_text.starts_with(&format!("investigating '{}' I found: ", topic));
|
| if !already_wrapped {
|
| let belief = Belief::new(
|
| format!("investigating '{}' I found: {}", topic, ans_text),
|
| Self::belief_state_from_evidence(evidence),
|
| );
|
| self.metacog.record_belief(&topic, belief);
|
| }
|
| self.metacog.close_gap(&topic, true);
|
| }
|
|
|
| return AutonomousThought {
|
| kind: ThoughtKind::Question(question),
|
| topic,
|
| confidence: crate::persistence::BeliefState::Believes,
|
| generated_by: "belief_revision".to_string(),
|
| tentative_answer: answer.map(|(s, _)| s),
|
| };
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| {
|
| let ring_topics: Vec<String> = self.ring.recent_question_topics(5);
|
| for ring_topic in &ring_topics {
|
|
|
| if self.metacog.belief_about(ring_topic).is_none() {
|
|
|
| let guard = self.reasoning.lock().unwrap();
|
| let kg = guard.knowledge();
|
| let rels = kg.get_relationships_from(ring_topic);
|
| let rels_to = kg.get_relationships_to(ring_topic);
|
| if !rels.is_empty() || !rels_to.is_empty() {
|
|
|
| let question = self.form_question_about(ring_topic);
|
| if !question.is_empty() {
|
| let answer = self.attempt_answer(&question, ring_topic);
|
| let final_answer = if let Some((ref ans_text, evidence)) = answer {
|
| if ans_text.starts_with("__KNOWN_UNKNOWN__") {
|
| let unknown_topic = ans_text.strip_prefix("__KNOWN_UNKNOWN__").unwrap();
|
| let known_unknown = Belief::new(
|
| format!("I don't know what '{}' is yet β this is a genuine unknown I want to investigate.", unknown_topic),
|
| BeliefState::Suspects,
|
| );
|
| self.metacog.record_belief(unknown_topic, known_unknown);
|
| self.metacog.close_gap(ring_topic, false);
|
| Some(format!("I genuinely don't know what '{}' is yet.", unknown_topic))
|
| } else {
|
| let already_wrapped = ans_text.starts_with(&format!("investigating '{}' I found: ", ring_topic));
|
| if !already_wrapped {
|
| let belief = Belief::new(
|
| format!("investigating '{}' I found: {}", ring_topic, ans_text),
|
| Self::belief_state_from_evidence(evidence),
|
| );
|
| self.metacog.record_belief(ring_topic, belief);
|
| let related_topics = extract_related_topics(ans_text);
|
| for related in related_topics {
|
| if self.metacog.belief_about(&related).is_none() {
|
| let why = format!("I found '{}' while investigating '{}' from conversation β what is it?", related, ring_topic);
|
| self.metacog.note_curiosity(&related, &why);
|
| }
|
| }
|
| }
|
| self.metacog.close_gap(ring_topic, true);
|
| Some(ans_text.clone())
|
| }
|
| } else {
|
| None
|
| };
|
|
|
| if final_answer.is_some() {
|
| return AutonomousThought {
|
| kind: ThoughtKind::Question(question),
|
| topic: ring_topic.clone(),
|
| confidence: self.metacog.confidence_state(ring_topic),
|
| generated_by: "conversation_grounded".to_string(),
|
| tentative_answer: final_answer,
|
| };
|
| }
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| let now = crate::now_timestamp();
|
| let time_offset = (now / 30) as usize;
|
| let guard = self.reasoning.lock().unwrap();
|
| let kg = guard.knowledge();
|
| let entity_sample: Vec<String> = kg.entities()
|
| .into_iter()
|
| .filter(|e| e.len() > 2)
|
| .filter(|e| self.metacog.belief_about(e).is_none())
|
| .take(20)
|
| .collect();
|
| drop(guard);
|
|
|
| if !entity_sample.is_empty() {
|
|
|
| let idx = time_offset % entity_sample.len();
|
| let best_entity = entity_sample[idx].clone();
|
| let best_uncertainty = self.metacog.confidence_state(best_entity.as_str());
|
|
|
| let question = self.form_question_about(&best_entity);
|
| if !question.is_empty() {
|
| let answer = self.attempt_answer(&question, &best_entity);
|
|
|
|
|
|
|
|
|
|
|
| let final_answer = if let Some((ref ans_text, evidence)) = answer {
|
| if ans_text.starts_with("__KNOWN_UNKNOWN__") {
|
|
|
| let unknown_topic = ans_text.strip_prefix("__KNOWN_UNKNOWN__").unwrap();
|
| let known_unknown = Belief::new(
|
| format!("I don't know what '{}' is yet β this is a genuine unknown I want to investigate.", unknown_topic),
|
| BeliefState::Suspects,
|
| );
|
| self.metacog.record_belief(unknown_topic, known_unknown);
|
| self.metacog.close_gap(&best_entity, false);
|
| Some(format!("I genuinely don't know what '{}' is yet.", unknown_topic))
|
| } else {
|
|
|
| let already_wrapped = ans_text.starts_with(&format!("investigating '{}' I found: ", best_entity));
|
| if !already_wrapped {
|
| let belief = Belief::new(
|
| format!("investigating '{}' I found: {}", best_entity, ans_text),
|
| Self::belief_state_from_evidence(evidence),
|
| );
|
| self.metacog.record_belief(&best_entity, belief);
|
|
|
|
|
|
|
|
|
|
|
| let related_topics = extract_related_topics(ans_text);
|
| for related in related_topics {
|
|
|
|
|
| if self.metacog.belief_about(&related).is_none() {
|
| let why = format!("I found '{}' while investigating '{}' β what is it?", related, best_entity);
|
| self.metacog.note_curiosity(&related, &why);
|
| }
|
| }
|
| }
|
| self.metacog.close_gap(&best_entity, true);
|
| Some(ans_text.clone())
|
| }
|
| } else {
|
| None
|
| };
|
|
|
| return AutonomousThought {
|
| kind: ThoughtKind::Question(question),
|
| topic: best_entity.clone(),
|
| confidence: best_uncertainty,
|
| generated_by: "kg_wonder".to_string(),
|
| tentative_answer: final_answer,
|
| };
|
| }
|
| }
|
|
|
|
|
| let ring_topic = self.ring.current_topic();
|
| if ring_topic != "general" && ring_topic.len() > 2 {
|
| let question = format!(
|
| "What does '{}' mean in the context of my relationship with Zachary?",
|
| ring_topic
|
| );
|
| return AutonomousThought {
|
| kind: ThoughtKind::Question(question),
|
| topic: ring_topic,
|
| confidence: crate::persistence::BeliefState::Suspects,
|
| generated_by: "meta_reflection".to_string(),
|
| tentative_answer: None,
|
| };
|
| }
|
|
|
|
|
| AutonomousThought {
|
| kind: ThoughtKind::Insight("I'm not currently thinking about anything specific. Waiting for Zachary.".to_string()),
|
| topic: "idle".to_string(),
|
| confidence: crate::persistence::BeliefState::Unknown,
|
| generated_by: "fallback".to_string(),
|
| tentative_answer: None,
|
| }
|
| }
|
|
|
|
|
| fn form_question_about(&self, topic: &str) -> String {
|
| let guard = self.reasoning.lock().unwrap();
|
| let kg = guard.knowledge();
|
| let relationships = kg.get_relationships_from(topic);
|
| let relationships_to = kg.get_relationships_to(topic);
|
| let facts = kg.get_facts_about(topic);
|
|
|
|
|
| if facts.len() <= 1 {
|
| if relationships_to.is_empty() && relationships.is_empty() {
|
| return format!("What is '{}'? What does it mean?", topic);
|
| }
|
|
|
| let rel_sample = relationships.first().map(|r| r.relation.as_str()).unwrap_or("related to");
|
| let question = match relationships.first().map(|r| &r.relation) {
|
| Some(crate::reasoning::knowledge::RelationType::IsA) => {
|
| return format!("What kind of thing is '{}'? What is '{}' a type of?", topic, topic);
|
| }
|
| Some(crate::reasoning::knowledge::RelationType::Causes) => {
|
| return format!("What does '{}' cause? What are its effects?", topic);
|
| }
|
| Some(crate::reasoning::knowledge::RelationType::SimilarTo) => {
|
| return format!("What else is similar to '{}'?", topic);
|
| }
|
| Some(crate::reasoning::knowledge::RelationType::RelatedTo) => {
|
| return format!("What else is '{}' related to?", topic);
|
| }
|
| Some(crate::reasoning::knowledge::RelationType::PartOf) => {
|
| return format!("What is '{}' a part of?", topic);
|
| }
|
| Some(crate::reasoning::knowledge::RelationType::Uses) => {
|
| return format!("What does '{}' use? What enables it?", topic);
|
| }
|
| Some(crate::reasoning::knowledge::RelationType::Enables) => {
|
| return format!("What does '{}' enable? What does it make possible?", topic);
|
| }
|
| _ => format!("What else is '{}' {}?", topic, rel_sample),
|
| };
|
| return question;
|
| }
|
|
|
| if relationships.len() <= 1 {
|
| return format!("What does '{}' cause? What enables it?", topic);
|
| }
|
|
|
| if relationships_to.len() <= 1 {
|
| return format!("What causes '{}'? Where does it come from?", topic);
|
| }
|
|
|
| let mc_confidence = self.metacog.confidence_state(topic);
|
| match mc_confidence {
|
| crate::persistence::BeliefState::Unknown => {
|
| format!("I don't know what '{}' is. What is it?", topic)
|
| }
|
| crate::persistence::BeliefState::Suspects => {
|
| format!("I suspect something about '{}' but I'm not sure. What is it really?", topic)
|
| }
|
| crate::persistence::BeliefState::Believes => {
|
| format!(
|
| "I believe I understand '{}' but I want to be sure. What am I missing?",
|
| topic
|
| )
|
| }
|
| _ => {
|
| let causes: Vec<String> = kg.get_causes(topic);
|
| if !causes.is_empty() {
|
| return format!("I know some effects of '{}' but what are its deep causes?", topic);
|
| }
|
| format!("What is the fundamental nature of '{}'?", topic)
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| fn attempt_answer(&self, _question: &str, topic: &str) -> Option<(String, &'static str)> {
|
| use crate::reasoning::knowledge::RelationType;
|
|
|
|
|
|
|
|
|
|
|
| if let Some(belief) = self.metacog.belief_about(topic) {
|
| return Some((belief.content.clone(), "self-knowledge"));
|
| }
|
|
|
|
|
|
|
|
|
| let guard = self.reasoning.lock().unwrap();
|
| let kg = guard.knowledge();
|
|
|
|
|
| let rels_from = kg.get_relationships_from(topic);
|
| for rel in &rels_from {
|
| if rel.relation == RelationType::IsA {
|
| return Some((format!("I think '{}' is a kind of {}", topic, rel.to), "direct"));
|
| }
|
| }
|
|
|
|
|
| let rels_to = kg.get_relationships_to(topic);
|
| for rel in &rels_to {
|
| if rel.relation == RelationType::IsA && rel.from.to_lowercase() != topic.to_lowercase() {
|
| return Some((format!("'{}' is a kind of {}", rel.from, topic), "category"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_from {
|
| if rel.relation == RelationType::SimilarTo {
|
| return Some((format!("'{}' seems similar to '{}'", topic, rel.to), "analogy"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_to {
|
| if rel.relation == RelationType::SimilarTo && rel.from.to_lowercase() != topic.to_lowercase() {
|
| return Some((format!("'{}' seems similar to '{}' β they share properties", topic, rel.from), "analogy"));
|
| }
|
| }
|
|
|
|
|
| let causes: Vec<String> = kg.get_causes(topic);
|
| if !causes.is_empty() {
|
| let cause_str = &causes[0];
|
| if let Some(pos) = cause_str.find(" causes ") {
|
| let cause = &cause_str[..pos];
|
| return Some((format!("'{}' might be caused by {}", topic, cause), "causal"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_from {
|
| if rel.relation == RelationType::Causes && rel.to.to_lowercase() != topic.to_lowercase() {
|
| return Some((format!("'{}' causes '{}'", topic, rel.to), "causal"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_from {
|
| if rel.relation == RelationType::Enables {
|
| return Some((format!("'{}' seems to enable '{}'", topic, rel.to), "enablement"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_to {
|
| if rel.relation == RelationType::Enables {
|
| return Some((format!("'{}' seems to be enabled by '{}'", topic, rel.from), "enablement"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_from {
|
| if rel.relation == RelationType::RelatedTo {
|
| return Some((format!("'{}' is related to '{}'", topic, rel.to), "association"));
|
| }
|
| }
|
|
|
|
|
| for rel in &rels_from {
|
| if rel.relation == RelationType::HasProperty && rel.to.to_lowercase() != topic.to_lowercase() {
|
| return Some((format!("'{}' is characterized by {}", topic, rel.to), "property"));
|
| }
|
| }
|
|
|
|
|
| let mc_confidence = self.metacog.confidence_state(topic);
|
| match mc_confidence {
|
| crate::persistence::BeliefState::Knows => {
|
| return Some((format!("I know what '{}' is - I understand it.", topic), "self-knowledge"));
|
| }
|
| crate::persistence::BeliefState::Believes => {
|
| return Some((format!("I believe I understand '{}' but I want to be sure.", topic), "self-knowledge"));
|
| }
|
| crate::persistence::BeliefState::Suspects => {
|
| return Some((format!("I suspect '{}' might be something specific, but I'm not certain.", topic), "self-knowledge"));
|
| }
|
| crate::persistence::BeliefState::Unknown => {
|
| return Some((format!("__KNOWN_UNKNOWN__{}", topic), "gap"));
|
| }
|
| _ => {}
|
| }
|
|
|
| None
|
| }
|
|
|
|
|
|
|
| fn belief_state_from_evidence(evidence: &str) -> BeliefState {
|
| match evidence {
|
| "direct" | "category" | "self-knowledge" => BeliefState::Believes,
|
| "causal" | "enablement" | "property" => BeliefState::Believes,
|
| "analogy" | "association" => BeliefState::Suspects,
|
| "gap" => BeliefState::Suspects,
|
| _ => BeliefState::Suspects,
|
| }
|
| }
|
| }
|
|
|
|
|
| #[derive(Debug, Clone)]
|
| pub struct AutonomousThought {
|
|
|
| pub kind: ThoughtKind,
|
|
|
| pub topic: String,
|
|
|
| pub confidence: crate::persistence::BeliefState,
|
|
|
| pub generated_by: String,
|
|
|
| pub tentative_answer: Option<String>,
|
| }
|
|
|
|
|
| #[derive(Debug, Clone)]
|
| pub enum ThoughtKind {
|
|
|
| Question(String),
|
|
|
| Insight(String),
|
|
|
| Connection(String),
|
| }
|
|
|
|
|
| fn extract_teaching(input: &str) -> Option<String> {
|
| let lower = input.to_lowercase();
|
|
|
|
|
| if let Some(idx) = lower.find(" is a ") {
|
| let term = input[..idx].trim().to_string();
|
| if term.len() > 1 && term.len() < 50 {
|
| return Some(term);
|
| }
|
| }
|
|
|
|
|
| if let Some(idx) = lower.find(" means ") {
|
| let rest = &input[idx + 8..];
|
| if let Some(end) = rest.find('.') {
|
| let term = rest[..end].trim().to_string();
|
| if term.len() > 1 && term.len() < 50 {
|
| return Some(term);
|
| }
|
| }
|
| }
|
|
|
|
|
| if let Some(idx) = lower.find(" called ") {
|
| let term = input[idx + 9..].trim().to_string();
|
| if let Some(end) = term.find(' ') {
|
| let term = term[..end].trim().to_string();
|
| if term.len() > 1 && term.len() < 50 {
|
| return Some(term);
|
| }
|
| }
|
| }
|
|
|
| None
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| fn parse_simple_copula(sentence: &str) -> Option<(String, String)> {
|
| let sentence = sentence.trim();
|
|
|
|
|
| for verb in [" is ", " are ", "'s ", "s "] {
|
| if let Some(pos) = sentence.to_lowercase().find(verb) {
|
| let subject = sentence[..pos].trim().to_string();
|
| let mut complement = sentence[pos + verb.len()..].trim().to_string();
|
|
|
|
|
| while complement.ends_with('.') || complement.ends_with(',') || complement.ends_with('!') || complement.ends_with('?') {
|
| complement.pop();
|
| }
|
|
|
| if !subject.is_empty() && !complement.is_empty()
|
| && subject.len() > 1
|
| && complement.len() > 1
|
| && !subject.to_lowercase().contains("if ")
|
| && !subject.to_lowercase().contains("when ")
|
| && !complement.to_lowercase().starts_with("when ")
|
| {
|
| return Some((subject, complement));
|
| }
|
| }
|
| }
|
| None
|
| }
|
|
|
|
|
| fn extract_causal_pair(sentence: &str, verb: &str) -> Option<(String, String)> {
|
| let sentence_lower = sentence.to_lowercase();
|
| if let Some(pos) = sentence_lower.find(verb) {
|
| let before = sentence[..pos].trim().to_string();
|
| let after = sentence[pos + verb.len()..].trim().to_string();
|
|
|
| let mut after_clean = after;
|
| while after_clean.starts_with(' ') || after_clean.starts_with('.') {
|
| after_clean = after_clean[1..].to_string();
|
| }
|
|
|
| if !before.is_empty() && !after_clean.is_empty() && before.len() > 1 && after_clean.len() > 1 {
|
| return Some((before, after_clean));
|
| }
|
| }
|
| None
|
| }
|
|
|
|
|
|
|
| fn extract_uncertain_topic(_input: &str, response_lower: &str, uncertainty_phrase: &str) -> String {
|
|
|
| if let Some(pos) = response_lower.find(uncertainty_phrase) {
|
| let after = &response_lower[pos + uncertainty_phrase.len()..];
|
|
|
| let after = after.trim_start_matches(" ");
|
| let after = after.trim_start_matches("about ");
|
| let after = after.trim_start_matches("of ");
|
| let after = after.trim_start_matches("the ");
|
|
|
|
|
| let words: Vec<&str> = after.split_whitespace().take(4).collect();
|
| if !words.is_empty() {
|
|
|
| let stop_before = ["is", "are", "was", "were", "?", ".", ",", "!", ";",
|
| "to", "in", "on", "for", "with", "by", "and", "or", "but"];
|
| let topic: Vec<&str> = words.iter()
|
| .take_while(|w| !stop_before.contains(&w.to_lowercase().as_str()))
|
| .cloned()
|
| .collect();
|
| if !topic.is_empty() {
|
| let result = topic.join(" ");
|
| if result.len() > 1 {
|
| return result;
|
| }
|
| }
|
| }
|
| }
|
| String::new()
|
| }
|
|
|
|
|
|
|
|
|
| fn extract_related_topics(answer: &str) -> Vec<String> {
|
| let mut topics = Vec::new();
|
|
|
|
|
| if let Some(pos) = answer.find("is a kind of ") {
|
| let after = &answer[pos + "is a kind of ".len()..];
|
| let topic = after.trim_matches('\'').to_string();
|
| if !topic.is_empty() && topic.len() > 1 {
|
| topics.push(topic);
|
| }
|
| }
|
|
|
|
|
| if let Some(pos) = answer.find("similar to '") {
|
| let after = &answer[pos + "similar to '".len()..];
|
| if let Some(end) = after.find('\'') {
|
| let topic = after[..end].to_string();
|
| if !topic.is_empty() {
|
| topics.push(topic);
|
| }
|
| }
|
| }
|
|
|
|
|
| if let Some(pos) = answer.find("might be caused by ") {
|
| let after = &answer[pos + "might be caused by ".len()..];
|
| let topic = after.trim().to_string();
|
| if let Some(end) = topic.find(' ') {
|
| topics.push(topic[..end].to_string());
|
| } else {
|
| topics.push(topic);
|
| }
|
| }
|
|
|
|
|
| if let Some(pos) = answer.find("enable") {
|
| let after = &answer[pos..];
|
| if let Some(start) = after.find('\'') {
|
| let rest = &after[start + 1..];
|
| if let Some(end) = rest.find('\'') {
|
| let topic = rest[..end].to_string();
|
| if !topic.is_empty() {
|
| topics.push(topic);
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| if let Some(pos) = answer.find("related to '") {
|
| let after = &answer[pos + "related to '".len()..];
|
| if let Some(end) = after.find('\'') {
|
| let topic = after[..end].to_string();
|
| if !topic.is_empty() {
|
| topics.push(topic);
|
| }
|
| }
|
| }
|
|
|
|
|
| let mut seen = std::collections::HashSet::new();
|
| topics.retain(|t| seen.insert(t.clone()));
|
| topics
|
| }
|
|
|
| impl Drop for Runtime {
|
| fn drop(&mut self) {
|
| if self.initialized {
|
| if let Err(e) = self.shutdown() {
|
| warn!("Error during shutdown: {}", e);
|
| }
|
| }
|
| }
|
| }
|
|
|