From bc18b8d3c6e0da927a7c08bbbaeaedde8602b69c Mon Sep 17 00:00:00 2001 From: Lawyered Date: Tue, 17 Feb 2026 07:52:11 -0500 Subject: [PATCH] fix(memory): harden lucid recall timeout and add cold-start test (#466) --- src/memory/lucid.rs | 67 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/src/memory/lucid.rs b/src/memory/lucid.rs index 4747bbd..454d0dc 100644 --- a/src/memory/lucid.rs +++ b/src/memory/lucid.rs @@ -24,7 +24,9 @@ pub struct LucidMemory { impl LucidMemory { const DEFAULT_LUCID_CMD: &'static str = "lucid"; const DEFAULT_TOKEN_BUDGET: usize = 200; - const DEFAULT_RECALL_TIMEOUT_MS: u64 = 120; + // Lucid CLI cold start can exceed 120ms on slower machines, which causes + // avoidable fallback to local-only memory and premature cooldown. + const DEFAULT_RECALL_TIMEOUT_MS: u64 = 500; const DEFAULT_STORE_TIMEOUT_MS: u64 = 800; const DEFAULT_LOCAL_HIT_THRESHOLD: usize = 3; const DEFAULT_FAILURE_COOLDOWN_MS: u64 = 15_000; @@ -415,6 +417,38 @@ EOF exit 0 fi +echo "unsupported command" >&2 +exit 1 +"#; + + fs::write(&script_path, script).unwrap(); + let mut perms = fs::metadata(&script_path).unwrap().permissions(); + perms.set_mode(0o755); + fs::set_permissions(&script_path, perms).unwrap(); + script_path.display().to_string() + } + + fn write_delayed_lucid_script(dir: &Path) -> String { + let script_path = dir.join("delayed-lucid.sh"); + let script = r#"#!/usr/bin/env bash +set -euo pipefail + +if [[ "${1:-}" == "store" ]]; then + echo '{"success":true,"id":"mem_1"}' + exit 0 +fi + +if [[ "${1:-}" == "context" ]]; then + # Simulate a cold start that is slower than 120ms but below the 500ms timeout. + sleep 0.2 + cat <<'EOF' + +- [decision] Delayed token refresh guidance + +EOF + exit 0 +fi + echo "unsupported command" >&2 exit 1 "#; @@ -468,7 +502,7 @@ exit 1 cmd, 200, 3, - Duration::from_millis(120), + Duration::from_millis(500), Duration::from_millis(400), Duration::from_secs(2), ) @@ -520,6 +554,31 @@ exit 1 assert!(entries.iter().any(|e| e.content.contains("token refresh"))); } + #[tokio::test] + async fn recall_handles_lucid_cold_start_delay_within_timeout() { + let tmp = TempDir::new().unwrap(); + let delayed_cmd = write_delayed_lucid_script(tmp.path()); + let memory = test_memory(tmp.path(), delayed_cmd); + + memory + .store( + "local_note", + "Local sqlite auth fallback note", + MemoryCategory::Core, + ) + .await + .unwrap(); + + let entries = memory.recall("auth", 5).await.unwrap(); + + assert!(entries + .iter() + .any(|e| e.content.contains("Local sqlite auth fallback note"))); + assert!(entries + .iter() + .any(|e| e.content.contains("Delayed token refresh guidance"))); + } + #[tokio::test] async fn recall_skips_lucid_when_local_hits_are_enough() { let tmp = TempDir::new().unwrap(); @@ -533,7 +592,7 @@ exit 1 probe_cmd, 200, 1, - Duration::from_millis(120), + Duration::from_millis(500), Duration::from_millis(400), Duration::from_secs(2), ); @@ -603,7 +662,7 @@ exit 1 failing_cmd, 200, 99, - Duration::from_millis(120), + Duration::from_millis(500), Duration::from_millis(400), Duration::from_secs(5), );