Repository navigation
feat(api): report semantic search freshness and identify the CLI to Cloud - #2056
Conversation
…loud Search responses now carry semantic_index_current_as_of: the oldest last-modified time among notes whose embeddings are not current, or null when all are current or semantic search is unavailable. A new entity.embedded_checksum (migration 0044) records the checksum a vector pass finished embedding, stamped with the checksum read before the pass read search rows; a partial index keeps the per-search lookup to the notes that are behind. The project and scoped search routes, the MCP all-projects merge, and bm_search text output all surface it. Every HTTP client the CLI and MCP open toward Basic Memory Cloud now sends User-Agent: basic-memory/<version> (cli), so Cloud can tell its own clients from unknown scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N9c6ouiT4Wid3arGR41Dv Signed-off-by: phernandez <paul@basicmachines.co>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d44bf1933f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await self._clear_entity_vectors(entity_id) | ||
| # An opted-out note owes no embeddings, so its content is as current as | ||
| # semantic search will ever make it. | ||
| await self.repository.record_entity_embedded_checksums({entity.id: entity.checksum}) |
There was a problem hiding this comment.
Clear prior deferral markers when stamping opted-out notes
If a previously deferred or failed note is changed to embed: false, stamping its checksum here does not make it current: the new freshness predicate also treats any non-null vector_sync_deferred_at as behind. _clear_entity_vectors() only deletes vector rows, and both opt-out paths bypass the repository sync boundary that clears deferral markers. Consequently, subsequent syncs and reindex passes keep reporting this opted-out note as behind indefinitely, pinning freshness for the entire searched scope even after all eligible notes finish embedding. Clear the obsolete deferral marker when completing opt-out cleanup, or exclude opted-out notes from the freshness calculation.
AGENTS.md reference: AGENTS.md:L185-L187
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 26984ab. Both opt-out paths now go through SearchService._settle_opted_out_entities. It clears vector_sync_deferred_at through record_entity_vector_deferrals(completed_entity_ids=...) and then stamps the checksum. Regression test test_opting_out_a_part_way_note_settles_it (deferred note, then embed: false, then freshness is None) fails without the fix and passes with it on SQLite and Postgres.
…dings Codex review: a note left part-way by a failed or sharded pass and then marked `embed: false` was stamped with its checksum, but its vector_sync_deferred_at marker stayed set. Opt-out bypasses the repository sync boundary that clears the marker, so the note pinned semantic_index_current_as_of forever. Both opt-out paths now clear the marker along with the stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N9c6ouiT4Wid3arGR41Dv Signed-off-by: phernandez <paul@basicmachines.co>
Why
Basic Memory Cloud is adding a fair-use limit for legacy plans (basic-memory-cloud#2974): past a daily chunk budget, a workspace's notes are still saved and full-text indexed, but their embeddings wait for the next UTC day. Two things were missing in core:
readyvector chunks, which look the same as current ones, so nothing in the schema showed that a note's embeddings lag its content.python-httpx/x.What changed
semantic_index_current_as_ofon search responsesSearchResponse.semantic_index_current_as_of: datetime | None. Semantic (vector) search reflects every note in the searched projects changed before this time. The value is the oldest last-modified time (entity.updated_at) among markdown notes whose embeddings are not current. It isnullwhen every note is current, when semantic search is unavailable (the keyword-only host fallback, [BUG] Fatal crash on startup when python.org Python 3.12 is present alongside Homebrew on macOS #711), or when the scope is empty.entity.embedded_checksum(migration0044): the checksum whose embeddings a vector-sync pass finished. A note is behind when this column is NULL (never embedded), differs fromchecksum(edited since), or the last pass left the note part-way (vector_sync_deferred_at).SearchRepositoryBase._sync_entity_vectors_internal, the one boundary every sync entry point goes through, reads checksums before the pass reads search rows and stamps them on the entities the pass finished. A write that lands during a pass leaves the note behind (the safe direction) rather than stamping content the pass never saw.SearchServicestamps notes that opted out withembed: false. They owe no embeddings, so they never hold the freshness value back.ix_entity_project_embedding_not_current (project_id, updated_at) WHERE <not current>holds only the notes that are behind, so the lookup is one indexedMINper search, with no per-result work. The predicate is one shared SQL constant, and both the index and the query use it, because SQLite only uses a partial index when the query repeats its predicate. A test asserts the SQLite plan uses it.SearchClientunchanged.bm_searchtext output addsSemantic results current as of YYYY-MM-DD HH:MM UTC.when the value is set. JSON output carries the field. Output is unchanged when everything is current. Documented inbm-search(3).ReadCacheChange.search_index), so cached search responses do not keep a stale value.CLI User-Agent
basic_memory/user_agent.pydefinesBASIC_MEMORY_USER_AGENT = "basic-memory/<version> (cli)". It is set as a client default on_cloud_client(the/proxyclient behindget_client, used by project sync and WebDAV upload),get_cloud_control_plane_client(WebDAV transfer and upload),cli/commands/cloud/api_client.py, andCLIAuth. A per-requestUser-Agentfrom a caller still wins, because httpx merges request headers over client headers.(cli). rclone (push/pull) is an external binary and is unchanged.Schema / deploy
0044_entity_embedded_checksum: adds a nullable column, runs oneUPDATEbackfill, and creates a partial index. It runs on SQLite and Postgres. Cloud picks it up with tenant schema migrations at deploy.Testing
just fast-check: clean (ruff and ty).tests/repository/test_semantic_index_freshness.py: all current givesNone; an edited note with its pass held back gives its modified time (older current notes do not count) and returns toNoneafter the pass; a never-embedded note; a part-way (deferred) note; an opted-out note; a stamp older than the checksum; non-markdown files; semantic unavailable givesNone; the SQLite plan uses the partial index.tests/api/v2/test_search_semantic_freshness.py: field on the project and scoped routes;nullwithout semantic search.tests/mcp/test_tool_bm_search.py: no note when current; JSON field and text line for one project and forall_projects.tests/test_entity_embedded_checksum_migration.py: backfill, index, downgrade (no duplicated generated columns).tests/test_cloud_user_agent.py: header on the wire (httpxMockTransport) for the proxy, control-plane, API, and auth clients; a caller's header wins.tests/services tests/indexing tests/mcp tests/cli tests/index tests/repository tests/api/v2 test-int/mcp): all pass. Postgres: freshness, readiness, deferred-resume, bm_search, API, and migration tests pass.🤖 Generated with Claude Code
https://claude.ai/code/session_016N9c6ouiT4Wid3arGR41Dv