basic-memory 0.10.1__py3-none-any.whl → 0.12.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 (34) hide show
  1. basic_memory/__init__.py +1 -1
  2. basic_memory/api/app.py +42 -3
  3. basic_memory/cli/app.py +0 -7
  4. basic_memory/cli/commands/db.py +15 -2
  5. basic_memory/cli/commands/mcp.py +8 -1
  6. basic_memory/cli/commands/sync.py +1 -0
  7. basic_memory/cli/commands/tool.py +30 -17
  8. basic_memory/cli/main.py +16 -7
  9. basic_memory/config.py +71 -12
  10. basic_memory/db.py +3 -1
  11. basic_memory/file_utils.py +3 -0
  12. basic_memory/markdown/entity_parser.py +16 -7
  13. basic_memory/markdown/utils.py +21 -13
  14. basic_memory/mcp/prompts/continue_conversation.py +7 -7
  15. basic_memory/mcp/prompts/search.py +6 -6
  16. basic_memory/mcp/resources/ai_assistant_guide.md +5 -5
  17. basic_memory/mcp/server.py +2 -2
  18. basic_memory/mcp/tools/__init__.py +2 -2
  19. basic_memory/mcp/tools/read_note.py +3 -4
  20. basic_memory/mcp/tools/search.py +64 -28
  21. basic_memory/mcp/tools/write_note.py +3 -1
  22. basic_memory/repository/search_repository.py +11 -11
  23. basic_memory/schemas/search.py +2 -2
  24. basic_memory/services/context_service.py +1 -1
  25. basic_memory/services/entity_service.py +10 -10
  26. basic_memory/services/link_resolver.py +8 -1
  27. basic_memory/services/search_service.py +3 -23
  28. basic_memory/sync/sync_service.py +60 -23
  29. basic_memory/utils.py +10 -2
  30. {basic_memory-0.10.1.dist-info → basic_memory-0.12.0.dist-info}/METADATA +44 -6
  31. {basic_memory-0.10.1.dist-info → basic_memory-0.12.0.dist-info}/RECORD +34 -34
  32. {basic_memory-0.10.1.dist-info → basic_memory-0.12.0.dist-info}/entry_points.txt +1 -0
  33. {basic_memory-0.10.1.dist-info → basic_memory-0.12.0.dist-info}/WHEEL +0 -0
  34. {basic_memory-0.10.1.dist-info → basic_memory-0.12.0.dist-info}/licenses/LICENSE +0 -0
basic_memory/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.10.1"
3
+ __version__ = "0.12.0"
basic_memory/api/app.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """FastAPI application for basic-memory knowledge graph API."""
2
2
 
3
+ import asyncio
3
4
  from contextlib import asynccontextmanager
4
5
 
5
6
  from fastapi import FastAPI, HTTPException
@@ -7,16 +8,54 @@ from fastapi.exception_handlers import http_exception_handler
7
8
  from loguru import logger
8
9
 
9
10
  from basic_memory import db
10
- from basic_memory.config import config as app_config
11
- from basic_memory.api.routers import knowledge, search, memory, resource, project_info
11
+ from basic_memory.api.routers import knowledge, memory, project_info, resource, search
12
+ from basic_memory.config import config as project_config
13
+ from basic_memory.config import config_manager
14
+ from basic_memory.sync import SyncService, WatchService
15
+
16
+
17
+ async def run_background_sync(sync_service: SyncService, watch_service: WatchService): # pragma: no cover
18
+ logger.info(f"Starting watch service to sync file changes in dir: {project_config.home}")
19
+ # full sync
20
+ await sync_service.sync(project_config.home, show_progress=False)
21
+
22
+ # watch changes
23
+ await watch_service.run()
12
24
 
13
25
 
14
26
  @asynccontextmanager
15
27
  async def lifespan(app: FastAPI): # pragma: no cover
16
28
  """Lifecycle manager for the FastAPI app."""
17
- await db.run_migrations(app_config)
29
+ await db.run_migrations(project_config)
30
+
31
+ # app config
32
+ basic_memory_config = config_manager.load_config()
33
+ logger.info(f"Sync changes enabled: {basic_memory_config.sync_changes}")
34
+ logger.info(f"Update permalinks on move enabled: {basic_memory_config.update_permalinks_on_move}")
35
+
36
+ watch_task = None
37
+ if basic_memory_config.sync_changes:
38
+ # import after migrations have run
39
+ from basic_memory.cli.commands.sync import get_sync_service
40
+
41
+ sync_service = await get_sync_service()
42
+ watch_service = WatchService(
43
+ sync_service=sync_service,
44
+ file_service=sync_service.entity_service.file_service,
45
+ config=project_config,
46
+ )
47
+ watch_task = asyncio.create_task(run_background_sync(sync_service, watch_service))
48
+ else:
49
+ logger.info("Sync changes disabled. Skipping watch service.")
50
+
51
+
52
+ # proceed with startup
18
53
  yield
54
+
19
55
  logger.info("Shutting down Basic Memory API")
56
+ if watch_task:
57
+ watch_task.cancel()
58
+
20
59
  await db.shutdown_db()
21
60
 
22
61
 
basic_memory/cli/app.py CHANGED
@@ -1,11 +1,7 @@
1
- import asyncio
2
1
  from typing import Optional
3
2
 
4
3
  import typer
5
4
 
6
- from basic_memory import db
7
- from basic_memory.config import config
8
-
9
5
 
10
6
  def version_callback(value: bool) -> None:
11
7
  """Show version and exit."""
@@ -58,9 +54,6 @@ def app_callback(
58
54
  config = new_config
59
55
 
60
56
 
61
- # Run database migrations
62
- asyncio.run(db.run_migrations(config))
63
-
64
57
  # Register sub-command groups
65
58
  import_app = typer.Typer(help="Import data from various sources")
66
59
  app.add_typer(import_app, name="import")
@@ -1,10 +1,13 @@
1
1
  """Database management commands."""
2
2
 
3
+ import asyncio
4
+
3
5
  import typer
4
6
  from loguru import logger
5
7
 
6
- from basic_memory.alembic import migrations
8
+ from basic_memory import db
7
9
  from basic_memory.cli.app import app
10
+ from basic_memory.config import config
8
11
 
9
12
 
10
13
  @app.command()
@@ -14,7 +17,17 @@ def reset(
14
17
  """Reset database (drop all tables and recreate)."""
15
18
  if typer.confirm("This will delete all data in your db. Are you sure?"):
16
19
  logger.info("Resetting database...")
17
- migrations.reset_database()
20
+ # Get database path
21
+ db_path = config.database_path
22
+
23
+ # Delete the database file if it exists
24
+ if db_path.exists():
25
+ db_path.unlink()
26
+ logger.info(f"Database file deleted: {db_path}")
27
+
28
+ # Create a new empty database
29
+ asyncio.run(db.run_migrations(config))
30
+ logger.info("Database reset complete")
18
31
 
19
32
  if reindex:
20
33
  # Import and run sync
@@ -4,7 +4,7 @@ from loguru import logger
4
4
 
5
5
  import basic_memory
6
6
  from basic_memory.cli.app import app
7
- from basic_memory.config import config
7
+ from basic_memory.config import config, config_manager
8
8
 
9
9
  # Import mcp instance
10
10
  from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
@@ -19,8 +19,15 @@ def mcp(): # pragma: no cover
19
19
  home_dir = config.home
20
20
  project_name = config.project
21
21
 
22
+ # app config
23
+ basic_memory_config = config_manager.load_config()
24
+
22
25
  logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}")
23
26
  logger.info(f"Project: {project_name}")
24
27
  logger.info(f"Project directory: {home_dir}")
28
+ logger.info(f"Sync changes enabled: {basic_memory_config.sync_changes}")
29
+ logger.info(
30
+ f"Update permalinks on move enabled: {basic_memory_config.update_permalinks_on_move}"
31
+ )
25
32
 
26
33
  mcp_server.run()
@@ -70,6 +70,7 @@ async def get_sync_service(): # pragma: no cover
70
70
 
71
71
  # Create sync service
72
72
  sync_service = SyncService(
73
+ config=config,
73
74
  entity_service=entity_service,
74
75
  entity_parser=entity_parser,
75
76
  entity_repository=entity_repository,
@@ -2,31 +2,29 @@
2
2
 
3
3
  import asyncio
4
4
  import sys
5
- from typing import Optional, List, Annotated
5
+ from typing import Annotated, List, Optional
6
6
 
7
7
  import typer
8
8
  from loguru import logger
9
9
  from rich import print as rprint
10
10
 
11
11
  from basic_memory.cli.app import app
12
- from basic_memory.mcp.tools import build_context as mcp_build_context
13
- from basic_memory.mcp.tools import read_note as mcp_read_note
14
- from basic_memory.mcp.tools import recent_activity as mcp_recent_activity
15
- from basic_memory.mcp.tools import search as mcp_search
16
- from basic_memory.mcp.tools import write_note as mcp_write_note
17
12
 
18
13
  # Import prompts
19
14
  from basic_memory.mcp.prompts.continue_conversation import (
20
15
  continue_conversation as mcp_continue_conversation,
21
16
  )
22
-
23
17
  from basic_memory.mcp.prompts.recent_activity import (
24
18
  recent_activity_prompt as recent_activity_prompt,
25
19
  )
26
-
20
+ from basic_memory.mcp.tools import build_context as mcp_build_context
21
+ from basic_memory.mcp.tools import read_note as mcp_read_note
22
+ from basic_memory.mcp.tools import recent_activity as mcp_recent_activity
23
+ from basic_memory.mcp.tools import search_notes as mcp_search
24
+ from basic_memory.mcp.tools import write_note as mcp_write_note
27
25
  from basic_memory.schemas.base import TimeFrame
28
26
  from basic_memory.schemas.memory import MemoryUrl
29
- from basic_memory.schemas.search import SearchQuery, SearchItemType
27
+ from basic_memory.schemas.search import SearchItemType
30
28
 
31
29
  tool_app = typer.Typer()
32
30
  app.add_typer(tool_app, name="tool", help="Access to MCP tools via CLI")
@@ -180,8 +178,8 @@ def recent_activity(
180
178
  raise
181
179
 
182
180
 
183
- @tool_app.command()
184
- def search(
181
+ @tool_app.command("search-notes")
182
+ def search_notes(
185
183
  query: str,
186
184
  permalink: Annotated[bool, typer.Option("--permalink", help="Search permalink values")] = False,
187
185
  title: Annotated[bool, typer.Option("--title", help="Search title values")] = False,
@@ -198,13 +196,28 @@ def search(
198
196
  raise typer.Abort()
199
197
 
200
198
  try:
201
- search_query = SearchQuery(
202
- permalink_match=query if permalink else None,
203
- text=query if not (permalink or title) else None,
204
- title=query if title else None,
205
- after_date=after_date,
199
+ if permalink and title: # pragma: no cover
200
+ typer.echo(
201
+ "Use either --permalink or --title, not both. Exiting.",
202
+ err=True,
203
+ )
204
+ raise typer.Exit(1)
205
+
206
+ # set search type
207
+ search_type = ("permalink" if permalink else None,)
208
+ search_type = ("permalink_match" if permalink and "*" in query else None,)
209
+ search_type = ("title" if title else None,)
210
+ search_type = "text" if search_type is None else search_type
211
+
212
+ results = asyncio.run(
213
+ mcp_search(
214
+ query,
215
+ search_type=search_type,
216
+ page=page,
217
+ after_date=after_date,
218
+ page_size=page_size,
219
+ )
206
220
  )
207
- results = asyncio.run(mcp_search(query=search_query, page=page, page_size=page_size))
208
221
  # Use json module for more controlled serialization
209
222
  import json
210
223
 
basic_memory/cli/main.py CHANGED
@@ -1,21 +1,26 @@
1
1
  """Main CLI entry point for basic-memory.""" # pragma: no cover
2
2
 
3
- from basic_memory.cli.app import app # pragma: no cover
3
+ import asyncio
4
+
4
5
  import typer
5
6
 
7
+ from basic_memory.cli.app import app # pragma: no cover
8
+
6
9
  # Register commands
7
10
  from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
8
- status,
9
- sync,
10
11
  db,
11
- import_memory_json,
12
- mcp,
12
+ import_chatgpt,
13
13
  import_claude_conversations,
14
14
  import_claude_projects,
15
- import_chatgpt,
16
- tool,
15
+ import_memory_json,
16
+ mcp,
17
17
  project,
18
+ status,
19
+ sync,
20
+ tool,
18
21
  )
22
+ from basic_memory.config import config
23
+ from basic_memory.db import run_migrations as db_run_migrations
19
24
 
20
25
 
21
26
  # Version command
@@ -55,4 +60,8 @@ def main(
55
60
 
56
61
 
57
62
  if __name__ == "__main__": # pragma: no cover
63
+ # Run database migrations
64
+ asyncio.run(db_run_migrations(config))
65
+
66
+ # start the app
58
67
  app()
basic_memory/config.py CHANGED
@@ -5,12 +5,13 @@ import os
5
5
  from pathlib import Path
6
6
  from typing import Any, Dict, Literal, Optional
7
7
 
8
- import basic_memory
9
- from basic_memory.utils import setup_logging
10
8
  from loguru import logger
11
9
  from pydantic import Field, field_validator
12
10
  from pydantic_settings import BaseSettings, SettingsConfigDict
13
11
 
12
+ import basic_memory
13
+ from basic_memory.utils import setup_logging
14
+
14
15
  DATABASE_NAME = "memory.db"
15
16
  DATA_DIR_NAME = ".basic-memory"
16
17
  CONFIG_FILE_NAME = "config.json"
@@ -37,7 +38,11 @@ class ProjectConfig(BaseSettings):
37
38
  default=500, description="Milliseconds to wait after changes before syncing", gt=0
38
39
  )
39
40
 
40
- log_level: str = "DEBUG"
41
+ # update permalinks on move
42
+ update_permalinks_on_move: bool = Field(
43
+ default=False,
44
+ description="Whether to update permalinks when files are moved or renamed. default (False)",
45
+ )
41
46
 
42
47
  model_config = SettingsConfigDict(
43
48
  env_prefix="BASIC_MEMORY_",
@@ -76,6 +81,18 @@ class BasicMemoryConfig(BaseSettings):
76
81
  description="Name of the default project to use",
77
82
  )
78
83
 
84
+ log_level: str = "INFO"
85
+
86
+ update_permalinks_on_move: bool = Field(
87
+ default=False,
88
+ description="Whether to update permalinks when files are moved or renamed. default (False)",
89
+ )
90
+
91
+ sync_changes: bool = Field(
92
+ default=True,
93
+ description="Whether to sync changes in real time. default (True)",
94
+ )
95
+
79
96
  model_config = SettingsConfigDict(
80
97
  env_prefix="BASIC_MEMORY_",
81
98
  extra="ignore",
@@ -193,9 +210,14 @@ def get_project_config(project_name: Optional[str] = None) -> ProjectConfig:
193
210
  "BASIC_MEMORY_PROJECT", project_name or config_manager.default_project
194
211
  )
195
212
 
213
+ update_permalinks_on_move = config_manager.load_config().update_permalinks_on_move
196
214
  try:
197
215
  project_path = config_manager.get_project_path(actual_project_name)
198
- return ProjectConfig(home=project_path, project=actual_project_name)
216
+ return ProjectConfig(
217
+ home=project_path,
218
+ project=actual_project_name,
219
+ update_permalinks_on_move=update_permalinks_on_move,
220
+ )
199
221
  except ValueError: # pragma: no cover
200
222
  logger.warning(f"Project '{actual_project_name}' not found, using default")
201
223
  project_path = config_manager.get_project_path(config_manager.default_project)
@@ -213,11 +235,48 @@ user_home = Path.home()
213
235
  log_dir = user_home / DATA_DIR_NAME
214
236
  log_dir.mkdir(parents=True, exist_ok=True)
215
237
 
216
- setup_logging(
217
- env=config.env,
218
- home_dir=user_home, # Use user home for logs
219
- log_level=config.log_level,
220
- log_file=f"{DATA_DIR_NAME}/basic-memory.log",
221
- console=False,
222
- )
223
- logger.info(f"Starting Basic Memory {basic_memory.__version__} (Project: {config.project})")
238
+
239
+ def get_process_name(): # pragma: no cover
240
+ """
241
+ get the type of process for logging
242
+ """
243
+ import sys
244
+
245
+ if "sync" in sys.argv:
246
+ return "sync"
247
+ elif "mcp" in sys.argv:
248
+ return "mcp"
249
+ elif "cli" in sys.argv:
250
+ return "cli"
251
+ else:
252
+ return "api"
253
+
254
+
255
+ process_name = get_process_name()
256
+
257
+ # Global flag to track if logging has been set up
258
+ _LOGGING_SETUP = False
259
+
260
+
261
+ def setup_basic_memory_logging(): # pragma: no cover
262
+ """Set up logging for basic-memory, ensuring it only happens once."""
263
+ global _LOGGING_SETUP
264
+ if _LOGGING_SETUP:
265
+ # We can't log before logging is set up
266
+ # print("Skipping duplicate logging setup")
267
+ return
268
+
269
+ setup_logging(
270
+ env=config.env,
271
+ home_dir=user_home, # Use user home for logs
272
+ log_level=config_manager.load_config().log_level,
273
+ log_file=f"{DATA_DIR_NAME}/basic-memory-{process_name}.log",
274
+ console=False,
275
+ )
276
+
277
+ logger.info(f"Starting Basic Memory {basic_memory.__version__} (Project: {config.project})")
278
+ _LOGGING_SETUP = True
279
+
280
+
281
+ # Set up logging
282
+ setup_basic_memory_logging()
basic_memory/db.py CHANGED
@@ -146,7 +146,9 @@ async def engine_session_factory(
146
146
  _session_maker = None
147
147
 
148
148
 
149
- async def run_migrations(app_config: ProjectConfig, database_type=DatabaseType.FILESYSTEM):
149
+ async def run_migrations(
150
+ app_config: ProjectConfig, database_type=DatabaseType.FILESYSTEM
151
+ ): # pragma: no cover
150
152
  """Run any pending alembic migrations."""
151
153
  logger.info("Running database migrations...")
152
154
  try:
@@ -104,6 +104,9 @@ def has_frontmatter(content: str) -> bool:
104
104
  Returns:
105
105
  True if content has valid frontmatter markers (---), False otherwise
106
106
  """
107
+ if not content:
108
+ return False
109
+
107
110
  content = content.strip()
108
111
  if not content.startswith("---"):
109
112
  return False
@@ -92,27 +92,36 @@ class EntityParser:
92
92
  async def parse_file(self, path: Path | str) -> EntityMarkdown:
93
93
  """Parse markdown file into EntityMarkdown."""
94
94
 
95
- absolute_path = self.base_path / path
95
+ # TODO move to api endpoint to check if absolute path was requested
96
+ # Check if the path is already absolute
97
+ if (
98
+ isinstance(path, Path)
99
+ and path.is_absolute()
100
+ or (isinstance(path, str) and Path(path).is_absolute())
101
+ ):
102
+ absolute_path = Path(path)
103
+ else:
104
+ absolute_path = self.base_path / path
105
+
96
106
  # Parse frontmatter and content using python-frontmatter
97
- post = frontmatter.load(str(absolute_path))
107
+ file_content = absolute_path.read_text()
108
+ return await self.parse_file_content(absolute_path, file_content)
98
109
 
110
+ async def parse_file_content(self, absolute_path, file_content):
111
+ post = frontmatter.loads(file_content)
99
112
  # Extract file stat info
100
113
  file_stats = absolute_path.stat()
101
-
102
114
  metadata = post.metadata
103
- metadata["title"] = post.metadata.get("title", absolute_path.name)
115
+ metadata["title"] = post.metadata.get("title", absolute_path.stem)
104
116
  metadata["type"] = post.metadata.get("type", "note")
105
117
  tags = parse_tags(post.metadata.get("tags", [])) # pyright: ignore
106
118
  if tags:
107
119
  metadata["tags"] = tags
108
-
109
120
  # frontmatter
110
121
  entity_frontmatter = EntityFrontmatter(
111
122
  metadata=post.metadata,
112
123
  )
113
-
114
124
  entity_content = parse(post.content)
115
-
116
125
  return EntityMarkdown(
117
126
  frontmatter=entity_frontmatter,
118
127
  content=post.content,
@@ -1,14 +1,14 @@
1
1
  """Utilities for converting between markdown and entity models."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Optional, Any
4
+ from typing import Any, Optional
5
5
 
6
6
  from frontmatter import Post
7
7
 
8
- from basic_memory.file_utils import has_frontmatter, remove_frontmatter
8
+ from basic_memory.file_utils import has_frontmatter, remove_frontmatter, parse_frontmatter
9
9
  from basic_memory.markdown import EntityMarkdown
10
- from basic_memory.models import Entity, Observation as ObservationModel
11
- from basic_memory.utils import generate_permalink
10
+ from basic_memory.models import Entity
11
+ from basic_memory.models import Observation as ObservationModel
12
12
 
13
13
 
14
14
  def entity_model_from_markdown(
@@ -32,16 +32,13 @@ def entity_model_from_markdown(
32
32
  if not markdown.created or not markdown.modified: # pragma: no cover
33
33
  raise ValueError("Both created and modified dates are required in markdown")
34
34
 
35
- # Generate permalink if not provided
36
- permalink = markdown.frontmatter.permalink or generate_permalink(file_path)
37
-
38
35
  # Create or update entity
39
36
  model = entity or Entity()
40
37
 
41
38
  # Update basic fields
42
39
  model.title = markdown.frontmatter.title
43
40
  model.entity_type = markdown.frontmatter.type
44
- model.permalink = permalink
41
+ model.permalink = markdown.frontmatter.permalink
45
42
  model.file_path = str(file_path)
46
43
  model.content_type = "text/markdown"
47
44
  model.created_at = markdown.created
@@ -77,22 +74,33 @@ async def schema_to_markdown(schema: Any) -> Post:
77
74
  """
78
75
  # Extract content and metadata
79
76
  content = schema.content or ""
80
- frontmatter_metadata = dict(schema.entity_metadata or {})
77
+ entity_metadata = dict(schema.entity_metadata or {})
81
78
 
82
79
  # if the content contains frontmatter, remove it and merge
83
80
  if has_frontmatter(content):
81
+ content_frontmatter = parse_frontmatter(content)
84
82
  content = remove_frontmatter(content)
85
83
 
84
+ # Merge content frontmatter with entity metadata
85
+ # (entity_metadata takes precedence for conflicts)
86
+ content_frontmatter.update(entity_metadata)
87
+ entity_metadata = content_frontmatter
88
+
86
89
  # Remove special fields for ordered frontmatter
87
90
  for field in ["type", "title", "permalink"]:
88
- frontmatter_metadata.pop(field, None)
91
+ entity_metadata.pop(field, None)
89
92
 
90
- # Create Post with ordered fields
93
+ # Create Post with fields ordered by insert order
91
94
  post = Post(
92
95
  content,
93
96
  title=schema.title,
94
97
  type=schema.entity_type,
95
- permalink=schema.permalink,
96
- **frontmatter_metadata,
97
98
  )
99
+ # set the permalink if passed in
100
+ if schema.permalink:
101
+ post.metadata["permalink"] = schema.permalink
102
+
103
+ if entity_metadata:
104
+ post.metadata.update(entity_metadata)
105
+
98
106
  return post
@@ -5,19 +5,19 @@ providing context from previous interactions to maintain continuity.
5
5
  """
6
6
 
7
7
  from textwrap import dedent
8
- from typing import Optional, Annotated
8
+ from typing import Annotated, Optional
9
9
 
10
10
  from loguru import logger
11
11
  from pydantic import Field
12
12
 
13
- from basic_memory.mcp.prompts.utils import format_prompt_context, PromptContext, PromptContextItem
13
+ from basic_memory.mcp.prompts.utils import PromptContext, PromptContextItem, format_prompt_context
14
14
  from basic_memory.mcp.server import mcp
15
15
  from basic_memory.mcp.tools.build_context import build_context
16
16
  from basic_memory.mcp.tools.recent_activity import recent_activity
17
- from basic_memory.mcp.tools.search import search
17
+ from basic_memory.mcp.tools.search import search_notes
18
18
  from basic_memory.schemas.base import TimeFrame
19
19
  from basic_memory.schemas.memory import GraphContext
20
- from basic_memory.schemas.search import SearchQuery, SearchItemType
20
+ from basic_memory.schemas.search import SearchItemType
21
21
 
22
22
 
23
23
  @mcp.prompt(
@@ -47,8 +47,8 @@ async def continue_conversation(
47
47
 
48
48
  # If topic provided, search for it
49
49
  if topic:
50
- search_results = await search(
51
- SearchQuery(text=topic, after_date=timeframe, types=[SearchItemType.ENTITY])
50
+ search_results = await search_notes(
51
+ query=topic, after_date=timeframe, entity_types=[SearchItemType.ENTITY]
52
52
  )
53
53
 
54
54
  # Build context from results
@@ -93,7 +93,7 @@ async def continue_conversation(
93
93
  ## Next Steps
94
94
 
95
95
  You can:
96
- - Explore more with: `search({{"text": "{topic}"}})`
96
+ - Explore more with: `search_notes({{"text": "{topic}"}})`
97
97
  - See what's changed: `recent_activity(timeframe="{timeframe or "7d"}")`
98
98
  - **Record new learnings or decisions from this conversation:** `write_note(title="[Create a meaningful title]", content="[Content with observations and relations]")`
99
99
 
@@ -10,9 +10,9 @@ from loguru import logger
10
10
  from pydantic import Field
11
11
 
12
12
  from basic_memory.mcp.server import mcp
13
- from basic_memory.mcp.tools.search import search as search_tool
13
+ from basic_memory.mcp.tools.search import search_notes as search_tool
14
14
  from basic_memory.schemas.base import TimeFrame
15
- from basic_memory.schemas.search import SearchQuery, SearchResponse
15
+ from basic_memory.schemas.search import SearchResponse
16
16
 
17
17
 
18
18
  @mcp.prompt(
@@ -40,7 +40,7 @@ async def search_prompt(
40
40
  """
41
41
  logger.info(f"Searching knowledge base, query: {query}, timeframe: {timeframe}")
42
42
 
43
- search_results = await search_tool(SearchQuery(text=query, after_date=timeframe))
43
+ search_results = await search_tool(query=query, after_date=timeframe)
44
44
  return format_search_results(query, search_results, timeframe)
45
45
 
46
46
 
@@ -144,9 +144,9 @@ def format_search_results(
144
144
  ## Next Steps
145
145
 
146
146
  You can:
147
- - Refine your search: `search("{query} AND additional_term")`
148
- - Exclude terms: `search("{query} NOT exclude_term")`
149
- - View more results: `search("{query}", after_date=None)`
147
+ - Refine your search: `search_notes("{query} AND additional_term")`
148
+ - Exclude terms: `search_notes("{query} NOT exclude_term")`
149
+ - View more results: `search_notes("{query}", after_date=None)`
150
150
  - Check recent activity: `recent_activity()`
151
151
 
152
152
  ## Synthesize and Capture Knowledge
@@ -49,7 +49,7 @@ content = await read_note("specs/search-design") # By path
49
49
  content = await read_note("memory://specs/search") # By memory URL
50
50
 
51
51
  # Searching for knowledge
52
- results = await search(
52
+ results = await search_notes(
53
53
  query="authentication system", # Text to search for
54
54
  page=1, # Optional: Pagination
55
55
  page_size=10 # Optional: Results per page
@@ -154,7 +154,7 @@ Users will interact with Basic Memory in patterns like:
154
154
  Human: "What were our decisions about auth?"
155
155
 
156
156
  You: Let me find that information for you.
157
- [Use search() to find relevant notes]
157
+ [Use search_notes() to find relevant notes]
158
158
  [Then build_context() to understand connections]
159
159
  ```
160
160
 
@@ -263,7 +263,7 @@ When creating relations, you can:
263
263
  # Example workflow for creating notes with effective relations
264
264
  async def create_note_with_effective_relations():
265
265
  # Search for existing entities to reference
266
- search_results = await search("travel")
266
+ search_results = await search_notes("travel")
267
267
  existing_entities = [result.title for result in search_results.primary_results]
268
268
 
269
269
  # Check if specific entities exist
@@ -335,7 +335,7 @@ Common issues to watch for:
335
335
  content = await read_note("Document")
336
336
  except:
337
337
  # Try search instead
338
- results = await search("Document")
338
+ results = await search_notes("Document")
339
339
  if results and results.primary_results:
340
340
  # Found something similar
341
341
  content = await read_note(results.primary_results[0].permalink)
@@ -381,7 +381,7 @@ Common issues to watch for:
381
381
  - **Create deliberate relations**: Connect each note to at least 2-3 related entities
382
382
  - **Use existing entities**: Before creating a new relation, search for existing entities
383
383
  - **Verify wikilinks**: When referencing `[[Entity]]`, use exact titles of existing notes
384
- - **Check accuracy**: Use `search()` or `recent_activity()` to confirm entity titles
384
+ - **Check accuracy**: Use `search_notes()` or `recent_activity()` to confirm entity titles
385
385
  - **Use precise relation types**: Choose specific relation types that convey meaning (e.g., "implements" instead
386
386
  of "relates_to")
387
387
  - **Consider bidirectional relations**: When appropriate, create inverse relations in both entities