personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: clean up stray topics/summaries references from insights migration

- Fix critical bug in mcp_tools.py using wrong path for insights
- Update UI tabs and URLs from 'summaries' to 'insights'
- Update documentation (JOURNAL.md, AGENTS.md, README.md, CORTEX.md)
- Rename integration test file and update function calls
- Fix docstring comments in apps/agents/routes.py

๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+47 -47
+1 -1
AGENTS.md
··· 42 42 โ”œโ”€โ”€ observe/ # Multimodal capture & AI analysis 43 43 โ”œโ”€โ”€ think/ # Data post-processing & AI analysis 44 44 โ”‚ โ”œโ”€โ”€ indexer/ # Database indexing subsystem 45 - โ”‚ โ””โ”€โ”€ topics/ # Topic extraction templates 45 + โ”‚ โ””โ”€โ”€ insights/ # Insight generation templates 46 46 โ”œโ”€โ”€ convey/ # Web app frontend & backend 47 47 โ”‚ โ”œโ”€โ”€ static/ # JavaScript and CSS assets 48 48 โ”‚ โ”œโ”€โ”€ templates/ # Jinja2 HTML templates
+5 -5
CORTEX.md
··· 109 109 "event": "tool_start", 110 110 "ts": 1234567890123, 111 111 "agent_id": "1234567890123", 112 - "tool": "search_summaries", 112 + "tool": "search_insights", 113 113 "args": {"query": "search terms", "limit": 10}, 114 - "call_id": "search_summaries-1" 114 + "call_id": "search_insights-1" 115 115 } 116 116 ``` 117 117 ··· 122 122 "event": "tool_end", 123 123 "ts": 1234567890123, 124 124 "agent_id": "1234567890123", 125 - "tool": "search_summaries", 125 + "tool": "search_insights", 126 126 "args": {"query": "search terms"}, 127 127 "result": ["result", "array", "or", "object"], 128 - "call_id": "search_summaries-1" 128 + "call_id": "search_insights-1" 129 129 } 130 130 ``` 131 131 ··· 243 243 - `max_tokens`: Maximum response token limit 244 244 - `tools`: MCP tools configuration (string or array) 245 245 - String: Tool pack name (e.g., "default", "journal") - expanded via `get_tools()` 246 - - Array: Explicit list of tool names (e.g., `["search_summaries", "get_facet"]`) 246 + - Array: Explicit list of tool names (e.g., `["search_insights", "get_facet"]`) 247 247 - If omitted, defaults to "default" pack 248 248 - `schedule`: Scheduling configuration for automated execution 249 249 - `"daily"`: Run automatically at midnight each day
+2 -2
JOURNAL.md
··· 10 10 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” 11 11 โ”‚ LAYER 3: INSIGHTS โ”‚ Narrative summaries 12 12 โ”‚ (Markdown files) โ”‚ "What it means" 13 - โ”‚ - topics/*.md (daily insights) โ”‚ 13 + โ”‚ - insights/*.md (daily insights) โ”‚ 14 14 โ”‚ - screen.md (segment insights) โ”‚ 15 15 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 16 16 โ†‘ synthesized from ··· 38 38 |------|------------|----------| 39 39 | **Capture** | Raw audio/video recording | `*.flac`, `*.webm` | 40 40 | **Extract** | Structured data from captures | `*.jsonl`, `occurrences.json` | 41 - | **Insight** | AI-generated narrative summary | `topics/*.md`, `screen.md` | 41 + | **Insight** | AI-generated narrative summary | `insights/*.md`, `screen.md` | 42 42 43 43 **Organization** 44 44
+1 -1
README.md
··· 18 18 - *Note: Requires Linux with GNOME desktop* 19 19 20 20 - **Think** ๐Ÿง  - Data processing and insights 21 - - `think-summarize` - Generates AI-powered summaries and topics 21 + - `think-insight` - Generates AI-powered insights and summaries 22 22 - `think-cluster` - Groups related content 23 23 - `think-indexer` - Builds searchable database 24 24 - `think-supervisor` - Orchestrates agent workflows
+6 -6
apps/agents/routes.py
··· 19 19 """Generic function to list items from think/{item_type}/. 20 20 21 21 Args: 22 - item_type: Either 'agents' or 'topics' 22 + item_type: Either 'agents' or 'insights' 23 23 24 24 Returns: 25 - List of items with id, title, and description (and color for topics) 25 + List of items with id, title, and description (and color for insights) 26 26 """ 27 27 # Special handling for insights to get colors from get_insights() 28 28 if item_type == "insights": ··· 170 170 """Generic function to get item content from {item_type}/{item_id}.txt. 171 171 172 172 Args: 173 - item_type: Either 'agents' or 'topics' 173 + item_type: Either 'agents' or 'insights' 174 174 item_id: The item identifier 175 175 176 176 Returns: ··· 289 289 """Generic function to update or create an item. 290 290 291 291 Args: 292 - item_type: Either 'agents' or 'topics' 292 + item_type: Either 'agents' or 'insights' 293 293 item_id: The item identifier 294 294 data: Request data with title and content 295 295 ··· 401 401 f.write(new_content) 402 402 403 403 action = "created" if is_new else "updated" 404 - item_name = item_type[:-1].title() # 'agents' -> 'Agent', 'topics' -> 'Topic' 404 + item_name = item_type[:-1].title() # 'agents' -> 'Agent', 'insights' -> 'Insight' 405 405 return {"success": True, "message": f"{item_name} {action} successfully"}, 200 406 406 except Exception as e: 407 407 return {"error": str(e)}, 500 ··· 412 412 """Update an agent's title and content or create a new one.""" 413 413 data = request.get_json() 414 414 415 - # Handle topics-specific fields (color, disabled) 415 + # Handle insights-specific fields (color, muted) 416 416 if "color" in data or "disabled" in data: 417 417 # This is actually a topic update, not an agent 418 418 return jsonify({"error": "Invalid fields for agent update"}), 400
+3 -3
apps/chat/workspace.html
··· 687 687 // Handle search tools with special event cards 688 688 if(args && args.query) { 689 689 // Determine which index is being searched 690 - let idx = 'summaries'; 691 - let indexName = 'Summaries'; 690 + let idx = 'insights'; 691 + let indexName = 'Insights'; 692 692 if(tool.includes('event')) { 693 693 idx = 'events'; 694 694 indexName = 'Events'; ··· 708 708 } 709 709 710 710 // Build search URL 711 - const q = args.query + (idx === 'summaries' ? '' : ' index:' + idx); 711 + const q = args.query + (idx === 'insights' ? '' : ' index:' + idx); 712 712 const url = searchBase + '#q=' + encodeURIComponent(q); 713 713 714 714 // Create descriptive text
+5 -5
apps/search/workspace.html
··· 14 14 <button type="submit">Search</button> 15 15 </form> 16 16 <div class="tabs"> 17 - <div class="tab active" data-index="summaries">Summaries</div> 17 + <div class="tab active" data-index="insights">Insights</div> 18 18 <div class="tab" data-index="events">Events</div> 19 19 <div class="tab" data-index="entities">Entities</div> 20 20 <div class="tab" data-index="transcripts">Transcripts</div> ··· 28 28 29 29 <script src="{{ url_for('root.static', filename='colors.js') }}"></script> 30 30 <script> 31 - const summariesUrl = '{{ url_for('app:search.search_summaries_api') }}'; 31 + const insightsUrl = '{{ url_for('app:search.search_insights_api') }}'; 32 32 const eventsUrl = '{{ url_for('app:search.search_events_api') }}'; 33 33 const entitiesUrl = '{{ url_for('app:search.search_entities_api') }}'; 34 34 const transcriptsUrl = '{{ url_for('app:search.search_transcripts_api') }}'; ··· 59 59 function parseQuery(q){ 60 60 let day = ''; 61 61 let topic = ''; 62 - let index = 'summaries'; 62 + let index = 'insights'; 63 63 let type = ''; 64 64 let name = ''; 65 65 let facet = ''; ··· 313 313 if(reset){ offset = 0; topicColors = {}; colorIndex=0; document.querySelector('#resultsTable tbody').innerHTML=''; } 314 314 setHeaders(parsed.index); 315 315 updateTabs(parsed.index); 316 - let api = summariesUrl; 316 + let api = insightsUrl; 317 317 if(parsed.index==='events') api = eventsUrl; 318 318 else if(parsed.index==='entities') api = entitiesUrl; 319 319 else if(parsed.index==='transcripts') api = transcriptsUrl; ··· 363 363 tab.addEventListener('click', () => { 364 364 const idx = tab.dataset.index; 365 365 const parts = queryInput.value.split(/\s+/).filter(p => p && !p.startsWith('index:')); 366 - if(idx !== 'summaries') parts.push('index:' + idx); 366 + if(idx !== 'insights') parts.push('index:' + idx); 367 367 queryInput.value = parts.join(' '); 368 368 query = queryInput.value.trim(); 369 369 window.location.hash = `q=${encodeURIComponent(query)}`;
+2 -2
muse/mcp_tools.py
··· 355 355 return {"total": total, "limit": limit, "offset": offset, "results": items} 356 356 except Exception as exc: 357 357 return { 358 - "error": f"Failed to search topics: {exc}", 358 + "error": f"Failed to search insights: {exc}", 359 359 "suggestion": "try adjusting the query or ensure indexes exist", 360 360 } 361 361 ··· 1197 1197 def get_summary(day: str, topic: str) -> TextResource: 1198 1198 """Return the markdown summary for a topic.""" 1199 1199 journal = os.getenv("JOURNAL_PATH", "journal") 1200 - md_path = Path(journal) / day / "topics" / f"{topic}.md" 1200 + md_path = Path(journal) / day / "insights" / f"{topic}.md" 1201 1201 1202 1202 if not md_path.is_file(): 1203 1203 text = f"Topic '{topic}' not found for day {day}"
+22 -22
tests/integration/test_indexer_summaries.py tests/integration/test_indexer_insights.py
··· 1 - """Integration tests for the summaries indexer.""" 1 + """Integration tests for the insights indexer.""" 2 2 3 3 import os 4 4 import sqlite3 ··· 9 9 10 10 from think.indexer import ( 11 11 reset_index, 12 - scan_summaries, 13 - search_summaries, 12 + scan_insights, 13 + search_insights, 14 14 ) 15 15 16 16 17 17 @pytest.mark.integration 18 - def test_summaries_indexer_scan_and_search(): 19 - """Test scanning and searching summary files from fixtures.""" 18 + def test_insights_indexer_scan_and_search(): 19 + """Test scanning and searching insight files from fixtures.""" 20 20 # Use fixtures journal path 21 21 journal_path = Path(__file__).parent.parent.parent / "fixtures" / "journal" 22 22 ··· 31 31 32 32 try: 33 33 # Get the index database 34 - index_path = Path(tmpdir) / "indexer" / "summaries.sqlite" 34 + index_path = Path(tmpdir) / "indexer" / "insights.sqlite" 35 35 index_path.parent.mkdir(parents=True, exist_ok=True) 36 36 37 37 # Override the index path for testing ··· 56 56 think.indexer.core.get_index = test_get_index 57 57 58 58 # Reset index to ensure clean state 59 - reset_index(str(journal_path), "summaries") 59 + reset_index(str(journal_path), "insights") 60 60 61 - # Scan the summaries 62 - scan_count = scan_summaries(str(journal_path)) 61 + # Scan the insights 62 + scan_count = scan_insights(str(journal_path)) 63 63 64 - # We should have scanned some summary files 65 - assert scan_count > 0, f"Expected to scan summary files, got {scan_count}" 64 + # We should have scanned some insight files 65 + assert scan_count > 0, f"Expected to scan insight files, got {scan_count}" 66 66 67 67 # Search for specific terms we know are in the fixtures 68 68 69 69 # Search for "authentication" (from flow.md) 70 - total, results = search_summaries("authentication") 70 + total, results = search_insights("authentication") 71 71 assert total > 0, "Should find results for 'authentication'" 72 72 assert len(results) > 0, "Should have actual results for 'authentication'" 73 73 # Verify the result contains expected content ··· 79 79 assert found_auth, "Should find 'authentication module' in results" 80 80 81 81 # Search for "sprint planning" (from flow.md) 82 - total, results = search_summaries("sprint planning") 82 + total, results = search_insights("sprint planning") 83 83 assert total > 0, "Should find results for 'sprint planning'" 84 84 85 85 # Search for "Docker" (from day 2) 86 - total, results = search_summaries("Docker") 86 + total, results = search_insights("Docker") 87 87 assert total > 0, "Should find results for 'Docker'" 88 88 89 89 # Search for "FastAPI" (from day 2) 90 - total, results = search_summaries("FastAPI") 90 + total, results = search_insights("FastAPI") 91 91 assert total > 0, "Should find results for 'FastAPI'" 92 92 93 93 # Verify results have expected structure ··· 113 113 114 114 115 115 @pytest.mark.integration 116 - def test_summaries_indexer_rescan(): 117 - """Test that rescanning summaries updates the index properly.""" 116 + def test_insights_indexer_rescan(): 117 + """Test that rescanning insights updates the index properly.""" 118 118 journal_path = Path(__file__).parent.parent.parent / "fixtures" / "journal" 119 119 120 120 if not journal_path.exists(): ··· 145 145 think.indexer.core.get_index = test_get_index 146 146 147 147 # Reset and scan initially 148 - reset_index(str(journal_path), "summaries") 149 - first_scan = scan_summaries(str(journal_path)) 148 + reset_index(str(journal_path), "insights") 149 + first_scan = scan_insights(str(journal_path)) 150 150 assert first_scan > 0 151 151 152 152 # Search for initial content 153 - total1, results1 = search_summaries("authentication") 153 + total1, results1 = search_insights("authentication") 154 154 initial_count = total1 155 155 156 156 # Rescan (should handle existing content gracefully) 157 - # second_scan = scan_summaries(str(journal_path)) 157 + # second_scan = scan_insights(str(journal_path)) 158 158 159 159 # Search again - should get same results 160 - total2, results2 = search_summaries("authentication") 160 + total2, results2 = search_insights("authentication") 161 161 assert total2 == initial_count, "Rescan should not duplicate entries" 162 162 163 163 finally: