Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/basic_memory/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ async def entity_already_exists_error_handler(request: Request, exc: EntityAlrea
request,
HTTPException(
status_code=409,
detail="Note already exists. Use edit_note to modify it, or delete it first.",
detail="Note already exists. Use bm_edit to modify it, or delete it first.",
),
)

Expand Down
2 changes: 1 addition & 1 deletion src/basic_memory/cli/commands/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class HarnessProfile:
default_recall_prompt=(
"You have Basic Memory available for this project. Before answering recall "
'questions ("what did we decide", "where did we leave off"), search the graph '
"first — prefer structured filters (search_notes with type/status). When the "
"first — prefer structured filters (bm_search with type/status). When the "
"user makes a material decision, capture it as a note with type: decision. "
"Cite permalinks when referencing prior work."
),
Expand Down
6 changes: 3 additions & 3 deletions src/basic_memory/config_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def __init__(self, **data: Any) -> None: ...
)
default_search_type: Literal["text", "vector", "hybrid"] | None = Field(
default=None,
description="Default search type for search_notes when not specified per-query. "
description="Default search mode for bm_search when not specified per-query. "
"Valid values: text, vector, hybrid. "
"When unset, defaults to 'hybrid' if semantic search is enabled, otherwise 'text'.",
)
Expand Down Expand Up @@ -591,8 +591,8 @@ def __init__(self, **data: Any) -> None: ...
write_note_overwrite_default: bool = Field(
default=False,
description=(
"Default value for write_note's overwrite parameter. "
"When False (default), write_note errors if note already exists. "
"Default value for bm_write's overwrite parameter. "
"When False (default), bm_write errors if note already exists. "
"Set to True to restore pre-v0.20 upsert behavior. "
"Env: BASIC_MEMORY_WRITE_NOTE_OVERWRITE_DEFAULT"
),
Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/indexing/accepted_note_mutation_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def accepted_note_integrity_rejection(error: IntegrityError) -> AcceptedNoteMuta
if conflict_kind is RuntimeAcceptedNoteWriteConflictKind.file_path:
return AcceptedNoteMutationRejection(
kind=AcceptedNoteMutationRejectKind.conflict,
detail="Note already exists. Use edit_note to modify it, or delete it first.",
detail="Note already exists. Use bm_edit to modify it, or delete it first.",
)

if conflict_kind is RuntimeAcceptedNoteWriteConflictKind.external_id:
Expand Down Expand Up @@ -1641,7 +1641,7 @@ def reject_accepted_note_file_path_conflict(
):
reject_accepted_note_mutation(
AcceptedNoteMutationRejectKind.conflict,
"Note already exists. Use edit_note to modify it, or delete it first.",
"Note already exists. Use bm_edit to modify it, or delete it first.",
)


Expand Down
26 changes: 26 additions & 0 deletions src/basic_memory/man/man3/bm-read(3).md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ bm_read(identifier, section=None, start_line=None, end_line=None,
output_format="text", project=None)
```

Cloud additionally supports the following hosted signature. These options are not
available on the local core tool:

```
bm_read(identifier, section=None, start_line=None, end_line=None,
max_tokens=None, include_frontmatter=True, include_content=True,
review_mode="structured", view="markdown", version=None,
versions=False, cursor=None, output_format="text", project=None)
```

## DESCRIPTION

Returns one note's Markdown, frontmatter included. The identifier can be a file
Expand Down Expand Up @@ -55,6 +65,22 @@ The shell counterpart is cat(1).
- **output_format** (optional, default: "text")
- **project** (string | null, optional, default: None) — Project name, 'workspace/project', or project external id. With multiple addressable projects, scope note and search calls explicitly with this parameter or a project-qualified target.

## CLOUD PARAMETERS

Cloud's `bm_read` adds these parameters to the core parameters above:

- **review_mode** ("structured" | "raw", optional, default: "structured") — Return prose with open review comments and suggestions as threads, or the stored bytes including review markup with "raw".
- **view** ("markdown" | "text", optional, default: "markdown") — "text" renders `::bm-*` components as Markdown and runs their live queries. Edit from the Markdown view.
- **version** (string | null, optional, default: None) — Read a stored note version, using a `version_id` from `versions=True`.
- **versions** (boolean, optional, default: False) — List stored note versions, newest first, instead of reading the note.
- **cursor** (string | null, optional, default: None) — With `versions=True`, continue from the previous page's `next_cursor`.

A versions listing cannot be combined with a version, slices, `view="text"`,
or `include_content=False`. A cursor requires `versions=True`. Stored versions
and the text view must be read whole. The text view requires
`review_mode="structured"` and cannot read a stored version because live queries
show current data. Stored versions are available for Markdown notes only.

## EXAMPLES

```
Expand Down
2 changes: 1 addition & 1 deletion src/basic_memory/man/man3/build-context(3).md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Compact output is not a fixed token limit.
- **page_size** (integer, optional, default: 10) — Number of primary results to return per page (default: 10, maximum: 50)
- **max_related** (integer, optional, default: 10) — Maximum total related results to return (default: 10, maximum: 100)
- **output_format** (string, optional, default: "json") — Response format - "json" for structured JSON dict, "text" for compact markdown text
- **compact** (boolean, optional, default: False) — Omit note bodies and entity observation lists for graph discovery; the server skips loading observations. Preserve identifiers, relation targets and pagination; use read_note for selected content. Not a guaranteed token budget.
- **compact** (boolean, optional, default: False) — Omit note bodies and entity observation lists for graph discovery; the server skips loading observations. Preserve identifiers, relation targets and pagination; use bm_read for selected content. Not a guaranteed token budget.

## MCP USAGE

Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/man/man3/edit-note(3).md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ permalink, or memory:// URL — there is no fuzzy fallback for edits.

## PARAMETERS

- **identifier** (string, required) — The exact title, permalink, or memory:// URL of the note to edit. Must be an exact match - fuzzy matching is not supported for edit operations. Use search_notes() or read_note() first to find the correct identifier if uncertain. From the CLI this is a positional argument, not a flag.
- **identifier** (string, required) — The exact title, permalink, or memory:// URL of the note to edit. Must be an exact match - fuzzy matching is not supported for edit operations. Use bm_search() or bm_read() first to find the correct identifier if uncertain. From the CLI this is a positional argument, not a flag.
- **operation** (string, required) — The editing operation to perform: - "append": Add content to the end of the note (creates the note if it doesn't exist) - "prepend": Add content to the beginning of the note (creates the note if it doesn't exist) - "find_replace": Replace occurrences of find_text with content (note must exist) - "replace_section": Replace a markdown section identified by its header (note and section must exist). The heading must match exactly, including trailing tags or text; a miss fails without writing. By default the section spans through the next heading of the same or higher level, so its subsections are replaced too; see replace_subsections. - "insert_before_section": Insert content before a section heading without consuming it (note must exist) - "insert_after_section": Insert content after a section heading without consuming it (note must exist)
- **content** (string, required) — The content to add or use for replacement
- **project** (string | null, optional, default: None) — Project name to edit in. Optional - server will resolve using hierarchy. Use "workspace/project" to route to a project in a specific cloud workspace. If unknown, use list_memory_projects() to discover available projects.
Expand All @@ -69,7 +69,7 @@ permalink, or memory:// URL — there is no fuzzy fallback for edits.
- **expected_replacements** (integer | null, optional, default: None) — For find_replace: required occurrence count of find_text (default 1). A mismatch fails without writing.
- **replace_subsections** (boolean | null, optional, default: None) — For replace_section operation. Default (true): the section spans everything through the next heading of the same or higher level in the original note, so replacing "## Section" also replaces its "###" subsections — the replacement content may freely introduce new headings. Set to false to replace only the immediate content under the header, stopping at the next heading of any level and preserving subsections.
- **metadata** (object | null, optional, default: None) — Optional dict of frontmatter fields to merge, independent of `operation`. Provided keys overwrite existing frontmatter values (or are added if new); unrelated frontmatter keys and the note body are left untouched. Can be combined with any operation in the same call. `title` and `permalink` are ignored since those have their own dedicated handling; `type` is applied like any other frontmatter field. Key deletion is not supported.
- **expected_checksum** (string | null, optional, default: None) — Optional revision precondition: the full `checksum` that read_note, write_note, or edit_note returned in JSON mode (the accepted revision's checksum; text output shows only its first 8 characters). When the note has changed since, the edit is refused with a revision conflict that names the current checksum, and nothing is written. A refused precondition never auto-creates the note. To refresh a stale checksum without reading the whole note, call read_note(identifier=..., include_content=False).
- **expected_checksum** (string | null, optional, default: None) — Optional revision precondition: the full `checksum` that bm_read, bm_write, or bm_edit returned in JSON mode (the accepted revision's checksum; text output shows only its first 8 characters). When the note has changed since, the edit is refused with a revision conflict that names the current checksum, and nothing is written. A refused precondition never auto-creates the note. To refresh a stale checksum without reading the whole note, call bm_read(identifier=..., include_content=False).
- **output_format** (string, optional, default: "text") — "text" returns a markdown summary of the edit and the note's resulting observations and relations. "json" returns machine-readable edit metadata.

## MCP USAGE
Expand Down
2 changes: 1 addition & 1 deletion src/basic_memory/man/man3/read-note(3).md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Accepted identifier forms (all verified):
- **include_frontmatter** (boolean, optional, default: False) — For unsliced JSON reads, include opening YAML in content; parsed frontmatter is returned either way. Explicit line ranges are never stripped. CLI: --frontmatter (--include-frontmatter is a deprecated alias).
- **start_line** (integer | null, optional, default: None) — First document line to read (1-based, inclusive). Defaults to 1 when only end_line is given. Line scans count the full Markdown, including frontmatter, matching cat's default line coordinates.
- **end_line** (integer | null, optional, default: None) — Last document line to read (inclusive); omitted means EOF. Out-of-file ranges return empty content; invalid/reversed ranges fail. With either bound, text output is numbered and JSON carries coordinates, has_more, and next_start_line/next_end_line. include_frontmatter does not strip an explicitly addressed range. Edits between calls may shift lines.
- **include_content** (boolean, optional, default: True) — Default true. Set to false to return only the note's metadata — title, permalink, file_path, the full accepted-revision checksum, and updated_at — without the body. Use it to refresh the checksum that edit_note/write_note take as expected_checksum without reading the whole note. Cannot be combined with start_line/end_line.
- **include_content** (boolean, optional, default: True) — Default true. Set to false to return only the note's metadata — title, permalink, file_path, the full accepted-revision checksum, and updated_at — without the body. Use it to refresh the checksum that bm_edit/bm_write take as expected_checksum without reading the whole note. Cannot be combined with start_line/end_line.

## MCP USAGE

Expand Down
4 changes: 2 additions & 2 deletions src/basic_memory/man/man3/write-note(3).md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ the same shape as edit_note's conflict. Without it,
- **tags** (array | string | null, optional, default: None) — Tags to categorize the note. Can be a list of strings, a comma-separated string, or None. Note: If passing from external MCP clients, use a string format (e.g. "tag1,tag2,tag3")
- **note_type** (string, optional, default: "note") — Type of note to create (stored in frontmatter `type:`). Defaults to "note". Can be "guide", "report", "config", "person", etc. The CLI flag is --type. A `type:` in content frontmatter takes precedence over this parameter, and this is what schema validation keys on.
- **metadata** (object | null, optional, default: None) — Optional dict of extra frontmatter fields merged into entity_metadata. Useful for schema notes or any note that needs custom YAML frontmatter beyond title/type/tags. Nested dicts are supported. Not available from the CLI.
- **overwrite** (boolean | null, optional, default: None) — If True, replace existing note on conflict. If False, error on conflict. If None (default), consult write_note_overwrite_default config setting. Overwrite replaces the body but merges frontmatter: keys the existing note has and this write does not set (custom fields) are kept, while keys this write sets (title, type, tags, metadata, or frontmatter in content) replace the old values. Existing frontmatter that cannot be parsed is discarded rather than merged. To remove a key, rewrite it with edit_note or edit the file.
- **expected_checksum** (string | null, optional, default: None) — Optional revision precondition for overwrite=True: the checksum of the note you read (from a JSON read_note, write_note or edit_note result). The note is replaced only while it is still that revision; otherwise the tool reports a revision conflict with the current checksum and changes nothing. Omit it to replace the note unconditionally.
- **overwrite** (boolean | null, optional, default: None) — If True, replace existing note on conflict. If False, error on conflict. If None (default), consult write_note_overwrite_default config setting. Overwrite replaces the body but merges frontmatter: keys the existing note has and this write does not set (custom fields) are kept, while keys this write sets (title, type, tags, metadata, or frontmatter in content) replace the old values. Existing frontmatter that cannot be parsed is discarded rather than merged. To remove a key, rewrite it with bm_edit or edit the file.
- **expected_checksum** (string | null, optional, default: None) — Optional revision precondition for overwrite=True: the checksum of the note you read (from a JSON bm_read, bm_write or bm_edit result). The note is replaced only while it is still that revision; otherwise the tool reports a revision conflict with the current checksum and changes nothing. Omit it to replace the note unconditionally.
- **output_format** (string, optional, default: "text") — "text" returns a markdown summary. "json" returns machine-readable metadata. A refused write (note exists, revision conflict, moved target, disallowed directory) is a tool error; in JSON mode its message is the payload with action: "conflict" and an error code.

## MCP USAGE
Expand Down
8 changes: 4 additions & 4 deletions src/basic_memory/mcp/resources/man.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
``memory://man`` is the index (apropos); ``memory://man/<page>`` is one page. Every
bundled page is also registered as a concrete resource so clients that browse
``resources/list`` see each page with its summary, while the template underneath
accepts any spelling of a page reference — ``search-notes(3)``, ``3/search-notes``,
``search_notes`` — so an agent's first guess resolves.
accepts any spelling of a page reference — ``bm-search(3)``, ``3/bm-search``,
``bm_search`` — so an agent's first guess resolves.
"""

from fastmcp.exceptions import ResourceError
Expand Down Expand Up @@ -35,9 +35,9 @@ async def manual_index() -> str:
uri=MANUAL_PAGE_TEMPLATE,
name="manual page",
description=(
"One manual page, e.g. memory://man/search-notes(3). Section-3 pages document "
"One manual page, e.g. memory://man/bm-search(3). Section-3 pages document "
"each MCP tool with parameters, verified examples, and gotchas. Any common "
"spelling of the page name resolves: search-notes(3), 3/search-notes, search_notes."
"spelling of the page name resolves: bm-search(3), 3/bm-search, bm_search."
),
mime_type="text/markdown",
)
Expand Down
2 changes: 1 addition & 1 deletion src/basic_memory/mcp/resources/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ async def read_note_markdown(identifier: str, context: Context | None) -> str:
except ToolError as error:
if "not found" in str(error).lower():
raise NoteNotFoundError(
f"No note {identifier!r}; search_notes can find the identifier"
f"No note {identifier!r}; bm_search can find the identifier"
) from error
raise
entity_id = resolved.json()["external_id"]
Expand Down
6 changes: 3 additions & 3 deletions src/basic_memory/mcp/tools/build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ async def build_context(
bool,
"Omit note bodies and entity observation lists for graph discovery; the server "
"skips loading observations. Preserve identifiers, relation targets and "
"pagination; use read_note for selected content. Not a guaranteed token budget.",
"pagination; use bm_read for selected content. Not a guaranteed token budget.",
] = False,
) -> dict[str, Any] | str:
"""Get context needed to continue a discussion within a specific project.
Expand Down Expand Up @@ -319,15 +319,15 @@ async def build_context(
if page_size > MAX_CONTEXT_PAGE_SIZE:
raise ValueError(
f"page_size must be <= {MAX_CONTEXT_PAGE_SIZE}, got {page_size}. "
"build_context is a bounded traversal starting point; request another "
"bm_context is a bounded traversal starting point; request another "
"page or follow the returned memory:// links."
)
if max_related < 0:
raise ValueError(f"max_related must be >= 0, got {max_related}")
if max_related > MAX_CONTEXT_RELATED_RESULTS:
raise ValueError(
f"max_related must be <= {MAX_CONTEXT_RELATED_RESULTS}, got {max_related}. "
"build_context is a bounded traversal starting point; follow the returned "
"bm_context is a bounded traversal starting point; follow the returned "
"memory:// links for more context."
)

Expand Down
Loading
Loading