Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
663 changes: 222 additions & 441 deletions src/basic_memory/api/v2/routers/knowledge_router.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/basic_memory/cli/commands/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async def run_doctor() -> None:
content=f"# {api_note_title}\n\n- [note] API to file check",
entity_metadata={"tags": ["doctor"]},
)
api_result = await knowledge_client.create_entity(api_note.model_dump(), fast=False)
api_result = await knowledge_client.create_entity(api_note.model_dump())

api_file = project_path / api_result.file_path
if not api_file.exists():
Expand Down
25 changes: 0 additions & 25 deletions src/basic_memory/deps/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,36 +492,13 @@ def schedule(self, task_name: str, **payload: Any) -> None:


async def get_task_scheduler(
entity_service: EntityServiceV2ExternalDep,
sync_service: SyncServiceV2ExternalDep,
search_service: SearchServiceV2ExternalDep,
project_config: ProjectConfigV2ExternalDep,
app_config: AppConfigDep,
) -> TaskScheduler:
"""Create a scheduler that maps task specs to coroutines."""

scheduler: LocalTaskScheduler | None = None

async def _reindex_entity(
entity_id: int,
resolve_relations: bool = False,
**_: Any,
) -> None:
await entity_service.reindex_entity(entity_id)
# Trigger: caller requests relation resolution
# Why: resolve forward references created before the entity existed
# Outcome: updates unresolved relations pointing to this entity
if resolve_relations:
await sync_service.resolve_relations(entity_id=entity_id)
# Trigger: semantic search enabled in local config.
# Why: vector chunks are derived and should refresh after canonical reindex completes.
# Outcome: schedules out-of-band vector sync without extending write latency.
if app_config.semantic_search_enabled and scheduler is not None:
scheduler.schedule("sync_entity_vectors", entity_id=entity_id)

async def _resolve_relations(entity_id: int, **_: Any) -> None:
await sync_service.resolve_relations(entity_id=entity_id)

async def _sync_entity_vectors(entity_id: int, **_: Any) -> None:
await search_service.sync_entity_vectors(entity_id)

Expand All @@ -537,8 +514,6 @@ async def _reindex_project(**_: Any) -> None:

scheduler = LocalTaskScheduler(
{
"reindex_entity": _reindex_entity,
"resolve_relations": _resolve_relations,
"sync_entity_vectors": _sync_entity_vectors,
"sync_project": _sync_project,
"reindex_project": _reindex_project,
Expand Down
17 changes: 1 addition & 16 deletions src/basic_memory/mcp/clients/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ def __init__(self, http_client: AsyncClient, project_id: str):

# --- Entity CRUD Operations ---

async def create_entity(
self, entity_data: dict[str, Any], *, fast: bool | None = None
) -> EntityResponse:
async def create_entity(self, entity_data: dict[str, Any]) -> EntityResponse:
"""Create a new entity.

Args:
Expand All @@ -58,18 +56,15 @@ async def create_entity(
Raises:
ToolError: If the request fails
"""
params = {"fast": fast} if fast is not None else None
with telemetry.scope(
"mcp.client.knowledge.create_entity",
client_name="knowledge",
operation="create_entity",
fast=fast,
):
response = await call_post(
self.http_client,
f"{self._base_path}/entities",
json=entity_data,
params=params,
client_name="knowledge",
operation="create_entity",
path_template="/v2/projects/{project_id}/knowledge/entities",
Expand All @@ -80,8 +75,6 @@ async def update_entity(
self,
entity_id: str,
entity_data: dict[str, Any],
*,
fast: bool | None = None,
) -> EntityResponse:
"""Update an existing entity (full replacement).

Expand All @@ -95,18 +88,15 @@ async def update_entity(
Raises:
ToolError: If the request fails
"""
params = {"fast": fast} if fast is not None else None
with telemetry.scope(
"mcp.client.knowledge.update_entity",
client_name="knowledge",
operation="update_entity",
fast=fast,
):
response = await call_put(
self.http_client,
f"{self._base_path}/entities/{entity_id}",
json=entity_data,
params=params,
client_name="knowledge",
operation="update_entity",
path_template="/v2/projects/{project_id}/knowledge/entities/{entity_id}",
Expand Down Expand Up @@ -143,8 +133,6 @@ async def patch_entity(
self,
entity_id: str,
patch_data: dict[str, Any],
*,
fast: bool | None = None,
) -> EntityResponse:
"""Partially update an entity.

Expand All @@ -158,18 +146,15 @@ async def patch_entity(
Raises:
ToolError: If the request fails
"""
params = {"fast": fast} if fast is not None else None
with telemetry.scope(
"mcp.client.knowledge.patch_entity",
client_name="knowledge",
operation="patch_entity",
fast=fast,
):
response = await call_patch(
self.http_client,
f"{self._base_path}/entities/{entity_id}",
json=patch_data,
params=params,
client_name="knowledge",
operation="patch_entity",
path_template="/v2/projects/{project_id}/knowledge/entities/{entity_id}",
Expand Down
8 changes: 2 additions & 6 deletions src/basic_memory/mcp/tools/edit_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,7 @@ async def edit_note(
directory=directory,
operation=operation,
)
result = await knowledge_client.create_entity(
entity.model_dump(), fast=False
)
result = await knowledge_client.create_entity(entity.model_dump())
file_created = True
else:
# find_replace/replace_section require existing content — re-raise
Expand All @@ -399,9 +397,7 @@ async def edit_note(
edit_data["expected_replacements"] = str(effective_replacements)

# Call the PATCH endpoint
result = await knowledge_client.patch_entity(
entity_id, edit_data, fast=False
)
result = await knowledge_client.patch_entity(entity_id, edit_data)

# --- Format response ---
# result is always set: either by create_entity (auto-create) or patch_entity (edit)
Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/mcp/tools/write_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ async def write_note(
logger.debug(f"Attempting to create entity permalink={entity.permalink}")
action = "Created" # Default to created
try:
result = await knowledge_client.create_entity(entity.model_dump(), fast=False)
result = await knowledge_client.create_entity(entity.model_dump())
action = "Created"
except Exception as e:
# If creation failed due to conflict (already exists), try to update
Expand Down Expand Up @@ -260,7 +260,7 @@ async def write_note(
) # pragma: no cover
entity_id = await knowledge_client.resolve_entity(entity.permalink)
result = await knowledge_client.update_entity(
entity_id, entity.model_dump(), fast=False
entity_id, entity.model_dump()
)
action = "Updated"
except Exception as update_error: # pragma: no cover
Expand Down
6 changes: 3 additions & 3 deletions src/basic_memory/models/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Entity(Base):
)

# Core identity
id: Mapped[int] = mapped_column(Integer, primary_key=True)
id: Mapped[int] = mapped_column(Integer, primary_key=True) # pyright: ignore [reportIncompatibleVariableOverride]
# External UUID for API references - stable identifier that won't change
external_id: Mapped[str] = mapped_column(String, unique=True, default=lambda: str(uuid.uuid4()))
title: Mapped[str] = mapped_column(String)
Expand Down Expand Up @@ -229,7 +229,7 @@ class Observation(Base):
Index("ix_observation_category", "category"), # Add category index
)

id: Mapped[int] = mapped_column(Integer, primary_key=True)
id: Mapped[int] = mapped_column(Integer, primary_key=True) # pyright: ignore [reportIncompatibleVariableOverride]
project_id: Mapped[int] = mapped_column(Integer, ForeignKey("project.id"), index=True)
entity_id: Mapped[int] = mapped_column(Integer, ForeignKey("entity.id", ondelete="CASCADE"))
content: Mapped[str] = mapped_column(Text)
Expand Down Expand Up @@ -276,7 +276,7 @@ class Relation(Base):
Index("ix_relation_to_id", "to_id"),
)

id: Mapped[int] = mapped_column(Integer, primary_key=True)
id: Mapped[int] = mapped_column(Integer, primary_key=True) # pyright: ignore [reportIncompatibleVariableOverride]
project_id: Mapped[int] = mapped_column(Integer, ForeignKey("project.id"), index=True)
from_id: Mapped[int] = mapped_column(Integer, ForeignKey("entity.id", ondelete="CASCADE"))
to_id: Mapped[Optional[int]] = mapped_column(
Expand Down
Loading
Loading