Skip to content

Commit 4e53bb8

Browse files
authored
refactor(core): simplify note write flow (#739)
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 8f2b25f commit 4e53bb8

17 files changed

+1275
-1578
lines changed

src/basic_memory/api/v2/routers/knowledge_router.py

Lines changed: 222 additions & 441 deletions
Large diffs are not rendered by default.

src/basic_memory/cli/commands/doctor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async def run_doctor() -> None:
6969
content=f"# {api_note_title}\n\n- [note] API to file check",
7070
entity_metadata={"tags": ["doctor"]},
7171
)
72-
api_result = await knowledge_client.create_entity(api_note.model_dump(), fast=False)
72+
api_result = await knowledge_client.create_entity(api_note.model_dump())
7373

7474
api_file = project_path / api_result.file_path
7575
if not api_file.exists():

src/basic_memory/deps/services.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -492,36 +492,13 @@ def schedule(self, task_name: str, **payload: Any) -> None:
492492

493493

494494
async def get_task_scheduler(
495-
entity_service: EntityServiceV2ExternalDep,
496495
sync_service: SyncServiceV2ExternalDep,
497496
search_service: SearchServiceV2ExternalDep,
498497
project_config: ProjectConfigV2ExternalDep,
499498
app_config: AppConfigDep,
500499
) -> TaskScheduler:
501500
"""Create a scheduler that maps task specs to coroutines."""
502501

503-
scheduler: LocalTaskScheduler | None = None
504-
505-
async def _reindex_entity(
506-
entity_id: int,
507-
resolve_relations: bool = False,
508-
**_: Any,
509-
) -> None:
510-
await entity_service.reindex_entity(entity_id)
511-
# Trigger: caller requests relation resolution
512-
# Why: resolve forward references created before the entity existed
513-
# Outcome: updates unresolved relations pointing to this entity
514-
if resolve_relations:
515-
await sync_service.resolve_relations(entity_id=entity_id)
516-
# Trigger: semantic search enabled in local config.
517-
# Why: vector chunks are derived and should refresh after canonical reindex completes.
518-
# Outcome: schedules out-of-band vector sync without extending write latency.
519-
if app_config.semantic_search_enabled and scheduler is not None:
520-
scheduler.schedule("sync_entity_vectors", entity_id=entity_id)
521-
522-
async def _resolve_relations(entity_id: int, **_: Any) -> None:
523-
await sync_service.resolve_relations(entity_id=entity_id)
524-
525502
async def _sync_entity_vectors(entity_id: int, **_: Any) -> None:
526503
await search_service.sync_entity_vectors(entity_id)
527504

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

