basic-memory 0.8.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

Files changed (76) hide show
  1. basic_memory/__init__.py +1 -1
  2. basic_memory/alembic/migrations.py +4 -9
  3. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +106 -0
  4. basic_memory/api/app.py +9 -6
  5. basic_memory/api/routers/__init__.py +2 -1
  6. basic_memory/api/routers/knowledge_router.py +30 -4
  7. basic_memory/api/routers/memory_router.py +3 -2
  8. basic_memory/api/routers/project_info_router.py +274 -0
  9. basic_memory/api/routers/search_router.py +22 -4
  10. basic_memory/cli/app.py +54 -3
  11. basic_memory/cli/commands/__init__.py +15 -2
  12. basic_memory/cli/commands/db.py +9 -13
  13. basic_memory/cli/commands/import_chatgpt.py +31 -36
  14. basic_memory/cli/commands/import_claude_conversations.py +32 -35
  15. basic_memory/cli/commands/import_claude_projects.py +34 -37
  16. basic_memory/cli/commands/import_memory_json.py +26 -28
  17. basic_memory/cli/commands/mcp.py +7 -1
  18. basic_memory/cli/commands/project.py +119 -0
  19. basic_memory/cli/commands/project_info.py +167 -0
  20. basic_memory/cli/commands/status.py +7 -9
  21. basic_memory/cli/commands/sync.py +54 -9
  22. basic_memory/cli/commands/{tools.py → tool.py} +92 -19
  23. basic_memory/cli/main.py +40 -1
  24. basic_memory/config.py +157 -10
  25. basic_memory/db.py +19 -4
  26. basic_memory/deps.py +10 -3
  27. basic_memory/file_utils.py +34 -18
  28. basic_memory/markdown/markdown_processor.py +1 -1
  29. basic_memory/markdown/utils.py +5 -0
  30. basic_memory/mcp/main.py +1 -2
  31. basic_memory/mcp/prompts/__init__.py +6 -2
  32. basic_memory/mcp/prompts/ai_assistant_guide.py +9 -10
  33. basic_memory/mcp/prompts/continue_conversation.py +65 -126
  34. basic_memory/mcp/prompts/recent_activity.py +55 -13
  35. basic_memory/mcp/prompts/search.py +72 -17
  36. basic_memory/mcp/prompts/utils.py +139 -82
  37. basic_memory/mcp/server.py +1 -1
  38. basic_memory/mcp/tools/__init__.py +11 -22
  39. basic_memory/mcp/tools/build_context.py +85 -0
  40. basic_memory/mcp/tools/canvas.py +17 -19
  41. basic_memory/mcp/tools/delete_note.py +28 -0
  42. basic_memory/mcp/tools/project_info.py +51 -0
  43. basic_memory/mcp/tools/{resource.py → read_content.py} +42 -5
  44. basic_memory/mcp/tools/read_note.py +190 -0
  45. basic_memory/mcp/tools/recent_activity.py +100 -0
  46. basic_memory/mcp/tools/search.py +56 -17
  47. basic_memory/mcp/tools/utils.py +245 -17
  48. basic_memory/mcp/tools/write_note.py +124 -0
  49. basic_memory/models/search.py +2 -1
  50. basic_memory/repository/entity_repository.py +3 -2
  51. basic_memory/repository/project_info_repository.py +9 -0
  52. basic_memory/repository/repository.py +23 -6
  53. basic_memory/repository/search_repository.py +33 -10
  54. basic_memory/schemas/__init__.py +12 -0
  55. basic_memory/schemas/memory.py +3 -2
  56. basic_memory/schemas/project_info.py +96 -0
  57. basic_memory/schemas/search.py +27 -32
  58. basic_memory/services/context_service.py +3 -3
  59. basic_memory/services/entity_service.py +8 -2
  60. basic_memory/services/file_service.py +107 -57
  61. basic_memory/services/link_resolver.py +5 -45
  62. basic_memory/services/search_service.py +45 -16
  63. basic_memory/sync/sync_service.py +274 -39
  64. basic_memory/sync/watch_service.py +174 -34
  65. basic_memory/utils.py +40 -40
  66. basic_memory-0.10.0.dist-info/METADATA +386 -0
  67. basic_memory-0.10.0.dist-info/RECORD +99 -0
  68. basic_memory/mcp/prompts/json_canvas_spec.py +0 -25
  69. basic_memory/mcp/tools/knowledge.py +0 -68
  70. basic_memory/mcp/tools/memory.py +0 -177
  71. basic_memory/mcp/tools/notes.py +0 -201
  72. basic_memory-0.8.0.dist-info/METADATA +0 -379
  73. basic_memory-0.8.0.dist-info/RECORD +0 -91
  74. {basic_memory-0.8.0.dist-info → basic_memory-0.10.0.dist-info}/WHEEL +0 -0
  75. {basic_memory-0.8.0.dist-info → basic_memory-0.10.0.dist-info}/entry_points.txt +0 -0
  76. {basic_memory-0.8.0.dist-info → basic_memory-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,22 +5,23 @@ providing context from previous interactions to maintain continuity.
5
5
  """
6
6
 
7
7
  from textwrap import dedent
8
- from typing import Optional, List, Annotated
8
+ from typing import Optional, Annotated
9
9
 
10
10
  from loguru import logger
11
- import logfire
12
11
  from pydantic import Field
13
12
 
13
+ from basic_memory.mcp.prompts.utils import format_prompt_context, PromptContext, PromptContextItem
14
14
  from basic_memory.mcp.server import mcp
15
- from basic_memory.mcp.tools.memory import build_context, recent_activity
15
+ from basic_memory.mcp.tools.build_context import build_context
16
+ from basic_memory.mcp.tools.recent_activity import recent_activity
16
17
  from basic_memory.mcp.tools.search import search
17
18
  from basic_memory.schemas.base import TimeFrame
18
19
  from basic_memory.schemas.memory import GraphContext
19
- from basic_memory.schemas.search import SearchQuery
20
+ from basic_memory.schemas.search import SearchQuery, SearchItemType
20
21
 
21
22
 
22
23
  @mcp.prompt(
23
- name="continue_conversation",
24
+ name="Continue Conversation",
24
25
  description="Continue a previous conversation",
25
26
  )
26
27
  async def continue_conversation(
@@ -42,131 +43,69 @@ async def continue_conversation(
42
43
  Returns:
43
44
  Context from previous sessions on this topic
44
45
  """
45
- with logfire.span("Continuing session", topic=topic, timeframe=timeframe): # pyright: ignore
46
- logger.info(f"Continuing session, topic: {topic}, timeframe: {timeframe}")
47
-
48
- # If topic provided, search for it
49
- if topic:
50
- search_results = await search(SearchQuery(text=topic, after_date=timeframe))
51
-
52
- # Build context from top results
53
- contexts = []
54
- for result in search_results.results[:3]:
55
- if hasattr(result, "permalink") and result.permalink:
56
- context = await build_context(f"memory://{result.permalink}")
57
- contexts.append(context)
58
-
59
- return format_continuation_context(topic, contexts, timeframe)
60
-
46
+ logger.info(f"Continuing session, topic: {topic}, timeframe: {timeframe}")
47
+
48
+ # If topic provided, search for it
49
+ if topic:
50
+ search_results = await search(
51
+ SearchQuery(text=topic, after_date=timeframe, types=[SearchItemType.ENTITY])
52
+ )
53
+
54
+ # Build context from results
55
+ contexts = []
56
+ for result in search_results.results:
57
+ if hasattr(result, "permalink") and result.permalink:
58
+ context: GraphContext = await build_context(f"memory://{result.permalink}")
59
+ if context.primary_results:
60
+ contexts.append(
61
+ PromptContextItem(
62
+ primary_results=context.primary_results[:1], # pyright: ignore
63
+ related_results=context.related_results[:3], # pyright: ignore
64
+ )
65
+ )
66
+
67
+ # get context for the top 3 results
68
+ prompt_context = format_prompt_context(
69
+ PromptContext(topic=topic, timeframe=timeframe, results=contexts) # pyright: ignore
70
+ )
71
+
72
+ else:
61
73
  # If no topic, get recent activity
62
- recent = await recent_activity(timeframe=timeframe)
63
- return format_continuation_context("Recent Activity", [recent], timeframe)
64
-
65
-
66
- def format_continuation_context(
67
- topic: str, contexts: List[GraphContext], timeframe: TimeFrame | None
68
- ) -> str:
69
- """Format continuation context into a helpful summary.
70
-
71
- Args:
72
- topic: The topic or focus of continuation
73
- contexts: List of context graphs
74
- timeframe: How far back to look for activity
75
-
76
- Returns:
77
- Formatted continuation summary
78
- """
79
- if not contexts or all(not context.primary_results for context in contexts):
80
- return dedent(f"""
81
- # Continuing conversation on: {topic}
82
-
83
- This is a memory retrieval session.
84
- Please use the available basic-memory tools to gather relevant context before responding.
85
- Start by executing one of the suggested commands below to retrieve content.
86
-
87
- I couldn't find any recent work specifically on this topic.
88
-
89
- ## Suggestions
90
- - Try a different search term
91
- - Check recent activity with `recent_activity(timeframe="1w")`
92
- - Start a new topic with `write_note(...)`
93
- """)
94
-
95
- # Start building our summary with header
96
- summary = dedent(f"""
97
- # Continuing conversation on: {topic}
98
-
99
- This is a memory retrieval session.
100
- Please use the available basic-memory tools to gather relevant context before responding.
101
- Start by executing one of the suggested commands below to retrieve content.
102
-
103
- Here's what I found about the previous conversation:
104
- """)
105
-
106
- # Track what we've added to avoid duplicates
107
- added_permalinks = set()
108
- sections = []
109
-
110
- # Process each context
111
- for context in contexts:
112
- # Add primary results
113
- for primary in context.primary_results:
114
- if hasattr(primary, "permalink") and primary.permalink not in added_permalinks:
115
- added_permalinks.add(primary.permalink)
116
-
117
- section = dedent(f"""
118
- ## {primary.title}
119
- - **Type**: {primary.type}
120
- """)
121
-
122
- # Add creation date if available
123
- if hasattr(primary, "created_at"):
124
- section += f"- **Created**: {primary.created_at.strftime('%Y-%m-%d %H:%M')}\n"
125
-
126
- section += dedent(f"""
127
-
128
- You can read this document with: `read_note("{primary.permalink}")`
129
- """)
130
-
131
- # Add related documents if available
132
- related_by_type = {}
133
- if context.related_results:
134
- for related in context.related_results:
135
- if hasattr(related, "relation_type") and related.relation_type: # pyright: ignore
136
- if related.relation_type not in related_by_type: # pyright: ignore
137
- related_by_type[related.relation_type] = [] # pyright: ignore
138
- related_by_type[related.relation_type].append(related) # pyright: ignore
139
-
140
- if related_by_type:
141
- section += dedent("""
142
- ### Related Documents
143
- """)
144
- for rel_type, relations in related_by_type.items():
145
- display_type = rel_type.replace("_", " ").title()
146
- section += f"- **{display_type}**:\n"
147
- for rel in relations[:3]: # Limit to avoid overwhelming
148
- if hasattr(rel, "to_id") and rel.to_id:
149
- section += f" - `{rel.to_id}`\n"
150
-
151
- sections.append(section)
152
-
153
- # Add all sections
154
- summary += "\n".join(sections)
155
-
156
- # Add next steps
74
+ timeframe = timeframe or "7d"
75
+ recent: GraphContext = await recent_activity(
76
+ timeframe=timeframe, type=[SearchItemType.ENTITY]
77
+ )
78
+ prompt_context = format_prompt_context(
79
+ PromptContext(
80
+ topic=f"Recent Activity from ({timeframe})",
81
+ timeframe=timeframe,
82
+ results=[
83
+ PromptContextItem(
84
+ primary_results=recent.primary_results[:5], # pyright: ignore
85
+ related_results=recent.related_results[:2], # pyright: ignore
86
+ )
87
+ ],
88
+ )
89
+ )
90
+
91
+ # Add next steps with strong encouragement to write
157
92
  next_steps = dedent(f"""
158
93
  ## Next Steps
159
-
94
+
160
95
  You can:
161
96
  - Explore more with: `search({{"text": "{topic}"}})`
162
- - See what's changed: `recent_activity(timeframe="{timeframe}")`
97
+ - See what's changed: `recent_activity(timeframe="{timeframe or "7d"}")`
98
+ - **Record new learnings or decisions from this conversation:** `write_note(title="[Create a meaningful title]", content="[Content with observations and relations]")`
99
+
100
+ ## Knowledge Capture Recommendation
101
+
102
+ As you continue this conversation, **actively look for opportunities to:**
103
+ 1. Record key information, decisions, or insights that emerge
104
+ 2. Link new knowledge to existing topics
105
+ 3. Suggest capturing important context when appropriate
106
+ 4. Create forward references to topics that might be created later
107
+
108
+ Remember that capturing knowledge during conversations is one of the most valuable aspects of Basic Memory.
163
109
  """)
164
110
 
165
- # Add specific exploration based on what we found
166
- if added_permalinks:
167
- first_permalink = next(iter(added_permalinks))
168
- next_steps += dedent(f"""
169
- - Continue the conversation: `build_context("memory://{first_permalink}")`
170
- """)
171
-
172
- return summary + next_steps
111
+ return prompt_context + next_steps
@@ -3,27 +3,27 @@
3
3
  These prompts help users see what has changed in their knowledge base recently.
4
4
  """
5
5
 
6
- from typing import Annotated, Optional
6
+ from typing import Annotated
7
7
 
8
8
  from loguru import logger
9
- import logfire
10
9
  from pydantic import Field
11
10
 
12
- from basic_memory.mcp.prompts.utils import format_context_summary
11
+ from basic_memory.mcp.prompts.utils import format_prompt_context, PromptContext, PromptContextItem
13
12
  from basic_memory.mcp.server import mcp
14
- from basic_memory.mcp.tools.memory import recent_activity as recent_activity_tool
13
+ from basic_memory.mcp.tools.recent_activity import recent_activity
15
14
  from basic_memory.schemas.base import TimeFrame
15
+ from basic_memory.schemas.search import SearchItemType
16
16
 
17
17
 
18
18
  @mcp.prompt(
19
- name="recent_activity",
19
+ name="Share Recent Activity",
20
20
  description="Get recent activity from across the knowledge base",
21
21
  )
22
22
  async def recent_activity_prompt(
23
23
  timeframe: Annotated[
24
- Optional[TimeFrame],
24
+ TimeFrame,
25
25
  Field(description="How far back to look for activity (e.g. '1d', '1 week')"),
26
- ] = None,
26
+ ] = "7d",
27
27
  ) -> str:
28
28
  """Get recent activity from across the knowledge base.
29
29
 
@@ -36,11 +36,53 @@ async def recent_activity_prompt(
36
36
  Returns:
37
37
  Formatted summary of recent activity
38
38
  """
39
- with logfire.span("Getting recent activity", timeframe=timeframe): # pyright: ignore
40
- logger.info(f"Getting recent activity, timeframe: {timeframe}")
39
+ logger.info(f"Getting recent activity, timeframe: {timeframe}")
41
40
 
42
- results = await recent_activity_tool(timeframe=timeframe)
41
+ recent = await recent_activity(timeframe=timeframe, type=[SearchItemType.ENTITY])
43
42
 
44
- time_display = f" ({timeframe})" if timeframe else ""
45
- header = f"# Recent Activity{time_display}"
46
- return format_context_summary(header, results)
43
+ prompt_context = format_prompt_context(
44
+ PromptContext(
45
+ topic=f"Recent Activity from ({timeframe})",
46
+ timeframe=timeframe,
47
+ results=[
48
+ PromptContextItem(
49
+ primary_results=recent.primary_results[:5],
50
+ related_results=recent.related_results[:2],
51
+ )
52
+ ],
53
+ )
54
+ )
55
+
56
+ # Add suggestions for summarizing recent activity
57
+ capture_suggestions = f"""
58
+ ## Opportunity to Capture Activity Summary
59
+
60
+ Consider creating a summary note of recent activity:
61
+
62
+ ```python
63
+ await write_note(
64
+ title="Activity Summary {timeframe}",
65
+ content='''
66
+ # Activity Summary for {timeframe}
67
+
68
+ ## Overview
69
+ [Summary of key changes and developments over this period]
70
+
71
+ ## Key Updates
72
+ [List main updates and their significance]
73
+
74
+ ## Observations
75
+ - [trend] [Observation about patterns in recent activity]
76
+ - [insight] [Connection between different activities]
77
+
78
+ ## Relations
79
+ - summarizes [[{recent.primary_results[0].title if recent.primary_results else "Recent Topic"}]]
80
+ - relates_to [[Project Overview]]
81
+ '''
82
+ )
83
+ ```
84
+
85
+ Summarizing periodic activity helps create high-level insights and connections between topics.
86
+ """
87
+
88
+ return prompt_context + capture_suggestions
@@ -7,17 +7,16 @@ from textwrap import dedent
7
7
  from typing import Annotated, Optional
8
8
 
9
9
  from loguru import logger
10
- import logfire
11
10
  from pydantic import Field
12
11
 
13
12
  from basic_memory.mcp.server import mcp
14
13
  from basic_memory.mcp.tools.search import search as search_tool
15
- from basic_memory.schemas.search import SearchQuery, SearchResponse
16
14
  from basic_memory.schemas.base import TimeFrame
15
+ from basic_memory.schemas.search import SearchQuery, SearchResponse
17
16
 
18
17
 
19
18
  @mcp.prompt(
20
- name="search",
19
+ name="Search Knowledge Base",
21
20
  description="Search across all content in basic-memory",
22
21
  )
23
22
  async def search_prompt(
@@ -39,11 +38,10 @@ async def search_prompt(
39
38
  Returns:
40
39
  Formatted search results with context
41
40
  """
42
- with logfire.span("Searching knowledge base", query=query, timeframe=timeframe): # pyright: ignore
43
- logger.info(f"Searching knowledge base, query: {query}, timeframe: {timeframe}")
41
+ logger.info(f"Searching knowledge base, query: {query}, timeframe: {timeframe}")
44
42
 
45
- search_results = await search_tool(SearchQuery(text=query, after_date=timeframe))
46
- return format_search_results(query, search_results, timeframe)
43
+ search_results = await search_tool(SearchQuery(text=query, after_date=timeframe))
44
+ return format_search_results(query, search_results, timeframe)
47
45
 
48
46
 
49
47
  def format_search_results(
@@ -65,11 +63,33 @@ def format_search_results(
65
63
 
66
64
  I couldn't find any results for this query.
67
65
 
68
- ## Suggestions
66
+ ## Opportunity to Capture Knowledge!
67
+
68
+ This is an excellent opportunity to create new knowledge on this topic. Consider:
69
+
70
+ ```python
71
+ await write_note(
72
+ title="{query.capitalize()}",
73
+ content=f'''
74
+ # {query.capitalize()}
75
+
76
+ ## Overview
77
+ [Summary of what we've discussed about {query}]
78
+
79
+ ## Observations
80
+ - [category] [First observation about {query}]
81
+ - [category] [Second observation about {query}]
82
+
83
+ ## Relations
84
+ - relates_to [[Other Relevant Topic]]
85
+ '''
86
+ )
87
+ ```
88
+
89
+ ## Other Suggestions
69
90
  - Try a different search term
70
91
  - Broaden your search criteria
71
92
  - Check recent activity with `recent_activity(timeframe="1w")`
72
- - Create new content with `write_note(...)`
73
93
  """)
74
94
 
75
95
  # Start building our summary with header
@@ -88,32 +108,38 @@ def format_search_results(
88
108
  for i, result in enumerate(results.results[:5]): # Limit to top 5 results
89
109
  summary += dedent(f"""
90
110
  ## {i + 1}. {result.title}
91
- - **Type**: {result.type}
111
+ - **Type**: {result.type.value}
92
112
  """)
93
113
 
94
114
  # Add creation date if available in metadata
95
- if hasattr(result, "metadata") and result.metadata and "created_at" in result.metadata:
115
+ if result.metadata and "created_at" in result.metadata:
96
116
  created_at = result.metadata["created_at"]
97
117
  if hasattr(created_at, "strftime"):
98
- summary += f"- **Created**: {created_at.strftime('%Y-%m-%d %H:%M')}\n"
118
+ summary += (
119
+ f"- **Created**: {created_at.strftime('%Y-%m-%d %H:%M')}\n" # pragma: no cover
120
+ )
99
121
  elif isinstance(created_at, str):
100
122
  summary += f"- **Created**: {created_at}\n"
101
123
 
102
124
  # Add score and excerpt
103
125
  summary += f"- **Relevance Score**: {result.score:.2f}\n"
126
+
104
127
  # Add excerpt if available in metadata
105
- if hasattr(result, "metadata") and result.metadata and "excerpt" in result.metadata:
106
- summary += f"- **Excerpt**: {result.metadata['excerpt']}\n"
128
+ if result.content:
129
+ summary += f"- **Excerpt**:\n{result.content}\n"
107
130
 
108
131
  # Add permalink for retrieving content
109
- if hasattr(result, "permalink") and result.permalink:
132
+ if result.permalink:
110
133
  summary += dedent(f"""
111
-
112
134
  You can view this content with: `read_note("{result.permalink}")`
113
135
  Or explore its context with: `build_context("memory://{result.permalink}")`
114
136
  """)
137
+ else:
138
+ summary += dedent(f"""
139
+ You can view this file with: `read_file("{result.file_path}")`
140
+ """) # pragma: no cover
115
141
 
116
- # Add next steps
142
+ # Add next steps with strong write encouragement
117
143
  summary += dedent(f"""
118
144
  ## Next Steps
119
145
 
@@ -122,6 +148,35 @@ def format_search_results(
122
148
  - Exclude terms: `search("{query} NOT exclude_term")`
123
149
  - View more results: `search("{query}", after_date=None)`
124
150
  - Check recent activity: `recent_activity()`
151
+
152
+ ## Synthesize and Capture Knowledge
153
+
154
+ Consider creating a new note that synthesizes what you've learned:
155
+
156
+ ```python
157
+ await write_note(
158
+ title="Synthesis of {query.capitalize()} Information",
159
+ content='''
160
+ # Synthesis of {query.capitalize()} Information
161
+
162
+ ## Overview
163
+ [Synthesis of the search results and your conversation]
164
+
165
+ ## Key Insights
166
+ [Summary of main points learned from these results]
167
+
168
+ ## Observations
169
+ - [insight] [Important observation from search results]
170
+ - [connection] [How this connects to other topics]
171
+
172
+ ## Relations
173
+ - relates_to [[{results.results[0].title if results.results else "Related Topic"}]]
174
+ - extends [[Another Relevant Topic]]
175
+ '''
176
+ )
177
+ ```
178
+
179
+ Remember that capturing synthesized knowledge is one of the most valuable features of Basic Memory.
125
180
  """)
126
181
 
127
182
  return summary