-
Notifications
You must be signed in to change notification settings - Fork 0
fix: wire memory system — was completely broken (never initialized) #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -387,6 +387,22 @@ async fn run_tui( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !hooks.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rt.set_hooks(mc_tools::HookEngine::new(hooks)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Initialize persistent memory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let memory_path = std::env::var_os("HOME").map(|h| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cwd = std::env::current_dir().unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let project_hash = format!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "{:x}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cwd.to_string_lossy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .bytes() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .fold(0u64, |h, b| h.wrapping_mul(31).wrapping_add(u64::from(b))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::path::PathBuf::from(h) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .join(".local/share/magic-code/memory") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .join(format!("{project_hash}.json")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(ref path) = memory_path { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rt.set_memory(mc_core::MemoryStore::load(path, 200)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+390
to
+405
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Memory is only initialized for TUI; single-run mode remains unconfigured. This block fixes Proposed fix async fn run_single(
@@
) -> Result<()> {
@@
let mut runtime =
mc_core::ConversationRuntime::new(model.to_string(), max_tokens, system.to_string());
+ if let Some(path) = std::env::var_os("HOME").map(|h| {
+ let cwd = std::env::current_dir().unwrap_or_default();
+ let project_hash = format!(
+ "{:x}",
+ cwd.to_string_lossy()
+ .bytes()
+ .fold(0u64, |h, b| h.wrapping_mul(31).wrapping_add(u64::from(b)))
+ );
+ std::path::PathBuf::from(h)
+ .join(".local/share/magic-code/memory")
+ .join(format!("{project_hash}.json"))
+ }) {
+ runtime.set_memory(mc_core::MemoryStore::load(&path, 200));
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Load hierarchical instructions (CLAUDE.md, AGENTS.md from root to cwd) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cwd = std::env::current_dir().unwrap_or_default(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let instructions = mc_config::load_hierarchical_instructions(&cwd); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1070,7 +1086,40 @@ async fn run_tui( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PendingCommand::Memory(cmd) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push(format!("📌 memory: {cmd}")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Ok(mut rt) = runtime.try_lock() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let parts: Vec<&str> = cmd.splitn(3, ' ').collect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| match parts.first().copied().unwrap_or("list") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "list" | "" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = rt.memory_read(&serde_json::json!({})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push("📌 Project Memory:".into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "get" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let key = parts.get(1).copied().unwrap_or(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = rt.memory_read(&serde_json::json!({"key": key})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "set" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let key = parts.get(1).copied().unwrap_or(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let value = parts.get(2).copied().unwrap_or(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = rt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .memory_write(&serde_json::json!({"key": key, "value": value})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "delete" => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let key = parts.get(1).copied().unwrap_or(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = rt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .memory_write(&serde_json::json!({"key": key, "delete": true})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1097
to
+1113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate memory keys for
Proposed fix "get" => {
let key = parts.get(1).copied().unwrap_or("");
+ if key.is_empty() {
+ app.output_lines.push("Usage: /memory get <key>".into());
+ continue;
+ }
let output = rt.memory_read(&serde_json::json!({"key": key}));
app.output_lines.push(output);
}
"set" => {
let key = parts.get(1).copied().unwrap_or("");
let value = parts.get(2).copied().unwrap_or("");
+ if key.is_empty() {
+ app.output_lines.push("Usage: /memory set <key> <value>".into());
+ continue;
+ }
let output = rt
.memory_write(&serde_json::json!({"key": key, "value": value}));
app.output_lines.push(output);
}
"delete" => {
let key = parts.get(1).copied().unwrap_or("");
+ if key.is_empty() {
+ app.output_lines.push("Usage: /memory delete <key>".into());
+ continue;
+ }
let output = rt
.memory_write(&serde_json::json!({"key": key, "delete": true}));
app.output_lines.push(output);
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push("Usage: /memory [list|get <key>|set <key> <value>|delete <key>]".into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .push("Memory not available (runtime busy)".into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PendingCommand::ThinkingToggle => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.output_lines.push("💭 Thinking toggled".into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1850,6 +1899,8 @@ fn build_system_prompt(project: &mc_config::ProjectContext) -> String { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - `lsp_query`: Query the Language Server for diagnostics, definitions, references. Use for type errors and navigation.\n\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Context & Memory\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - `memory_read`/`memory_write`: Read/write persistent project facts across sessions.\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Proactively save useful facts: test commands, framework versions, coding conventions, architecture decisions.\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Use `memory_write` after discovering project patterns (e.g. \"test_cmd\" = \"cargo test\").\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - `web_fetch`: Fetch content from a URL. Use to read documentation or API specs.\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - `web_search`: Search the web for current information.\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - `ask_user`: Ask the user a clarifying question when requirements are ambiguous.\n\n\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -172,6 +172,26 @@ impl ConversationRuntime { | |||||||||||||||||||||||||||||||||||||||
| self.memory = Some(memory); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /// Read from memory (for /memory command). | ||||||||||||||||||||||||||||||||||||||||
| pub fn memory_read(&self, input: &serde_json::Value) -> String { | ||||||||||||||||||||||||||||||||||||||||
| match &self.memory { | ||||||||||||||||||||||||||||||||||||||||
| Some(store) => store.handle_read(input), | ||||||||||||||||||||||||||||||||||||||||
| None => "Memory not configured".into(), | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /// Write to memory (for /memory command). | ||||||||||||||||||||||||||||||||||||||||
| pub fn memory_write(&mut self, input: &serde_json::Value) -> String { | ||||||||||||||||||||||||||||||||||||||||
| match &mut self.memory { | ||||||||||||||||||||||||||||||||||||||||
| Some(store) => { | ||||||||||||||||||||||||||||||||||||||||
| let out = store.handle_write(input); | ||||||||||||||||||||||||||||||||||||||||
| let _ = store.save(); | ||||||||||||||||||||||||||||||||||||||||
| out | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+187
to
+189
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The result of let out = store.handle_write(input);
if let Err(e) = store.save() {
return format!("Error persisting memory: {e}");
}
out |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+184
to
+190
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not swallow persistence failures in memory writes. Line 188 ignores Proposed fix pub fn memory_write(&mut self, input: &serde_json::Value) -> String {
match &mut self.memory {
Some(store) => {
let out = store.handle_write(input);
- let _ = store.save();
- out
+ match store.save() {
+ Ok(()) => out,
+ Err(e) => format!("{out}\n⚠ Failed to persist memory: {e}"),
+ }
}
None => "Memory not configured".into(),
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| None => "Memory not configured".into(), | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /// Attach an image to the next user message. | ||||||||||||||||||||||||||||||||||||||||
| pub fn attach_image(&mut self, path: String, media_type: String) { | ||||||||||||||||||||||||||||||||||||||||
| self.pending_image = Some((path, media_type)); | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1282,22 +1302,29 @@ Fix this before continuing." | |||||||||||||||||||||||||||||||||||||||
| let Some(ref mut memory) = self.memory else { | ||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
| // Heuristic: save lines that look like project facts | ||||||||||||||||||||||||||||||||||||||||
| for line in text.lines() { | ||||||||||||||||||||||||||||||||||||||||
| let trimmed = line.trim(); | ||||||||||||||||||||||||||||||||||||||||
| if (trimmed.starts_with("Note:") | ||||||||||||||||||||||||||||||||||||||||
| if trimmed.len() < 20 || trimmed.len() > 300 { | ||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| // Detect project facts worth remembering | ||||||||||||||||||||||||||||||||||||||||
| let is_fact = trimmed.starts_with("Note:") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.starts_with("Remember:") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("convention is")) | ||||||||||||||||||||||||||||||||||||||||
| && trimmed.len() > 20 | ||||||||||||||||||||||||||||||||||||||||
| && trimmed.len() < 200 | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("convention is") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("always use") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("project uses") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("test command:") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("configured with") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("running on port") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("database is") | ||||||||||||||||||||||||||||||||||||||||
| || trimmed.contains("deploy with"); | ||||||||||||||||||||||||||||||||||||||||
| if is_fact { | ||||||||||||||||||||||||||||||||||||||||
| let key = format!( | ||||||||||||||||||||||||||||||||||||||||
| "auto_{}_{}", | ||||||||||||||||||||||||||||||||||||||||
| "auto_{}", | ||||||||||||||||||||||||||||||||||||||||
| std::time::SystemTime::now() | ||||||||||||||||||||||||||||||||||||||||
| .duration_since(std::time::UNIX_EPOCH) | ||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_default() | ||||||||||||||||||||||||||||||||||||||||
| .as_millis(), | ||||||||||||||||||||||||||||||||||||||||
| trimmed.len(), | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
1322
to
1328
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using
Suggested change
Comment on lines
1322
to
1328
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Auto-memory keys can collide and overwrite facts. Lines 1322-1328 use millisecond timestamp only. When multiple facts are detected in one pass, keys can collide and earlier facts get overwritten. Proposed fix- for line in text.lines() {
+ for (line_idx, line) in text.lines().enumerate() {
let trimmed = line.trim();
// ...
if is_fact {
let key = format!(
- "auto_{}",
+ "auto_{}_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis(),
+ line_idx,
);
memory.set(&key, trimmed);
let _ = memory.save();
}
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| memory.set(&key, trimmed); | ||||||||||||||||||||||||||||||||||||||||
| let _ = memory.save(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The memory system is currently broken on Windows because
HOMEis not a standard environment variable (Windows usesUSERPROFILEorAPPDATA). WhenHOMEis missing,memory_pathbecomesNone, and theMemoryStoreis never initialized, leading to 'Memory not configured' errors. Furthermore,current_dir().unwrap_or_default()will cause multiple projects to share the same0.jsonmemory file if the current directory cannot be resolved.