538515
scheduler = LocalTaskScheduler(
539516
{
540-
"reindex_entity": _reindex_entity,
541-
"resolve_relations": _resolve_relations,
542517
"sync_entity_vectors": _sync_entity_vectors,
543518
"sync_project": _sync_project,
544519
"reindex_project": _reindex_project,

src/basic_memory/mcp/clients/knowledge.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def __init__(self, http_client: AsyncClient, project_id: str):
4444

4545
# --- Entity CRUD Operations ---
4646

47-
async def create_entity(
48-
self, entity_data: dict[str, Any], *, fast: bool | None = None
49-
) -> EntityResponse:
47+
async def create_entity(self, entity_data: dict[str, Any]) -> EntityResponse:
5048
"""Create a new entity.
5149
5250
Args:
@@ -58,18 +56,15 @@ async def create_entity(
5856
Raises:
5957
ToolError: If the request fails
6058
"""
61-
params = {"fast": fast} if fast is not None else None
6259
with telemetry.scope(
6360
"mcp.client.knowledge.create_entity",
6461
client_name="knowledge",
6562
operation="create_entity",
66-
fast=fast,
6763
):
6864
response = await call_post(
6965
self.http_client,
7066
f"{self._base_path}/entities",
7167
json=entity_data,
72-
params=params,
7368
client_name="knowledge",
7469
operation="create_entity",
7570
path_template="/v2/projects/{project_id}/knowledge/entities",
@@ -80,8 +75,6 @@ async def update_entity(
8075
self,
8176
entity_id: str,
8277
entity_data: dict[str, Any],
83-
*,
84-
fast: bool | None = None,
8578
) -> EntityResponse:
8679
"""Update an existing entity (full replacement).
8780
@@ -95,18 +88,15 @@ async def update_entity(
9588
Raises:
9689
ToolError: If the request fails
9790
"""
98-
params = {"fast": fast} if fast is not None else None
9991
with telemetry.scope(
10092
"mcp.client.knowledge.update_entity",
10193
client_name="knowledge",
10294
operation="update_entity",
103-
fast=fast,
10495
):
10596
response = await call_put(
10697
self.http_client,
10798
f"{self._base_path}/entities/{entity_id}",
10899
json=entity_data,
109-
params=params,
110100
client_name="knowledge",
111101
operation="update_entity",
112102
path_template="/v2/projects/{project_id}/knowledge/entities/{entity_id}",
@@ -143,8 +133,6 @@ async def patch_entity(
143133
self,
144134
entity_id: str,
145135
patch_data: dict[str, Any],
146-
*,
147-
fast: bool | None = None,
148136
) -> EntityResponse:
149137
"""Partially update an entity.
150138
@@ -158,18 +146,15 @@ async def patch_entity(
158146
Raises:
159147
ToolError: If the request fails
160148
"""
161-
params = {"fast": fast} if fast is not None else None
162149
with telemetry.scope(
163150
"mcp.client.knowledge.patch_entity",
164151
client_name="knowledge",
165152
operation="patch_entity",
166-
fast=fast,
167153
):
168154
response = await call_patch(
169155
self.http_client,
170156
f"{self._base_path}/entities/{entity_id}",
171157
json=patch_data,
172-
params=params,
173158
client_name="knowledge",
174159
operation="patch_entity",
175160
path_template="/v2/projects/{project_id}/knowledge/entities/{entity_id}",

src/basic_memory/mcp/tools/edit_note.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,7 @@ async def edit_note(
374374
directory=directory,
375375
operation=operation,
376376
)
377-
result = await knowledge_client.create_entity(
378-
entity.model_dump(), fast=False
379-
)
377+
result = await knowledge_client.create_entity(entity.model_dump())
380378
file_created = True
381379
else:
382380
# find_replace/replace_section require existing content — re-raise
@@ -399,9 +397,7 @@ async def edit_note(
399397
edit_data["expected_replacements"] = str(effective_replacements)
400398

401399
# Call the PATCH endpoint
402-
result = await knowledge_client.patch_entity(
403-
entity_id, edit_data, fast=False
404-
)
400+
result = await knowledge_client.patch_entity(entity_id, edit_data)
405401

406402
# --- Format response ---
407403
# result is always set: either by create_entity (auto-create) or patch_entity (edit)

src/basic_memory/mcp/tools/write_note.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ async def write_note(
222222
logger.debug(f"Attempting to create entity permalink={entity.permalink}")
223223
action = "Created" # Default to created
224224
try:
225-
result = await knowledge_client.create_entity(entity.model_dump(), fast=False)
225+
result = await knowledge_client.create_entity(entity.model_dump())
226226
action = "Created"
227227
except Exception as e:
228228
# If creation failed due to conflict (already exists), try to update
@@ -260,7 +260,7 @@ async def write_note(
260260
) # pragma: no cover
261261
entity_id = await knowledge_client.resolve_entity(entity.permalink)
262262
result = await knowledge_client.update_entity(
263-
entity_id, entity.model_dump(), fast=False
263+
entity_id, entity.model_dump()
264264
)
265265
action = "Updated"
266266
except Exception as update_error: # pragma: no cover

src/basic_memory/models/knowledge.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class Entity(Base):
6262
)
6363

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

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

279-
id: Mapped[int] = mapped_column(Integer, primary_key=True)
279+
id: Mapped[int] = mapped_column(Integer, primary_key=True) # pyright: ignore [reportIncompatibleVariableOverride]
280280
project_id: Mapped[int] = mapped_column(Integer, ForeignKey("project.id"), index=True)
281281
from_id: Mapped[int] = mapped_column(Integer, ForeignKey("entity.id", ondelete="CASCADE"))
282282
to_id: Mapped[Optional[int]] = mapped_column(

0 commit comments

Comments
 (0)