BASIC_MEMORY_CLOUD_MODE env var was never checked in resolve_runtime_mode(),
so cloud deployments always ran as LOCAL mode. This caused file sync to start
in the cloud container, which then failed with "DATABASE_URL must be set when
using Postgres backend" because there's no local DB in cloud mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Returns all entities and resolved relations in a flat node/edge format
optimized for graph rendering. Replaces the frontend's use of the
recent memory endpoint which only returned a subset of relations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Test was polluted by other tests leaving rows in search_vector_chunks,
causing _needs_semantic_embedding_backfill to return False.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
The previous backfill trigger relied on Alembic revision tracking, but
alembic_version only stores the head revision — intermediate revisions
(like the backfill trigger) are invisible after a multi-step upgrade or
fresh DB creation.
Three changes fix this:
1. Replace Alembic revision check with a simple "entities exist but
embeddings are empty" check that works regardless of migration path
2. Generate embeddings during sync — after FTS indexing, batch-embed all
synced entities at the end of the sync operation
3. Add background backfill at MCP startup for the upgrade path (entities
already exist, no embeddings) without blocking server readiness
Also adds clear startup logging for semantic embedding status so issues
are easy to spot in the logs.
📋 Covers: fresh DB, upgrade from pre-embedding version, db reset,
interrupted backfill
Signed-off-by: Pedro Hernandez <pedro@basicmachines.co>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
- Add Annotated descriptions to note_types and entity_types parameters so
LLMs can distinguish frontmatter type filtering from knowledge graph item
type filtering (search.py, ui_sdk.py)
- Lowercase note_types values at filter time so "Chapter" matches stored
"chapter"
- Fix misleading entity_types references in schema.py guidance strings
(should be note_types)
- Add permalink pattern documentation note about full path matching
- Add test for note_types case-insensitive lowercasing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
In Postgres test mode, stale dependency_overrides on the module-level
FastAPI app allow _resolve_default_project_from_api() to query a live
database and return 'test-project' even when the test sets
default_project=None. Monkeypatch the async fallback in the three
affected tests to isolate config-based resolution from API leakage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
The test previously asserted that write_note fails when ConfigManager
has no default_project. With the API fallback, it now correctly
resolves to the database is_default project. Updated the test to
verify this fallback behavior instead of expecting an error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
In cloud mode, ConfigManager has no local config so default_project
is always None. Add API fallback in resolve_project_parameter that
queries /v2/projects/ for the default_project field. This fixes all
MCP tools that rely on project resolution (recent_activity, etc).
Removed discovery mode tests that simulated an invalid state by
clearing is_default — there must always be a default project.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Both search() and fetch() read default_project from ConfigManager,
which returns None in cloud mode. Remove the manual ConfigManager
lookup and let the underlying search_notes/read_note resolve the
project via get_project_client(), which works in both modes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
In cloud mode, ConfigManager has no local config file so
default_project always returned None. Add async
get_default_project_name() on ProjectService that falls back
to the database is_default flag when ConfigManager returns None.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Add configurable cache_dir, threads, and parallel settings for FastEmbed
to support cloud deployments where defaults fail. Cache embedding providers
at the process level to avoid re-creating heavy ONNX model instances.
- Add semantic_embedding_cache_dir, semantic_embedding_threads, and
semantic_embedding_parallel config fields
- Thread-safe provider cache with double-checked locking in factory
- Forward runtime knobs through to TextEmbedding and embed() calls
- Fix if/elif chain in factory for correct error handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Thread constructor no longer receives daemon=True, update mock
signatures to match. Also assert on the new "type": "event" field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Three issues prevented CLI analytics from reaching the Umami dashboard:
1. Wrong API endpoint — cloud.umami.is rejects /api/send, the JS tracker
uses api-gateway.umami.dev
2. Missing "type": "event" top-level field required by Umami v2 API
3. Non-browser User-Agent ("basic-memory-cli/...") triggers Umami's bot
detection, which silently drops events with {"beep":"boop"} 🤖
4. Daemon thread was killed before HTTP request completed on fast commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
🔧#640 — LinkResolver selects worst match instead of best
Replace `min(results, key=lambda x: x.score)` with `results[0]`.
Both SQLite and Postgres return results sorted best-first in SQL,
so using `results[0]` is backend-agnostic and correct.
🔧#641 — search_notes output_format="text" returns raw Pydantic model
Add `_format_search_markdown()` that formats SearchResponse as readable
markdown with title, permalink, score, and matched snippet per result.
Update prompts to use `output_format="json"` since they need structured
data for result counting and branching logic.
🔧#642 — metadata_filters with `note_type` key returns empty results
Add `_METADATA_KEY_ALIASES` mapping at the tool level that aliases
`note_type` → `type` before passing metadata_filters to the search query.
The frontmatter field is `type`, not `note_type`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Update both the compact and extended AI assistant guides with v0.19.0 changes:
- 📝 write_note overwrite guard: callout, edit_note examples, overwrite=True
- 🔍 Expanded search section: all search types, tag: shorthand, filter-only
searches, metadata_filters operators, min_similarity
- ⚠️ "Note already exists" error handling pattern
- 📋 Tool quick reference: updated params, added list_workspaces
- 🔗 memory:// URL: added cross-project format
- ✏️ Best practice: prefer edit_note for updates
Fix tag: shorthand parsing to handle multiple tags anywhere in the query.
Old parser only handled queries starting with "tag:" and broke on
"tag:coffee AND tag:brewing". New parser uses re.findall to extract all
tag:value tokens, strips boolean connectors, and preserves remaining text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
- Simplify `bm cloud status` output: remove verbose health check details
(status/version/timestamp), show simple "Cloud connected" / "Cloud not
connected" message instead
- Improve `bm reindex --project` error for cloud projects: distinguish
between "project not found" and "project is cloud-only" with a helpful
message explaining reindexing is a local operation
- Improve `bm project list` cloud error message: show the actual error
and soften the credentials suggestion
- Add tests for cloud status command (5 tests)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
The coverage collection had multiple issues:
- Wrong pytest markers caused 0 tests to run
- Postgres jobs silently skipped artifact uploads
- Coverage Summary job failed when artifacts were missing
- uv venv picked wrong Python version for coverage jobs
Simplify: every job just runs tests via `just` recipes. No more
dual code paths, artifact uploads, or summary aggregation job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
The coverage code paths for Postgres unit and integration jobs filtered
with `-m postgres`, but no tests use that marker. Postgres is selected
via BASIC_MEMORY_TEST_POSTGRES=1 env var. This caused 0 tests to run
on Python 3.12 (the only version with coverage: true).
- Postgres unit: remove `-m postgres` (matches `just test-unit-postgres`)
- Postgres integration: use `-m "not semantic"` (matches `just test-int-postgres`)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>