Skip to content

Commit 64e6e99

Browse files
fix: update KnowledgeService call sites and add knowledge cleanup migration
Pass embedding service to KnowledgeService::new() at all call sites (mcp, serve, degraded_mode test). Align CLI auto_archive retention to 90 days. Add migration to strip UUIDs from knowledge titles, merge duplicates, and fix null/invalid project names in observations. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 2879815 commit 64e6e99

5 files changed

Lines changed: 80 additions & 4 deletions

File tree

crates/cli/src/commands/mcp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub(crate) async fn run(config: Arc<AppConfig>) -> Result<()> {
7373
&config,
7474
));
7575
let session_service = Arc::new(SessionService::new(storage.clone(), llm.clone()));
76-
let knowledge_service = Arc::new(KnowledgeService::new(storage.clone()));
76+
let knowledge_service = Arc::new(KnowledgeService::new(storage.clone(), embeddings.clone()));
7777
let search_service = Arc::new(SearchService::new(
7878
storage,
7979
embeddings,

crates/cli/src/commands/search.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ pub(crate) async fn run_backfill_embeddings(batch_size: usize) -> Result<()> {
124124
pub(crate) async fn run_knowledge_lifecycle() -> Result<()> {
125125
let storage = crate::create_storage_from_env().await?;
126126
let decayed = storage.decay_confidence().await?;
127-
let archived = storage.auto_archive(30).await?;
127+
let archived = storage.auto_archive(90).await?;
128128
println!("Knowledge confidence lifecycle complete:");
129129
println!(" Entries with decayed confidence: {decayed}");
130130
println!(" Entries archived: {archived}");

crates/cli/src/commands/serve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub(crate) async fn run(port: u16, host: String, config: Arc<AppConfig>) -> Resu
8080
&config,
8181
));
8282
let session_service = Arc::new(SessionService::new(storage.clone(), llm.clone()));
83-
let knowledge_service = Arc::new(KnowledgeService::new(storage.clone()));
83+
let knowledge_service = Arc::new(KnowledgeService::new(storage.clone(), embeddings.clone()));
8484
let search_service = Arc::new(SearchService::new(
8585
storage.clone(),
8686
embeddings.clone(),

crates/mcp/tests/degraded_mode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn setup_degraded_services() -> (
8282
&config,
8383
));
8484
let session_service = Arc::new(SessionService::new(backend.clone(), llm.clone()));
85-
let knowledge_service = Arc::new(KnowledgeService::new(backend.clone()));
85+
let knowledge_service = Arc::new(KnowledgeService::new(backend.clone(), None));
8686
let search_service = Arc::new(SearchService::new(
8787
backend,
8888
None,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
-- Knowledge cleanup migration: strip UUIDs from titles, merge duplicates,
2+
-- fix null/invalid project names.
3+
4+
-- 1. Strip UUID suffixes from knowledge titles.
5+
-- Pattern: optional whitespace + hex UUID (8-4-4+ with optional trailing segments).
6+
UPDATE global_knowledge
7+
SET title = TRIM(REGEXP_REPLACE(title, '\s*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4,}(-[0-9a-f]*)*\s*', '', 'gi')),
8+
updated_at = NOW()
9+
WHERE title ~ '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4,}';
10+
11+
-- 2. Merge duplicate knowledge entries (same lowercased title after UUID stripping).
12+
-- Keep the entry with highest usage_count (ties broken by earliest created_at).
13+
-- Sum usage_counts, merge source_observations arrays, take highest confidence.
14+
DO $$
15+
DECLARE
16+
dup RECORD;
17+
keeper_id TEXT;
18+
merged_usage BIGINT;
19+
merged_obs JSONB;
20+
max_confidence FLOAT8;
21+
BEGIN
22+
-- Find groups of duplicates (2+ entries with same normalized title)
23+
FOR dup IN
24+
SELECT LOWER(TRIM(title)) AS norm_title,
25+
COUNT(*) AS cnt
26+
FROM global_knowledge
27+
WHERE archived_at IS NULL
28+
GROUP BY LOWER(TRIM(title))
29+
HAVING COUNT(*) > 1
30+
LOOP
31+
-- Determine the keeper: highest usage_count, then earliest created_at
32+
SELECT id INTO keeper_id
33+
FROM global_knowledge
34+
WHERE LOWER(TRIM(title)) = dup.norm_title AND archived_at IS NULL
35+
ORDER BY usage_count DESC, created_at ASC
36+
LIMIT 1;
37+
38+
-- Aggregate usage_count, source_observations, and max confidence from all duplicates
39+
SELECT COALESCE(SUM(usage_count), 0),
40+
COALESCE(
41+
(SELECT jsonb_agg(DISTINCT elem)
42+
FROM global_knowledge g2,
43+
jsonb_array_elements(g2.source_observations) AS elem
44+
WHERE LOWER(TRIM(g2.title)) = dup.norm_title
45+
AND g2.archived_at IS NULL
46+
AND elem != 'null'::jsonb),
47+
'[]'::jsonb
48+
),
49+
COALESCE(MAX(confidence), 0.5)
50+
INTO merged_usage, merged_obs, max_confidence
51+
FROM global_knowledge
52+
WHERE LOWER(TRIM(title)) = dup.norm_title AND archived_at IS NULL;
53+
54+
UPDATE global_knowledge
55+
SET usage_count = merged_usage,
56+
source_observations = merged_obs,
57+
confidence = max_confidence,
58+
updated_at = NOW()
59+
WHERE id = keeper_id;
60+
61+
DELETE FROM global_knowledge
62+
WHERE LOWER(TRIM(title)) = dup.norm_title
63+
AND archived_at IS NULL
64+
AND id != keeper_id;
65+
END LOOP;
66+
END $$;
67+
68+
-- 3. Fix NULL project in observations — set to 'unknown'.
69+
UPDATE observations
70+
SET project = 'unknown'
71+
WHERE project IS NULL;
72+
73+
-- 4. Fix 'ivan plankin' project name (personal name, not a project).
74+
UPDATE observations
75+
SET project = 'unknown'
76+
WHERE LOWER(project) = 'ivan plankin';

0 commit comments

Comments
 (0)